基於非阻塞 socket 的多線程服務器

大家好,我是濤哥。

沒錯,今天週三。最近寫了一個服務端程序,能與多個客戶端進行通信。那麼,這個服務端是怎麼實現的呢?很簡單,它就是很常規的多線程服務器。

服務端實現

既然是多線程服務器,那麼,這些線程肯定是有明確分工的。主線程來處理網絡的連接,而通信線程來處理客戶端與服務端的通信。

而且,主線程要負責多個客戶端的連接請求,所以不能阻塞主線程哦,因此必須用非阻塞 socket. 於是,我寫的服務端程序如下:

#include <stdio.h>
#include <winsock2.h>   
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE  100
sockaddr_in addrClient; // 爲了讓通信線程獲取ip
// 通信線程
DWORD  WINAPI  CommThread(LPVOID  lp)  
{  
   SOCKET sClient = (SOCKET)(LPVOID)lp;  
   while(1)
   {    
     char buf[BUF_SIZE] = {0}; 
     int retVal = recv(sClient, buf, BUF_SIZE, 0);
     if(SOCKET_ERROR == retVal)   
     {   
       int err = WSAGetLastError();
       if(WSAEWOULDBLOCK == err) // 暫時沒有數據
       {
         Sleep(100);
         continue;
       }
     }   
    // 輸出客戶端連接信息
    SYSTEMTIME st;
    GetLocalTime(&st);
    char sDateTime[100] = {0};
    sprintf(sDateTime, "%4d-%2d-%2d %2d:%2d:%2d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
    printf("%s, The client is [%s:%d]. Msg from client is : %s\n", sDateTime, inet_ntoa(addrClient.sin_addr), addrClient.sin_port, buf);   
    char msg[BUF_SIZE] = {0};  
    sprintf(msg, "Message received is : %s", buf); 
    while(1)
    {
      retVal = send(sClient, msg, strlen(msg), 0);  // 回顯
      if(SOCKET_ERROR == retVal)   
      {   
        int err = WSAGetLastError();
        if(err == WSAEWOULDBLOCK)
        {
          Sleep(500);
          continue;
        }
      }
      break;
    }      
  }
   closesocket(sClient);   
}
int main()
{
    WSADATA wsd;   
    WSAStartup(MAKEWORD(2, 2), &wsd);   
    SOCKET sServer = socket(AF_INET, SOCK_STREAM, 0);   
    // 設置套接字爲非阻塞模式
    int iMode = 1;
    ioctlsocket(sServer, FIONBIO, (u_long FAR*) &iMode);
    // 設置服務器套接字地址   
    SOCKADDR_IN addrServ;   
    addrServ.sin_family = AF_INET;   
    addrServ.sin_port = htons(8888);
    addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);    
    bind(sServer,(const struct sockaddr*)&addrServ, sizeof(SOCKADDR_IN));   
    listen(sServer, 10);   
    printf("Server start...\n");
    int addrClientlen = sizeof(addrClient);   
    while(1)
    {
      SOCKET sClient = accept(sServer, (sockaddr FAR*)&addrClient, &addrClientlen);   
      if(INVALID_SOCKET == sClient)   
      {   
        int err = WSAGetLastError();
        if(WSAEWOULDBLOCK == err)  // 無法立即完成非阻塞套接字上的操作
        {
          Sleep(100);
          continue;
      }
    } 
    // 創建通信線程
    CreateThread(NULL, NULL, CommThread, (LPVOID)sClient, 0, NULL);
   }
    // 釋放套接字   
    closesocket(sServer);   
    WSACleanup();   
    getchar();
    return 0;
}

編譯並運行程序,開啓服務端,等待客戶端的連接。

客戶端實現

我們可以看到,上述服務端的程序需要有主線程和通信線程,而客戶端的程序就相對簡單了,主線程本身就是通信線程。程序如下:

#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
  WORD wVersionRequested;
  WSADATA wsaData;
  wVersionRequested = MAKEWORD(2, 2);
  WSAStartup( wVersionRequested, &wsaData );
  SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
  SOCKADDR_IN addrSrv;
  addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  addrSrv.sin_family = AF_INET;
  addrSrv.sin_port = htons(8888);
  connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
  send(sockClient, "hello world", strlen("hello world") + 1, 0);
  char recvBuf[100] = {0};
  recv(sockClient, recvBuf, 100, 0);
  printf("%s\n", recvBuf);
  while(1);
  closesocket(sockClient);
  WSACleanup();
  return 0;
}

我們編譯並運行程序,開啓 3 個客戶端進程,分別去連接服務端。實際驗證發現,服務端分配了三個通信線程,分別來應對 3 個客戶端,可以正常通信哦。

由此,我們就實現了多線程服務器,能處理多個客戶端的併發連接。這是一個非常初級的服務器模型,從事後端開發的同學,肯定會很熟悉這個典型模型。

建議有興趣的同學實際玩一下這個例子,加深對多線程服務器的理解,提高實戰能力。網絡編程就是這樣,定好思路,多編程,多抓包,多調試,很舒!

今天先這樣,咱們明天見。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/C2INQqU9nYhHqebNsktOuA