写了一个基于select的并发服务器

本文通过实例介绍了如何使用select模型实现一个并发服务器,能够同时处理多个客户端的通信请求。服务端通过非阻塞模式的socket监听和接收客户端连接,客户端则发送数据。实验证明,服务端可以并发地与多个客户端通信,且不会混淆各个客户端的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大家好,我是涛哥。

 

有很多读者对网络编程很感兴趣,觉得挺有意思,希望我多写一些网络编程方面的实战例子。没问题,这就来了。

对于后端开发同学而言,肯定了解多线程并发服务器,那么多路复用的并发服务又怎么玩呢?看完这篇就清楚了。

今天,我们来实战一下基于select模型的并发服务器,并给出实际实验效果,希望大家能有一些新的认识和收获。

如果你还不清楚,那我来手绘一下,你就明白啦:8个男生追求一个女生,这个女生同时处理8个男生的通信请求。

服务端实现

大家对select函数的作用应该很熟悉了,接下来,我们看看服务端程序,在关键的地方,基本都有详细的注释:


#include <stdio.h>
#include <winsock2.h>   
#pragma comment(lib, "ws2_32.lib")
 
int totalSockets = 0; // socket的总数
SOCKET socketArray[100]; // socket组成的数组,假设最多有100个socket吧
 
// 日志打印
void log(const char *pStr)
{
  FILE *fp = fopen("log.txt", "a");
  fprintf(fp, "log:%s\n", pStr);
  fclose(fp);
}
 
// 创建socket
void addToSocketArr(SOCKET s)   
{
  socketArray[totalSockets] = s;       
  totalSockets++; 
}
 
// 启动服务器
int main()
{
  log("into main");
 
  // 网络初始化
  WSADATA   wsaData;
  WSAStartup(MAKEWORD(1, 1), &wsaData);
 
  // 创建socket
  SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
  addToSocketArr(listenSocket);    
   
  // 服务地信息
  SOCKADDR_IN   srvAddr;
  srvAddr.sin_family = AF_INET;   
  srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);   
  srvAddr.sin_port = htons(8888);  
  
  // 绑定
  bind(listenSocket, (SOCKADDR*)&srvAddr, sizeof(srvAddr)); 
  
  // 监听
  listen(listenSocket, 10); 
  
  // 设置socket为非阻塞模式
  unsigned long nonBlock = 1;   
  ioctlsocket(listenSocket, FIONBIO, &nonBlock); 
 
  while(1)   
  {
    // 读集, 要记得清零初始化
    FD_SET   readSet;
    FD_ZERO(&readSet);   
 
    // 将每个socket都塞入读集,便于让内核来监测这些socket
    int i = 0;
    for(i = 0; i < totalSockets; i++)   
    {            
      FD_SET(socketArray[i], &readSet);    
    }
 
    // 应用程序通知内核来监测读集中的socket, 最后的NULL表示超时时间无限长
    int total = select(0, &readSet, NULL, NULL, NULL);  
      
    // 我们不考虑select失败,那么程序到这里,说明读集中必有socket处于"就绪状态"
    for(i = 0; i < totalSockets; i++)   
    {
      char szTmp[20] = {0};
      sprintf(szTmp, "%d", i);
      log(szTmp);
 
      if(socketArray[i] == listenSocket)   // 对监听的socket进行判断
      {
        log("socketArray[i] == listenSocket");
 
        if(FD_ISSET(listenSocket, &readSet)) // 如果该socket在可读集中,则表明有客户端来连接
        {
          log("listenSocket, socketArray[i] == listenSocket");
 
          // 接收来自于客户端的connect请求
          SOCKADDR_IN addrClient;  
          int len = sizeof(SOCKADDR);
          SOCKET acceptSocket = 0;
          acceptSocket = accept(listenSocket,(SOCKADDR*)&addrClient, &len);
 
          // 设置为非阻塞模式
          nonBlock = 1;   
          ioctlsocket(acceptSocket, FIONBIO, &nonBlock);
 
          // 添加到socket数组中
          addToSocketArr(acceptSocket);
        }
 
        continue;
      }
  
      // 注意:上面的listenSocket是不负责通信的,下面的一些socket都是负责通信的socket
 
      // 如果通信socket处于读就绪状态
      if (FD_ISSET(socketArray[i], &readSet))   
      {
        log("to receive"); 
        char szRecvBuf[1024] = {0};
        recv(socketArray[i], szRecvBuf, sizeof(szRecvBuf) - 1, 0);   
        printf("socketArray[i] is %d, %s\n", socketArray[i], szRecvBuf);
      }
    }       
   }
 
  // 省略了关闭socket等后续操作
 
  return 0;
}

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

客户端实现

接下来,我们看客户端的程序,很常规,也很简洁:


#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
 
int main()
{
  WORD wVersionRequested;
  WSADATA wsaData;
  wVersionRequested = MAKEWORD(1, 1);
  
  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));
  
  while(1)
  {
    char szSendBuf[100] = {0};
    scanf("%s", szSendBuf);
    send(sockClient, szSendBuf, strlen(szSendBuf) + 1, 0);
  }
 
  closesocket(sockClient);
  WSACleanup();
 
  return 0;
}

实战的结果

我们编译上述的客户端程序,然后连续运行8次客户端,也就产生了8个客户端进程。请注意,保持这8个客户端进程都运行,不要关闭他们。

然后,在8个客户端进程中依次输入1, 2, 3, 4, 5, 6, 7, 8后,分别按Enter发送,然后在第一个客户端进程中再次输入111,并按Enter发送。

接下来,我们一起看看服务端的结果:

socketArray[i] is 136, 1

socketArray[i] is 152, 2

socketArray[i] is 164, 3

socketArray[i] is 176, 4

socketArray[i] is 188, 5

socketArray[i] is 200, 6

socketArray[i] is 212, 7

socketArray[i] is 224, 8

socketArray[i] is 136, 111

可以看到,服务端可以并发地与8个客户端进行通信,而且还不会混淆它们,这就是基于select模型的并发服务器。

另外,从log.txt的日志中可以看出,尽管有8个客户端,但服务端的监听socket是唯一的,这也是很好理解的事情。

建议有兴趣的同学实际玩一下这个例子,加深对多路复用并发服务器的理解,提高实战能力。

网络编程就是这样,定好思路,多编程,多抓包,多调试,爽爽哒!大家如果有疑问请留言。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值