Google
 

1.    引言


多路复用I/O模型(select)是UNIX/LINUX用得的最多的一种I/O模型,在Windows下也


可做为一种异步I/O使用。本文给出该I/O模型处理多Client的简单(在主线程中)实现。


2.    关于select


select I/O模型是一种异步I/O模型,在单线程中Linux/WinNT默认支持64个客户端套


接字。这种I/O模型主要涉及以下几个函数及宏:


int select(…)、FD_ZERO、FD_SET、FD_ISSET以及FD_SETSIZE。


3.    用select开发一个Server


3.1 只支持单个Client


  // 相关初始化处理, 创建监听套接字


  listen(listensock, 5);


  clientsock = accept(listensock, NULL, NULL);


  for (; ;)


  {


       FD_ZERO(&readfds);


       FD_SET(clientsock, &readfds);


       nResult = select(


           0,     // Windows中这个参数忽略,Linux中在此处为1


           readfds,  // 可读套接字集合


           ……


       )


       if  (nResult = = SOCKET_ERROR)


          return –1;


       // 判断cliensock是否处于待读状态


       if (FD_ISSET(clientsock, &readfds))


      {

               // 相关处理


      }


  }


其实Winsock中的WSAEventSelect模型是与之类似的。


3.2 在单线程中支持63个Client


  SOCKET clientsockarray[FD_SETSIZE – 1];  // FD_SETSIZE is 64


// 相关初始化处理, 创建监听套接字


  


listen(listensock, 5);


// 初始化套接字数组


InitSock(clientsockarray);


FD_ZERO(&readfds);


FD_SET(listensock, &readfds);


for (; ;)


{


nRet = select(0, &readfds, NULL, NULL, NULL);


// 判断监听套接字是否可读


if (FD_ISSET(listensock, &readfds))


{


     clientsock = accept(listensock, NULL, NULL);


     // 将客户套接字放到套接字数组中


     if  (!InsertSock(clientsockarray, clientsock))


     {


          printf("客户端超过了63个,此次连接被拒绝.\n");


          closesocket(clientsock);


          continue;


      }  


}


  


// 逐个处理处于待决状态的套接字


for (nIndex = 0; nIndex < FD_SETSIZE - 1; nIndex++)


{


      if  (FD_ISSET(clientsockarray[nIndex], &readfds))


      {


           nRet = recv(clientsockarray[nIndex], buff, sizeof(buff), 0);


           if (nRet = = 0 || nRet = = SOCKET_ERROR)


           {


                closesocket(clientsockarray[nIndex]);


              clientsockarray[nIndex] = INVALID_SOCKET;


              continue;    // 继续处理套接字句柄数组中的其它套接字


           }


           // 将接受到的数据进行处理,此处只将其输出


           printf("%s\n", buff);


       }


    }




    // 初始化套接字集合


    FD_ZERO(&readfds);


    FD_SET(listensock, &readfds);


    // 将所有有效的套接字句柄加入到套接字句柄数组中


    for (nIndex = 0; nIndex < FD_SETSIZE - 1; nIndex++)


    {


if (clientsockarray[nIndex] != INVALID_SOCKET)


       FD_SET(clientsockarray[nIndex], &readfds);


    }


}




BOOL InsertSock(SOCKET* pSock, SOCKET sock)


{


     for  (int nIndex = 0; nIndex < FD_SETSIZE – 1; nIndex++)


     {

                   if  (pSock[nIndex] = = INVALID_SOCKET)


         {


             pSock[nIndex] = sock;


             break;


         }


     }




     if  (nIndex = = FD_SETSIZE – 1)


         return FALSE;


  


     return TRUE;


}




    上面只是给简要的代码,有的辅助函数也没有给出。用select支持多Client是比较方便的,在一个线程中可支持63个;可以采用多线程支持更大数量的Client。


4.    效率的讨论


4.1 对套接字数组扫描的效率问题


  在上面的程序中,存在多处对套接字句柄的扫描处理,这个肯定会影响效率。不知道各位朋友是怎么处理这个问题的。


4.2 对客户端实时响应问题


上面的程序处理待决的套接字的时候,是逐个处理的,如果响应某个Client的时间长到一定程度的话,肯定会影响对其它客户端的响应。我的解决方法是当这个套接字处于可读的待决状态的话,产生一个子线程去处理------接收数据和处理数据。这样主线程继续自己的工作,其它Client可以得及时的响应;当然当有大量的Client请求时,对线程的控制会成为一个新问题。


在UNIX/LINUX下做一个支持大量Client的Server的话,本人还是最先选择select这种I/O模型,这是因为我还不知道LINUX还有哪些更好的I/O模型。WinNT的话,还有CompletionPort和Overlapped,特别对于有大数据量传送,同时只有少量的Client时,Overlapped可以发挥相当大的作用。各位朋友请给出使用select的好方法