关键词搜索

源码搜索 ×
×

写了一个多线程服务器(基于非阻塞socket)

发布2022-02-20浏览1082次

详情内容

大家好,我是涛哥。

最近写了一个服务端程序,能与多个客户端进行通信。那么,这个服务端是怎么实现的呢?很简单,它就是很常规的多线程服务器。

如果你还不太清楚,那我画个图,你就明白了。三个男生都追一个女生,这个女生又不好拒绝,于是与三个男生之间保持通信关系。

                                                           涛哥手绘

服务端实现

既然是多线程服务器,那么,这些线程肯定是有明确分工的。主线程来处理网络的连接,而通信线程来处理客户端与服务端的通信。

而且,主线程要负责多个客户端的连接请求,所以不能阻塞主线程哦,因此必须用非阻塞socket. 于是,我写的服务端程序如下:

  1. #include <stdio.h>
  2. #include <winsock2.h>
  3. #include <windows.h>
  4. #pragma comment(lib, "ws2_32.lib")
  5. #define BUF_SIZE 100
  6. sockaddr_in addrClient; // 为了让通信线程获取ip
  7. // 通信线程
  8. DWORD WINAPI CommThread(LPVOID lp)
  9. {
  10. SOCKET sClient = (SOCKET)(LPVOID)lp;
  11. while(1)
  12. {
  13. char buf[BUF_SIZE] = {0};
  14. int retVal = recv(sClient, buf, BUF_SIZE, 0);
  15. if(SOCKET_ERROR == retVal)
  16. {
  17. int err = WSAGetLastError();
  18. if(WSAEWOULDBLOCK == err) // 暂时没有数据
  19. {
  20. Sleep(100);
  21. continue;
  22. }
  23. }
  24. // 输出客户端连接信息
  25. SYSTEMTIME st;
  26. GetLocalTime(&st);
  27. char sDateTime[100] = {0};
  28. sprintf(sDateTime, "%4d-%2d-%2d %2d:%2d:%2d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
  29. printf("%s, The client is [%s:%d]. Msg from client is : %s\n", sDateTime, inet_ntoa(addrClient.sin_addr), addrClient.sin_port, buf);
  30. char msg[BUF_SIZE] = {0};
  31. sprintf(msg, "Message received is : %s", buf);
  32. while(1)
  33. {
  34. retVal = send(sClient, msg, strlen(msg), 0); // 回显
  35. if(SOCKET_ERROR == retVal)
  36. {
  37. int err = WSAGetLastError();
  38. if(err == WSAEWOULDBLOCK)
  39. {
  40. Sleep(500);
  41. continue;
  42. }
  43. }
  44. break;
  45. }
  46. }
  47. closesocket(sClient);
  48. }
  49. int main()
  50. {
  51. WSADATA wsd;
  52. WSAStartup(MAKEWORD(2, 2), &wsd);
  53. SOCKET sServer = socket(AF_INET, SOCK_STREAM, 0);
  54. // 设置套接字为非阻塞模式
  55. int iMode = 1;
  56. ioctlsocket(sServer, FIONBIO, (u_long FAR*) &iMode);
  57. // 设置服务器套接字地址
  58. SOCKADDR_IN addrServ;
  59. addrServ.sin_family = AF_INET;
  60. addrServ.sin_port = htons(8888);
  61. addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
  62. bind(sServer,(const struct sockaddr*)&addrServ, sizeof(SOCKADDR_IN));
  63. listen(sServer, 10);
  64. printf("Server start...\n");
  65. int addrClientlen = sizeof(addrClient);
  66. while(1)
  67. {
  68. SOCKET sClient = accept(sServer, (sockaddr FAR*)&addrClient, &addrClientlen);
  69. if(INVALID_SOCKET == sClient)
  70. {
  71. int err = WSAGetLastError();
  72. if(WSAEWOULDBLOCK == err) // 无法立即完成非阻塞套接字上的操作
  73. {
  74. Sleep(100);
  75. continue;
  76. }
  77. }
  78. // 创建通信线程
  79. CreateThread(NULL, NULL, CommThread, (LPVOID)sClient, 0, NULL);
  80. }
  81. // 释放套接字
  82. closesocket(sServer);
  83. WSACleanup();
  84. getchar();
  85. return 0;
  86. }

 编译并运行程序,开启服务端,等待客户端的连接。

客户端实现

我们可以看到,上述服务端的程序需要有主线程和通信线程,而客户端的程序就相对简单了,主线程本身就是通信线程。程序如下:

  1. #include <winsock2.h>
  2. #include <stdio.h>
  3. #pragma comment(lib, "ws2_32.lib")
  4. int main()
  5. {
  6. WORD wVersionRequested;
  7. WSADATA wsaData;
  8. wVersionRequested = MAKEWORD(2, 2);
  9. WSAStartup( wVersionRequested, &wsaData );
  10. SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
  11. SOCKADDR_IN addrSrv;
  12. addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  13. addrSrv.sin_family = AF_INET;
  14. addrSrv.sin_port = htons(8888);
  15. connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
  16. send(sockClient, "hello world", strlen("hello world") + 1, 0);
  17. char recvBuf[100] = {0};
  18. recv(sockClient, recvBuf, 100, 0);
  19. printf("%s\n", recvBuf);
  20. while(1);
  21. closesocket(sockClient);
  22. WSACleanup();
  23. return 0;
  24. }

我们编译并运行程序,开启3个客户端进程,分别去连接服务端。实际验证发现,服务端分配了三个通信线程,分别来应对3个客户端,可以正常通信哦。

由此,我们就实现了多线程服务器,能处理多个客户端的并发连接。这是一个非常初级的服务器模型,从事后端开发的同学,肯定会很熟悉这个典型模型。

建议有兴趣的同学实际玩一下这个例子,加深对多线程服务器的理解,提高实战能力。网络编程就是这样,定好思路,多编程,多抓包,多调试,爽爽哒!

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载