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

大家好,我是涛哥。

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

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

写了一个多线程服务器(基于非阻塞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个客户端,可以正常通信哦。

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

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

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

上一篇:【Android基础】Fragment 详解之Fragment介绍


下一篇:Java实现验证码制作之一自己动手