当前位置:   article > 正文

基于 HPSocket , 实现 socket 通讯

hpsocket

HPSocket

HP-Socket 是一套通用的高性能 TCP/UDP/HTTP 通信框架,包含服务端组件、客户端组件和 Agent 组件,广泛适用于各种不同应用场景的 TCP/UDP/HTTP 通信系统,提供 C/C++、C#、Delphi、E(易语言)、Java、Python 等编程语言接口。HP-Socket 对通信层实现完全封装,应用程序不必关注通信层的任何细节;HP-Socket 提供基于事件通知模型的 API 接口,能非常简单高效地整合到新旧应用程序中。
HP-Socket的一些应用方向

HP-Socket不同于传统MVC框架,HP-Socket不仅可以用于TCP/UDP开发,同时还有更广阔的应用领域,例如即时通讯类、物联网、游戏、服务治理、其它跨平台服务器或者中间件,这无疑大大提高了程序开发者的视野。目前这些领域的程序开发者奇缺,如果想在通讯领域有自己的技术优势,不满足于每天的增删改查工作,或者想向架构师方向或者技术大牛的方向发展,HP-Socket都是非常值得学习的框架。建议开发者不仅会用,而且能基于HP-Socket开发出属于自己的开源项目,提升技能增加自己的影响力。

 github:  GitHub - ldcsaa/HP-Socket: High Performance TCP/UDP/HTTP Communication Component

 看云:  序言 · HP-SOCKET · 看云

            


前言

官方DEMO提供很多应用实例,但大多是MFC程序,所以准备搭建个基础版的,方便以后扩展。demo采用HPSocket的Pack模式,这样我们只需要处理逻辑,而不需要考虑分包、组包、粘包等情况

开发

  1. Connect

    • Server端 Start() 后, 会进入 OnPrepareListen() ,可获取server端 Ip , port 。
      1. EnHandleResult JSocketServer::OnPrepareListen(ITcpServer* pSender, SOCKET soListen)
      2. {
      3. TCHAR szAddress[50];
      4. int iAddressLen = sizeof(szAddress) / sizeof(TCHAR);
      5. USHORT usPort;
      6. pSender->GetListenAddress(szAddress, iAddressLen, usPort);
      7. if (m_prepare_listen != nullptr) {
      8. m_prepare_listen(szAddress, iAddressLen, usPort);
      9. }
      10. return HR_OK;
      11. }

    • Client端 Start()后,会进入OnConnectListen(),可获取Server端 ip ,port ,也可获取本地 ip ,port。 server端会进入 OnAccept() ,获取上线用户 connid , ip , port,

      1. Client:
      2. EnHandleResult JSocketClient::OnConnect(ITcpClient* pSender, CONNID dwConnID)
      3. {
      4. TCHAR szAddress[50];
      5. int iAddressLen = sizeof(szAddress) / sizeof(TCHAR);
      6. USHORT usPort;
      7. pSender->GetLocalAddress(szAddress, iAddressLen, usPort);
      8. if (m_connect_listen != nullptr) {
      9. m_connect_listen(dwConnID, szAddress, iAddressLen, usPort);
      10. }
      11. return HR_OK;
      12. }
      13. Server:
      14. EnHandleResult JSocketServer::OnAccept(ITcpServer* pSender, CONNID dwConnID, SOCKET soClient)
      15. {
      16. BOOL bPass = TRUE;
      17. TCHAR szAddress[50];
      18. int iAddressLen = sizeof(szAddress) / sizeof(TCHAR);
      19. USHORT usPort;
      20. pSender->GetRemoteAddress(dwConnID, szAddress, iAddressLen, usPort);
      21. if (m_accepte_listen != nullptr) {
      22. m_accepte_listen(dwConnID, szAddress, iAddressLen, usPort, nullptr);
      23. }
      24. return bPass ? HR_OK : HR_ERROR;
      25. }

    • 这里有个问题,就是不知道 client 和 server 如何通过连接绑定额外数据(比如用户名,地址等信息),类似于 SetConnectionExtra() 和 GetConnectionExtra() 那种方式。我采用的办法是当server收到用户上线通知后,主动将用户的信息发送给client端,client端收到消息后,将绑定信息发送给server,server收到通知后,缓存到集合中,然后将在线的用户通过json的方式发送给client端。(这种方式感觉不是很合理,暂且这么做把,以后再优化,有小伙伴知道的,麻烦指导一二。)

      • client端: 接收 server端 用户信息和在线用户通知

        1. JSocketResult JSocketClientBus::OnReceiveListen(ULONG connid, const UCHAR* data, int bodyLen) {
        2. JPkgBodyByte* bodyInfo = (JPkgBodyByte*)data;
        3. JUserInfo srcUserInfo;
        4. JUserInfo desUserInfo;
        5. GetUserInfoByBodyInfo(bodyInfo, srcUserInfo, desUserInfo);
        6. /*
        7. 当客户端连接服务端成功后,无法获取服务端中客户端的connid,所以客户端需要接受服务端的用户信息,即connid。
        8. 服务端当接受 OnAccept 监听后 ,给客户端发送用户信息指令 ,客户端收到用户信息指令后,保存用户信息。
        9. 最后发送消息给服务端,服务端接受后,更新本地该用户信息.(即Ip , Port , AliasName ...)
        10. */
        11. if (bodyInfo->msg_data_type == MSG_USER_INFO_TYPE) {
        12. m_userInfo.usId = desUserInfo.usId;
        13. m_userInfo.usAddress = new char[20];
        14. lstrcpyA(m_userInfo.usAddress, desUserInfo.usAddress);
        15. m_userInfo.usPort = desUserInfo.usPort;
        16. m_userInfo.usName = new char[strlen(m_aliasName) * sizeof(char)];
        17. lstrcpyA(m_userInfo.usName, m_aliasName);
        18. m_userInfo.usState = ONLINE_STATUS;
        19. /* 给服务端发送用户指令, 更新 AliasName */
        20. std::wstring s = L"updateUser";
        21. if (!SendMsgInfo(bodyInfo->msg_data_type, &m_userInfo, const_cast<wchar_t*>(s.c_str()), s.length())) {
        22. #if IS_LOG
        23. JSocketHelper::LogMsg("Client OnReceiveListen", m_userInfo, "发送用户信息给server失败.", GetUserLastErrorCode());
        24. #endif // IS_TOAST
        25. }
        26. if (m_connectAPIListen != nullptr) {
        27. m_connectAPIListen(&m_userInfo);
        28. }
        29. return JResult_OK;
        30. }
        31. // 获取用户列表
        32. if (bodyInfo->msg_data_type == MSG_USER_LIST_TYPE) {
        33. if (m_receiveAPIListen != nullptr) {
        34. m_receiveAPIListen(bodyInfo->msg_data_type, &srcUserInfo, &desUserInfo, bodyInfo->desc, bodyInfo->desLen);
        35. }
        36. return JResult_OK;
        37. }
        38. if (bodyInfo->msg_data_type == MSG_INFO_TYPE) {
        39. if (m_receiveAPIListen != nullptr) {
        40. m_receiveAPIListen(bodyInfo->msg_data_type, &srcUserInfo, &desUserInfo, bodyInfo->desc, bodyInfo->desLen);
        41. }
        42. return JResult_OK;
        43. }
        44. if (bodyInfo->msg_data_type == MSG_USER_ONLINE_TYPE || bodyInfo->msg_data_type == MSG_USER_OFFLINE_TYPE) {
        45. /* 有终端上线 */
        46. if (bodyInfo->msg_data_type == MSG_USER_ONLINE_TYPE && m_acceptAPIListen != nullptr) {
        47. m_acceptAPIListen(&srcUserInfo);
        48. return JResult_OK;
        49. }
        50. /* 有终端下线 */
        51. if (bodyInfo->msg_data_type == MSG_USER_OFFLINE_TYPE && m_closeAPIListen != nullptr) {
        52. m_closeAPIListen(&srcUserInfo);
        53. return JResult_OK;
        54. }
        55. return JResult_OK;
        56. }
        57. return JResult_OK;
        58. }

      • server端:

        • 收到client端发送的用户信息后,将在线用户信息通过json方式发送给client端

        • 将用户信息发送给别的client端,通知该用户上线了。

        • 将用户信息缓存到集合中。

          1. JSocketResult JSocketServerBus::OnReceiveListen(ULONG connid, const UCHAR* data, int bodyLen) {
          2. JPkgBodyByte* bodyInfo = (JPkgBodyByte*)data;
          3. JUserInfo srcUserInfo;
          4. JUserInfo desUserInfo;
          5. GetUserInfoByBodyInfo(bodyInfo, srcUserInfo, desUserInfo);
          6. /* 更新用户信息 (connid, ip, port, AliasName ..)*/
          7. if (bodyInfo->msg_data_type == MSG_USER_INFO_TYPE) {
          8. m_user_collection.push_back(srcUserInfo);
          9. #if IS_LOG
          10. JSocketHelper::LogMsg("Server OnReceiveListen", srcUserInfo, "用户上线.", GetUserLastErrorCode());
          11. #endif // IS_TOAST
          12. if (m_acceptAPIListen != nullptr) {
          13. m_acceptAPIListen(&srcUserInfo);
          14. }
          15. // 通知用户上线
          16. post_user_info_to_users(MSG_USER_ONLINE_TYPE, srcUserInfo, "online");
          17. // 发送用户列表
          18. post_users_list_to_user(srcUserInfo);
          19. return JResult_OK;
          20. }
          21. // 发送消息
          22. if (bodyInfo->msg_data_type == MSG_INFO_TYPE) {
          23. if (!m_pSocketServer->SendMsg(desUserInfo.usId, (BYTE*)data, bodyLen)) {
          24. #if IS_LOG
          25. JSocketHelper::LogMsg("Server OnReceiveListen", srcUserInfo, desUserInfo, bodyInfo->desc, "转发消息失败.", GetUserLastErrorCode());
          26. #endif // IS_TOAST
          27. }
          28. if (m_receiveAPIListen != nullptr) {
          29. m_receiveAPIListen(bodyInfo->msg_data_type, &srcUserInfo, &desUserInfo, bodyInfo->desc, bodyInfo->desLen);
          30. }
          31. }
          32. return JResult_OK;
          33. }

  2. Send 

    1. client端发送消息给server端,server端收到消息后,转发给接受的client端。
      1. client:
      2. // msg_data_type: 收发消息类型 desUser:接受者信息 msg:内容(UTF-8 byte[])
      3. bool JSocketBusBase::SendMsgInfo(int msg_data_type, JUserInfo *desUser, wchar_t* msg, int len) {
      4. int bodyLen;
      5. JPkgBodyByte* info = JSocketHelper::GetMsgBodyInfo(msg_data_type, &m_userInfo, desUser, msg, len, bodyLen);
      6. bool flag = Get()->SendMsg(desUser->usId, (BYTE*)info, bodyLen);
      7. if (!flag) {
      8. #if IS_LOG
      9. JSocketHelper::LogMsg("Base SendMsgInfo", m_userInfo, *desUser, msg, "转发信息失败.", GetUserLastErrorCode());
      10. #endif // IS_LOG
      11. }
      12. free(info);
      13. return flag;
      14. }
      15. server:
      16. // 根据connid,转发消息
      17. JSocketResult JSocketServerBus::OnReceiveListen(ULONG connid, const UCHAR* data, int bodyLen) {
      18. JPkgBodyByte* bodyInfo = (JPkgBodyByte*)data;
      19. JUserInfo srcUserInfo;
      20. JUserInfo desUserInfo;
      21. GetUserInfoByBodyInfo(bodyInfo, srcUserInfo, desUserInfo);
      22. // 发送消息
      23. if (bodyInfo->msg_data_type == MSG_INFO_TYPE) {
      24. if (!m_pSocketServer->SendMsg(desUserInfo.usId, (BYTE*)data, bodyLen)) {
      25. #if IS_LOG
      26. JSocketHelper::LogMsg("Server OnReceiveListen", srcUserInfo, desUserInfo, bodyInfo->desc, "转发消息失败.", GetUserLastErrorCode());
      27. #endif // IS_LOG
      28. }
      29. if (m_receiveAPIListen != nullptr) {
      30. m_receiveAPIListen(bodyInfo->msg_data_type, &srcUserInfo, &desUserInfo, bodyInfo->desc, bodyInfo->desLen);
      31. }
      32. }
      33. return JResult_OK;
      34. }

  3. DisConnect

    1. client端断开连接,server端在OnCloseListen()接受消息后,通知其他client端,该用户下线,并删除用户信息。client端在OnClose中接受到消息后,我在业务逻辑层中,回调了shutDown,我想的是和server端做到统一。实际client端没有shutDown回调。
      1. JSocketResult JSocketServerBus::OnCloseListen(ULONG connid) {
      2. JUserInfo user;
      3. BOOL flag = get_user_info_with_collection(connid, true, user);
      4. #if IS_LOG
      5. JSocketHelper::LogMsg("Server OnCloseListen", user, "用户下线.", GetUserLastErrorCode());
      6. #endif // IS_TOAST
      7. // 给用户发送下线通知
      8. if (flag) {
      9. post_user_info_to_users(MSG_USER_OFFLINE_TYPE, user, "offline");
      10. }
      11. else {
      12. // 缓存中没有id(todo)
      13. user.usId = connid;
      14. post_user_info_to_users(MSG_USER_OFFLINE_TYPE, user, "offline");
      15. }
      16. if (m_closeAPIListen != nullptr) {
      17. m_closeAPIListen(&user);
      18. }
      19. return JResult_OK;
      20. }

    2. server端断开连接后,会在OnShutdownListen中接受到消息,其实不光是主动断开连接,异常断线,比如网络异常等情况也会进入此回调函数中。所以我在这个回调中增加了自动重连,也就是当用户非主动断开连接,则会自动重连。同理,client端也是如此。

      1. client:
      2. JSocketResult JSocketClientBus::OnCloseListen(ULONG connid) {
      3. // 正在重连中。。
      4. if (m_enState == EN_RECONNECTING)
      5. return JResult_OK;
      6. if (m_shutDownAPIListen != nullptr) {
      7. m_shutDownAPIListen();
      8. }
      9. // 主动断开连接,不进行自动重连..
      10. if (m_enState == EN_STOPPED || m_enState == EN_STOPPING)
      11. return JResult_OK;
      12. clear();
      13. reconnect();
      14. return JResult_OK;
      15. }
      16. server:
      17. JSocketResult JSocketServerBus::OnShutdownListen() {
      18. // 正在重连中。。
      19. if (m_enState == EN_RECONNECTING)
      20. return JResult_OK;
      21. if (m_shutDownAPIListen != nullptr) {
      22. m_shutDownAPIListen();
      23. }
      24. if (m_enState == EN_STOPPED || m_enState == EN_STOPPING)
      25. return JResult_OK;
      26. clear();
      27. reconnect();
      28. return JResult_OK;
      29. }

  4. Other

    1. 设置一些基础的属性设置接口和辅助函数,比如SetMaxPackSize、SetPackHeaderFlag、SetKeepAliveTime、SetKeepAliveTimeInterval、GetLastError等等,官方demo中都有详细的介绍。
  5. DLL 接口

    1. dll 接口中参考官方demo也设置了对应的回调函数,但调整了几个,具体如下:
    2. 主要想的是 client 和 server 端 保持统一。
  1. client:
  2. /* client端 连接成功通知 */
  3. typedef void(*JSocketOnConnectListener)(JUserInfo *userAPI);
  4. /* client端 接受用户上线通知 */
  5. typedef void(*JSocketOnAcceptListener)(JUserInfo *server);
  6. /* client端 接受消息通知 srcUser:发送者 desUser:接受者 info:信息 */
  7. typedef void(*JSocketOnReceiveListener)(int msg_data_type, JUserInfo *srcUser, JUserInfo *desUser, wchar_t *info, int len);
  8. /* client端 已发送消息通知*/
  9. typedef void(*JSocketOnSendListener)(JUserInfo *body, wchar_t *info, int len);
  10. /* client端 用户下线通知 */
  11. typedef void(*JSocketOnCloseListener)(JUserInfo *user);
  12. /* client端 断开连接通知 */
  13. typedef void(*JSocketOnShutDownListener)();
  14. server:
  15. /* server端 连接成功通知 */
  16. typedef void(*JSocketOnPrepareListener)(JUserInfo *server);
  17. /* server端 接受用户上线通知 */
  18. typedef void(*JSocketOnAcceptListener)(JUserInfo *server);
  19. /* server端 接受消息通知 srcUser:发送者 desUser:接受者 info:信息 */
  20. typedef void(*JSocketOnReceiveListener)(int msg_data_type, JUserInfo *srcUser, JUserInfo *desUser, wchar_t *info, int len);
  21. /* server端 用户下线通知 */
  22. typedef void(*JSocketOnCloseListener)(JUserInfo *user);
  23. /* server端 断开连接通知 */
  24. typedef void(*JSocketOnShutDownListener)();

C# demo程序 + C++ DLL

效果:

      

地址: https://download.csdn.net/download/haiyangyunbao813/14999630


END

  1. 以上内容就基本搭建好了一个基础版本的socket通讯,等以后实际落地应用在慢慢扩展,有需要的小伙伴可以参考参考。

  2. 此程序么有怎么测试过,也没有落地实际应用过,只是我简单的测试了一下,所以说肯定会有很多潜在bug,以及不足的地方,所以说各位大神,大佬们,有啥问题请及时指正,非常感谢。

  3. 最后还有一个星期就过年了,在此祝各位小伙伴们新年快乐。

                    

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/128559
推荐阅读
相关标签
  

闽ICP备14008679号