select()函数的一个奇怪现象

时间:2022-09-11 12:37:19

DWORD WINAPI CCMPPAPI::thread_recv( LPVOID pdata)
{
...
for(;;)
{
        FD_ZERO( &fdset);
        FD_SET( cmpp._soc, &fdset);
err = select(
0,
&fdset,
NULL,
NULL,
&timeout);
// 出错
if( err == SOCKET_ERROR)
        {
            #ifdef _DEBUG
cout<<"Socket 等待数据错误.错误代码 = "<<WSAGetLastError()<<endl<<endl;
            #endif
continue;//break;
        }
// 超时
if( err == 0)
continue;
// 先接收包头部分,以确定包的大小、类型
  err = cmpp._recv( (char *)&_CCMPPAPI->m_pkg.head, sizeof( _CCMPPAPI->m_pkg.head));
if(err == 0) //TODO
continue;
if( err != sizeof( _CCMPPAPI->m_pkg.head))
        {
            #ifdef _DEBUG
cout<<"接收返回值=" <<err<<",长度 = "<<sizeof( _CCMPPAPI->m_pkg.head)<<", 错误 = ,"<<WSAGetLastError()<<endl<<endl;
            #endif
            continue;//break;
        }
// 接收包体
err = cmpp._recv( _CCMPPAPI->m_pkg.data, ntohl( _CCMPPAPI->m_pkg.head.size )-sizeof( _CCMPPAPI->m_pkg.head));
if( err == SOCKET_ERROR)
        {
            #ifdef _DEBUG
cout<<"Socket 等待数据错误.错误代码 = "<<WSAGetLastError()<<endl<<endl;
            #endif
continue;//break;
        }
  ...
}

这是一个客户端程序,登录上服务器以后没问题,一旦进行一次通信,select函数返回就不正常了.这里的select一直返回是1, 我另外一段的服务器就算关闭的, 这边select也返回1, 是什么个情况,

4 个解决方案

#1


高分问题,没人回答啊!

#2


你的Select模型用法不对。具体参考
Select工作流程
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
#include <winsock2.h>   
#include <stdio.h>   

#define PORT  5150   

#define MSGSIZE  1024   

#pragma comment(lib, "ws2_32.lib")   

int g_iTotalConn1 = 0;  
int g_iTotalConn2 = 0;   

SOCKET g_CliSocketArr1[FD_SETSIZE];  
SOCKET g_CliSocketArr2[FD_SETSIZE];  

DWORD WINAPI WorkerThread1(LPVOID lpParam);   
int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);

DWORD WINAPI WorkerThread2(LPVOID lpParam);   
int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);


int main(int argc, char* argv[])   
{   
 WSADATA wsaData;   
 SOCKET sListen, sClient;   
 SOCKADDR_IN local, client;   
 int iAddrSize = sizeof(SOCKADDR_IN);   
 DWORD dwThreadId;   
 // Initialize windows socket library   
 WSAStartup(0x0202, &wsaData);   
 // Create listening socket   
 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   
 // Bind   

 local.sin_family = AF_INET;   
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
 local.sin_port = htons(PORT);   
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));   

 // Listen   

 listen(sListen, 3);   

 // Create worker thread   

 CreateThread(NULL, 0, WorkerThread1, NULL, 0, &dwThreadId);  
// CreateThread(NULL, 0, WorkerThread2, NULL, 0, &dwThreadId);  

 while (TRUE)    
 {     
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc1, 0);
  printf("1:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn1);   
  g_CliSocketArr1[g_iTotalConn1++] = sClient;  
/*         
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc2, 0);
  printf("2:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn2);   
  g_CliSocketArr2[g_iTotalConn2++] = sClient;   */

 }   
 return 0;   
}   

DWORD WINAPI WorkerThread1(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   FD_SET(g_CliSocketArr1[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr1[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr1[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
    // Client socket closed   
     printf("1:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr1[i]);   
     if (i < g_iTotalConn1-1)    
     {   
      g_CliSocketArr1[i--] = g_CliSocketArr1[--g_iTotalConn1];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr1[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn1 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

DWORD WINAPI WorkerThread2(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   FD_SET(g_CliSocketArr2[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr2[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr2[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
     // Client socket closed   
     printf("2:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr2[i]);   
     if (i < g_iTotalConn2-1)    
     {   
      g_CliSocketArr2[i--] = g_CliSocketArr2[--g_iTotalConn2];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr2[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn2 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

服务器的几个主要动作如下:
1.创建监听套接字,绑定,监听;
2.创建工作者线程;
3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
4.接受客户端的连接。
 
这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。 
 
工作者线程里面是一个死循环,一次循环完成的动作是:
1.将当前所有的客户端套接字加入到读集fdread中;
2.调用select函数;
3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)
 
除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
 
关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
 
对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
 
注意:
1.那个最大的连接数是指每一个线程可以处理的连接数,当你有多个线程时,连接数是可以无限增长的,不过此时的效率就比较低。
2.关于发送操作writefds的问题,当套接字成功连接或者一个套接字刚刚成功接收信息时都会调用。
3.我们通常会创建一个套接字来进行监听,之后用accept返回的套接字进行通信。这里要注意一点,用于监听的套接字在没有新连接时也会进行writefds的操作。

#3


引用 2 楼 YLCN2010 的回复:
你的Select模型用法不对。具体参考
Select工作流程
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
#include <winsock2.h>   
#include <stdio.h>   

#define PORT  5150   

#define MSGSIZE  1024   

#pragma comment(lib, "ws2_32.lib")   

int g_iTotalConn1 = 0;  
int g_iTotalConn2 = 0;   

SOCKET g_CliSocketArr1[FD_SETSIZE];  
SOCKET g_CliSocketArr2[FD_SETSIZE];  

DWORD WINAPI WorkerThread1(LPVOID lpParam);   
int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);

DWORD WINAPI WorkerThread2(LPVOID lpParam);   
int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);


int main(int argc, char* argv[])   
{   
 WSADATA wsaData;   
 SOCKET sListen, sClient;   
 SOCKADDR_IN local, client;   
 int iAddrSize = sizeof(SOCKADDR_IN);   
 DWORD dwThreadId;   
 // Initialize windows socket library   
 WSAStartup(0x0202, &wsaData);   
 // Create listening socket   
 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   
 // Bind   

 local.sin_family = AF_INET;   
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
 local.sin_port = htons(PORT);   
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));   

 // Listen   

 listen(sListen, 3);   

 // Create worker thread   

 CreateThread(NULL, 0, WorkerThread1, NULL, 0, &dwThreadId);  
// CreateThread(NULL, 0, WorkerThread2, NULL, 0, &dwThreadId);  

 while (TRUE)    
 {     
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc1, 0);
  printf("1:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn1);   
  g_CliSocketArr1[g_iTotalConn1++] = sClient;  
/*         
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc2, 0);
  printf("2:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn2);   
  g_CliSocketArr2[g_iTotalConn2++] = sClient;   */

 }   
 return 0;   
}   

DWORD WINAPI WorkerThread1(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   FD_SET(g_CliSocketArr1[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr1[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr1[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
    // Client socket closed   
     printf("1:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr1[i]);   
     if (i < g_iTotalConn1-1)    
     {   
      g_CliSocketArr1[i--] = g_CliSocketArr1[--g_iTotalConn1];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr1[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn1 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

DWORD WINAPI WorkerThread2(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   FD_SET(g_CliSocketArr2[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr2[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr2[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
     // Client socket closed   
     printf("2:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr2[i]);   
     if (i < g_iTotalConn2-1)    
     {   
      g_CliSocketArr2[i--] = g_CliSocketArr2[--g_iTotalConn2];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr2[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn2 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

服务器的几个主要动作如下:
1.创建监听套接字,绑定,监听;
2.创建工作者线程;
3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
4.接受客户端的连接。
 
这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。 
 
工作者线程里面是一个死循环,一次循环完成的动作是:
1.将当前所有的客户端套接字加入到读集fdread中;
2.调用select函数;
3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)
 
除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
 
关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
 
对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
 
注意:
1.那个最大的连接数是指每一个线程可以处理的连接数,当你有多个线程时,连接数是可以无限增长的,不过此时的效率就比较低。
2.关于发送操作writefds的问题,当套接字成功连接或者一个套接字刚刚成功接收信息时都会调用。
3.我们通常会创建一个套接字来进行监听,之后用accept返回的套接字进行通信。这里要注意一点,用于监听的套接字在没有新连接时也会进行writefds的操作。


我select用法是正确的啊, 我这边是客户端,所以只用检查一个套接字,现在遇到的问题是,经过一次或多次通讯以后,尽管服务器没有发送数据,select返回仍然是1.按理应该是返回0才对

#4


引用 3 楼 K_Lord 的回复:
Quote: 引用 2 楼 YLCN2010 的回复:

你的Select模型用法不对。具体参考
Select工作流程
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
#include <winsock2.h>   
#include <stdio.h>   

#define PORT  5150   

#define MSGSIZE  1024   

#pragma comment(lib, "ws2_32.lib")   

int g_iTotalConn1 = 0;  
int g_iTotalConn2 = 0;   

SOCKET g_CliSocketArr1[FD_SETSIZE];  
SOCKET g_CliSocketArr2[FD_SETSIZE];  

DWORD WINAPI WorkerThread1(LPVOID lpParam);   
int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);

DWORD WINAPI WorkerThread2(LPVOID lpParam);   
int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);


int main(int argc, char* argv[])   
{   
 WSADATA wsaData;   
 SOCKET sListen, sClient;   
 SOCKADDR_IN local, client;   
 int iAddrSize = sizeof(SOCKADDR_IN);   
 DWORD dwThreadId;   
 // Initialize windows socket library   
 WSAStartup(0x0202, &wsaData);   
 // Create listening socket   
 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   
 // Bind   

 local.sin_family = AF_INET;   
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
 local.sin_port = htons(PORT);   
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));   

 // Listen   

 listen(sListen, 3);   

 // Create worker thread   

 CreateThread(NULL, 0, WorkerThread1, NULL, 0, &dwThreadId);  
// CreateThread(NULL, 0, WorkerThread2, NULL, 0, &dwThreadId);  

 while (TRUE)    
 {     
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc1, 0);
  printf("1:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn1);   
  g_CliSocketArr1[g_iTotalConn1++] = sClient;  
/*         
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc2, 0);
  printf("2:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn2);   
  g_CliSocketArr2[g_iTotalConn2++] = sClient;   */

 }   
 return 0;   
}   

DWORD WINAPI WorkerThread1(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   FD_SET(g_CliSocketArr1[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr1[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr1[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
    // Client socket closed   
     printf("1:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr1[i]);   
     if (i < g_iTotalConn1-1)    
     {   
      g_CliSocketArr1[i--] = g_CliSocketArr1[--g_iTotalConn1];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr1[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn1 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

DWORD WINAPI WorkerThread2(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   FD_SET(g_CliSocketArr2[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr2[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr2[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
     // Client socket closed   
     printf("2:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr2[i]);   
     if (i < g_iTotalConn2-1)    
     {   
      g_CliSocketArr2[i--] = g_CliSocketArr2[--g_iTotalConn2];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr2[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn2 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

服务器的几个主要动作如下:
1.创建监听套接字,绑定,监听;
2.创建工作者线程;
3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
4.接受客户端的连接。
 
这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。 
 
工作者线程里面是一个死循环,一次循环完成的动作是:
1.将当前所有的客户端套接字加入到读集fdread中;
2.调用select函数;
3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)
 
除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
 
关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
 
对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
 
注意:
1.那个最大的连接数是指每一个线程可以处理的连接数,当你有多个线程时,连接数是可以无限增长的,不过此时的效率就比较低。
2.关于发送操作writefds的问题,当套接字成功连接或者一个套接字刚刚成功接收信息时都会调用。
3.我们通常会创建一个套接字来进行监听,之后用accept返回的套接字进行通信。这里要注意一点,用于监听的套接字在没有新连接时也会进行writefds的操作。


我select用法是正确的啊, 我这边是客户端,所以只用检查一个套接字,现在遇到的问题是,经过一次或多次通讯以后,尽管服务器没有发送数据,select返回仍然是1.按理应该是返回0才对


返回1不代表该套接字已发送了数据,而是代表你可以在这个套接字上面发送数据。


#1


高分问题,没人回答啊!

#2


你的Select模型用法不对。具体参考
Select工作流程
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
#include <winsock2.h>   
#include <stdio.h>   

#define PORT  5150   

#define MSGSIZE  1024   

#pragma comment(lib, "ws2_32.lib")   

int g_iTotalConn1 = 0;  
int g_iTotalConn2 = 0;   

SOCKET g_CliSocketArr1[FD_SETSIZE];  
SOCKET g_CliSocketArr2[FD_SETSIZE];  

DWORD WINAPI WorkerThread1(LPVOID lpParam);   
int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);

DWORD WINAPI WorkerThread2(LPVOID lpParam);   
int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);


int main(int argc, char* argv[])   
{   
 WSADATA wsaData;   
 SOCKET sListen, sClient;   
 SOCKADDR_IN local, client;   
 int iAddrSize = sizeof(SOCKADDR_IN);   
 DWORD dwThreadId;   
 // Initialize windows socket library   
 WSAStartup(0x0202, &wsaData);   
 // Create listening socket   
 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   
 // Bind   

 local.sin_family = AF_INET;   
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
 local.sin_port = htons(PORT);   
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));   

 // Listen   

 listen(sListen, 3);   

 // Create worker thread   

 CreateThread(NULL, 0, WorkerThread1, NULL, 0, &dwThreadId);  
// CreateThread(NULL, 0, WorkerThread2, NULL, 0, &dwThreadId);  

 while (TRUE)    
 {     
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc1, 0);
  printf("1:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn1);   
  g_CliSocketArr1[g_iTotalConn1++] = sClient;  
/*         
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc2, 0);
  printf("2:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn2);   
  g_CliSocketArr2[g_iTotalConn2++] = sClient;   */

 }   
 return 0;   
}   

DWORD WINAPI WorkerThread1(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   FD_SET(g_CliSocketArr1[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr1[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr1[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
    // Client socket closed   
     printf("1:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr1[i]);   
     if (i < g_iTotalConn1-1)    
     {   
      g_CliSocketArr1[i--] = g_CliSocketArr1[--g_iTotalConn1];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr1[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn1 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

DWORD WINAPI WorkerThread2(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   FD_SET(g_CliSocketArr2[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr2[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr2[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
     // Client socket closed   
     printf("2:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr2[i]);   
     if (i < g_iTotalConn2-1)    
     {   
      g_CliSocketArr2[i--] = g_CliSocketArr2[--g_iTotalConn2];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr2[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn2 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

服务器的几个主要动作如下:
1.创建监听套接字,绑定,监听;
2.创建工作者线程;
3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
4.接受客户端的连接。
 
这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。 
 
工作者线程里面是一个死循环,一次循环完成的动作是:
1.将当前所有的客户端套接字加入到读集fdread中;
2.调用select函数;
3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)
 
除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
 
关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
 
对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
 
注意:
1.那个最大的连接数是指每一个线程可以处理的连接数,当你有多个线程时,连接数是可以无限增长的,不过此时的效率就比较低。
2.关于发送操作writefds的问题,当套接字成功连接或者一个套接字刚刚成功接收信息时都会调用。
3.我们通常会创建一个套接字来进行监听,之后用accept返回的套接字进行通信。这里要注意一点,用于监听的套接字在没有新连接时也会进行writefds的操作。

#3


引用 2 楼 YLCN2010 的回复:
你的Select模型用法不对。具体参考
Select工作流程
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
#include <winsock2.h>   
#include <stdio.h>   

#define PORT  5150   

#define MSGSIZE  1024   

#pragma comment(lib, "ws2_32.lib")   

int g_iTotalConn1 = 0;  
int g_iTotalConn2 = 0;   

SOCKET g_CliSocketArr1[FD_SETSIZE];  
SOCKET g_CliSocketArr2[FD_SETSIZE];  

DWORD WINAPI WorkerThread1(LPVOID lpParam);   
int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);

DWORD WINAPI WorkerThread2(LPVOID lpParam);   
int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);


int main(int argc, char* argv[])   
{   
 WSADATA wsaData;   
 SOCKET sListen, sClient;   
 SOCKADDR_IN local, client;   
 int iAddrSize = sizeof(SOCKADDR_IN);   
 DWORD dwThreadId;   
 // Initialize windows socket library   
 WSAStartup(0x0202, &wsaData);   
 // Create listening socket   
 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   
 // Bind   

 local.sin_family = AF_INET;   
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
 local.sin_port = htons(PORT);   
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));   

 // Listen   

 listen(sListen, 3);   

 // Create worker thread   

 CreateThread(NULL, 0, WorkerThread1, NULL, 0, &dwThreadId);  
// CreateThread(NULL, 0, WorkerThread2, NULL, 0, &dwThreadId);  

 while (TRUE)    
 {     
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc1, 0);
  printf("1:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn1);   
  g_CliSocketArr1[g_iTotalConn1++] = sClient;  
/*         
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc2, 0);
  printf("2:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn2);   
  g_CliSocketArr2[g_iTotalConn2++] = sClient;   */

 }   
 return 0;   
}   

DWORD WINAPI WorkerThread1(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   FD_SET(g_CliSocketArr1[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr1[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr1[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
    // Client socket closed   
     printf("1:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr1[i]);   
     if (i < g_iTotalConn1-1)    
     {   
      g_CliSocketArr1[i--] = g_CliSocketArr1[--g_iTotalConn1];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr1[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn1 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

DWORD WINAPI WorkerThread2(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   FD_SET(g_CliSocketArr2[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr2[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr2[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
     // Client socket closed   
     printf("2:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr2[i]);   
     if (i < g_iTotalConn2-1)    
     {   
      g_CliSocketArr2[i--] = g_CliSocketArr2[--g_iTotalConn2];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr2[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn2 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

服务器的几个主要动作如下:
1.创建监听套接字,绑定,监听;
2.创建工作者线程;
3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
4.接受客户端的连接。
 
这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。 
 
工作者线程里面是一个死循环,一次循环完成的动作是:
1.将当前所有的客户端套接字加入到读集fdread中;
2.调用select函数;
3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)
 
除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
 
关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
 
对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
 
注意:
1.那个最大的连接数是指每一个线程可以处理的连接数,当你有多个线程时,连接数是可以无限增长的,不过此时的效率就比较低。
2.关于发送操作writefds的问题,当套接字成功连接或者一个套接字刚刚成功接收信息时都会调用。
3.我们通常会创建一个套接字来进行监听,之后用accept返回的套接字进行通信。这里要注意一点,用于监听的套接字在没有新连接时也会进行writefds的操作。


我select用法是正确的啊, 我这边是客户端,所以只用检查一个套接字,现在遇到的问题是,经过一次或多次通讯以后,尽管服务器没有发送数据,select返回仍然是1.按理应该是返回0才对

#4


引用 3 楼 K_Lord 的回复:
Quote: 引用 2 楼 YLCN2010 的回复:

你的Select模型用法不对。具体参考
Select工作流程
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
#include <winsock2.h>   
#include <stdio.h>   

#define PORT  5150   

#define MSGSIZE  1024   

#pragma comment(lib, "ws2_32.lib")   

int g_iTotalConn1 = 0;  
int g_iTotalConn2 = 0;   

SOCKET g_CliSocketArr1[FD_SETSIZE];  
SOCKET g_CliSocketArr2[FD_SETSIZE];  

DWORD WINAPI WorkerThread1(LPVOID lpParam);   
int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);

DWORD WINAPI WorkerThread2(LPVOID lpParam);   
int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);


int main(int argc, char* argv[])   
{   
 WSADATA wsaData;   
 SOCKET sListen, sClient;   
 SOCKADDR_IN local, client;   
 int iAddrSize = sizeof(SOCKADDR_IN);   
 DWORD dwThreadId;   
 // Initialize windows socket library   
 WSAStartup(0x0202, &wsaData);   
 // Create listening socket   
 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   
 // Bind   

 local.sin_family = AF_INET;   
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
 local.sin_port = htons(PORT);   
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));   

 // Listen   

 listen(sListen, 3);   

 // Create worker thread   

 CreateThread(NULL, 0, WorkerThread1, NULL, 0, &dwThreadId);  
// CreateThread(NULL, 0, WorkerThread2, NULL, 0, &dwThreadId);  

 while (TRUE)    
 {     
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc1, 0);
  printf("1:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn1);   
  g_CliSocketArr1[g_iTotalConn1++] = sClient;  
/*         
  sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc2, 0);
  printf("2:Accepted client:%s:%d:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn2);   
  g_CliSocketArr2[g_iTotalConn2++] = sClient;   */

 }   
 return 0;   
}   

DWORD WINAPI WorkerThread1(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   FD_SET(g_CliSocketArr1[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn1; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr1[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr1[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
    // Client socket closed   
     printf("1:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr1[i]);   
     if (i < g_iTotalConn1-1)    
     {   
      g_CliSocketArr1[i--] = g_CliSocketArr1[--g_iTotalConn1];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr1[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn1 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

DWORD WINAPI WorkerThread2(LPVOID lpParam)   
{   
 int i;   
 fd_set fdread;   
 int ret;   
 struct timeval tv = {1, 0};   
 char szMessage[MSGSIZE];   
 while (TRUE)    
 {   
  FD_ZERO(&fdread);   //1清空队列
  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   FD_SET(g_CliSocketArr2[i], &fdread);   //2将要检查的套接口加入队列
  }   

  // We only care read event   
  ret = select(0, &fdread, NULL, NULL, &tv);   //3查询满足要求的套接字,不满足要求,出队
  if (ret == 0)    
  {   
   // Time expired   
   continue;   
  }   

  for (i = 0; i < g_iTotalConn2; i++)    
  {   
   if (FD_ISSET(g_CliSocketArr2[i], &fdread))    //4.是否依然在队列
   {   
    // A read event happened on g_CliSocketArr   

    ret = recv(g_CliSocketArr2[i], szMessage, MSGSIZE, 0);   
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
    {   
     // Client socket closed   
     printf("2:Client socket %d closed.\n", g_CliSocketArr1[i]);   
     closesocket(g_CliSocketArr2[i]);   
     if (i < g_iTotalConn2-1)    
     {   
      g_CliSocketArr2[i--] = g_CliSocketArr2[--g_iTotalConn2];   
     }   
    }    
    else    
    {   
     // We reveived a message from client   
     szMessage[ret] = '\0';   
     send(g_CliSocketArr2[i], szMessage, strlen(szMessage), 0);   
    }   
   }   
  }  
 }   


int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (g_iTotalConn2 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

服务器的几个主要动作如下:
1.创建监听套接字,绑定,监听;
2.创建工作者线程;
3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
4.接受客户端的连接。
 
这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。 
 
工作者线程里面是一个死循环,一次循环完成的动作是:
1.将当前所有的客户端套接字加入到读集fdread中;
2.调用select函数;
3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)
 
除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
 
关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
 
对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
 
注意:
1.那个最大的连接数是指每一个线程可以处理的连接数,当你有多个线程时,连接数是可以无限增长的,不过此时的效率就比较低。
2.关于发送操作writefds的问题,当套接字成功连接或者一个套接字刚刚成功接收信息时都会调用。
3.我们通常会创建一个套接字来进行监听,之后用accept返回的套接字进行通信。这里要注意一点,用于监听的套接字在没有新连接时也会进行writefds的操作。


我select用法是正确的啊, 我这边是客户端,所以只用检查一个套接字,现在遇到的问题是,经过一次或多次通讯以后,尽管服务器没有发送数据,select返回仍然是1.按理应该是返回0才对


返回1不代表该套接字已发送了数据,而是代表你可以在这个套接字上面发送数据。