共有6种类型套接字I/O模型。blocking(阻塞),select(选择),WSAAsyncSelect(异步选择),WSAEventSelect(事件选择),overlapped(重叠),completionport(完成端口)。

1.select

之所以称select模型,是因为工作原理是利用select函数实现对I/O的管理。

select可用于判断套接字上是否存在数据,或者能否向一个套接字写入数据,之所以要设计这个函数,其目的是防止应用程序在套接字处于阻塞模式时,在I/O绑定调用(如send或recv)过程中进入阻塞状态;同时也放在套接字处于非阻塞模式中时,产生WSAEWOULDBLOCK错误,除非满足事先用参数规定的条件,否则select函数在进行I/O操作时会阻塞。

 
  
  1. int select( 
  2.   int nfds, 
  3.   fd_set FAR* readfds, 
  4.   fd_set FAR* writefds, 
  5.   fd_set FAR* exceptfds, 
  6.   const struct timeval FAR* timeout 
  7. ); 

nfds会被忽略,之所以保留是为了与早期套接字兼容

3个fd_sed,readfds用于检测可读性,writefds用于检测可写性,exceptfds用于外地数据,从根本上讲,fd_set数据类型代表着一系列特定套接字结合。

readfds集合包括符合下述任何一个条件的套接字:

.有数据可以读入

.连接已经被关闭,终止或重启

.假如已调用了listen,而且有一个连接正处于搁置状态,那么accept调用会成功 

writefds集合包括符合下述任何一个条件的套接字:

.有数据可以发出

.如果正在对一个非阻塞连接调用进行处理,则连接就成功

exceptfds集合包括符合下述任何一个条件的套接字:

.如果正在对一个非阻塞连接调用进行处理,连接尝试将会失败

.有OOB(out-of-band,外地)数据可供读写

假设我们想测试一个套接字是否可读,必须将套接字添加到readfds集合中,在等待select函数完成。当select调用完成后,必须判断这个套接字是否仍为readfds中的一部分。若是,则表明该套接字可读,可立即进行读取数据。在3个参数中,任何两个都可以是NULL,但至少有一个不能为NULL,在任何不为空的集合中,必须包含至少一个套接字句柄,否则select没有任何东西可以等待。timeout对应的是一个指针,指向一个timeval结构,用于决定select等待I/O操作完成时,最多等待多长时间。如果timeout是一个空指针,那么select调用会无限期处于阻塞状态,直到至少有一个描述符与指定条件相符才结束。

 
  
  1. struct timeval 
  2.   long tv_sec;//秒为单位指定等待时间 
  3.   long tv_usec;//以毫秒为单位指定等待时间 
  4. }; 
  5. //若将时间设置为{0,0}表明select会立即返回。处于对性能考虑,应避免这么设置 

对fd_set集合进行处理与检查:

FD_ZERO(*set);//将set初始化为空集合,集合在使用前都要清空

FD_CLR(s, *set);//从set中删除套接字s

FD_ISSET(s, *set);//检测s是否是set中的一个成员,如果是,返回TRUE

FD_SET(s, *set);//将套接字s加入到set中

采用以下步骤便可完成select操作一个或多个套接字句柄的全过程:

1.使用FD_ZERO初始化自己感兴趣的每个fd_set

2.使用FD_SET将套接字句柄分配给自己感兴趣的fd_set

3.调用select函数,然后等待直到I/O活动在在指定的fd_set集合中设置好了一个或多个套接字句柄。select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新

4.根据select的返回值,应用程序便可判断哪些套接字存在着被搁置的I/O操作--具体的方法是使用FD_ISSET宏,对每个fd_set进行检查

5.知道了每个集合中被挂起的I/O操作之后,对I/O进行处理,然后返回步骤1,继续处理select

select返回后,它会修改每个fd_set结构。删除那些不存在被挂起的I/O操作的套接字句柄。

示例代码

 
  
  1. SOCKET s; 
  2. fd_set fdread; 
  3. int ret; 
  4.  
  5. //创建套接字,接受连接 
  6.  
  7. //在套接字上管理I/O 
  8. while(true
  9.   //在调用select前,清楚读出集 
  10.   FD_ZERO(&fdread); 
  11.   //将s添加到读出集 
  12.   FD_SET(s, &fdread); 
  13.   if((ret=select(0, &fdread, NULL, NULL, NULL))==SOCKET_ERROR) 
  14.   { 
  15.     //条件出错 
  16.   } 
  17.  
  18.   if(ret>0) 
  19.   { 
  20.     // 
  21.     // 
  22.     if(FD_ISSET(s, &fdread)) 
  23.     { 
  24.       //套接字s上已发生了一个事件 
  25.     } 
  26.   } 

使用select的优势是能够从当个线程的多个套接字上进行多重连接及I/O。

 

=======================================================================

客户端代码:

这是一个最简单的客户端代码,负责发送数据,然后接受返回。

 
  
  1. #include<stdio.h> 
  2. #include<winsock2.h> 
  3. #pragma comment(lib, "ws2_32.lib") 
  4.  
  5. #define SERVER_IP "192.168.1.222" 
  6. #define PORT 5150 
  7. #define MSGSIZE 1024 
  8.  
  9. int main() 
  10.   WSADATA wsaData; 
  11.   SOCKET sClient; 
  12.   SOCKADDR_IN server; 
  13.   char szMessage[MSGSIZE]; 
  14.   int ret; 
  15.    
  16.   WSAStartup(MAKEWORD(2,2), &wsaData); 
  17.   sClient = socket(AF_INET, SOCK_STREAM,  IPPROTO_TCP);     
  18.   memset(&server, 0, sizeof(server)); 
  19.   server.sin_family = AF_INET; 
  20.   server.sin_port = htons(PORT); 
  21.   server.sin_addr.s_addr = inet_addr(SERVER_IP); 
  22.    
  23.   connect(sClient, (sockaddr*)&server, sizeof(SOCKADDR_IN)); 
  24.   while(TRUE) 
  25.   { 
  26.     printf("Send:"); 
  27.     gets(szMessage); 
  28.     send(sClient, szMessage, strlen(szMessage), 0); 
  29.     ret = recv(sClient, szMessage, MSGSIZE, 0); 
  30.     szMessage[ret] = '\0'
  31.     printf("Received [%d bytes]: '%s'\n", ret, szMessage); 
  32.   } 
  33.   closesocket(sClient); 
  34.   WSACleanup(); 
  35.    
  36.   //system("pause"); 
  37.   return 0; 

 

服务端代码:

这是异步模型中最简单的一种,服务器端的几个主要流程如下: 

1.创建监听套接字,绑定,监听; 

2.创建工作者线程; 

3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组; 

4.接受客户端的连接。

 
  
  1. #include<stdio.h> 
  2. #include<winsock2.h> 
  3. #pragma comment(lib, "ws2_32.lib") 
  4.  
  5. #define PORT 5150 
  6. #define MSGSIZE 1024 
  7.  
  8. int g_iTotalConn = 0; 
  9. SOCKET g_CliSocketArr[FD_SETSIZE]; 
  10. DWORD WINAPI WorkerThread(LPVOID lpParam); 
  11.  
  12. int main() 
  13.     WSADATA wsaData; 
  14.     SOCKET sListen, sClient; 
  15.     SOCKADDR_IN local, client; 
  16.     int iAddrSize = sizeof(SOCKADDR_IN); 
  17.     DWORD dwThreadId; 
  18.      
  19.     WSAStartup(MAKEWORD(2,2), &wsaData); 
  20.     sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
  21.     memset(&local, 0, sizeof(SOCKADDR_IN)); 
  22.     local.sin_family = AF_INET; 
  23.     local.sin_port = htons(PORT); 
  24.     local.sin_addr.s_addr = htonl(INADDR_ANY); 
  25.      
  26.     bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN)); 
  27.     listen(sListen, 5); 
  28.      
  29.     CreateThread(NULL, 0 , WorkerThread, NULL, 0, &dwThreadId); 
  30.      
  31.     while(TRUE) 
  32.     { 
  33.       sClient = accept(sListen, (sockaddr*)&client, &iAddrSize); 
  34.       printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); 
  35.       g_CliSocketArr[g_iTotalConn++] = sClient; 
  36.     } 
  37.      
  38.     return 0; 
  39.  
  40. DWORD WINAPI WorkerThread(LPVOID lpParam) 
  41.   fd_set fdread; 
  42.   int ret; 
  43.   int i; 
  44.   struct timeval tv = {1,0}; 
  45.   char szMessage[MSGSIZE]; 
  46.   while(TRUE) 
  47.   { 
  48.     FD_ZERO(&fdread); 
  49.     for(i=0; i<g_iTotalConn; ++i) 
  50.     { 
  51.       FD_SET(g_CliSocketArr[i], &fdread); 
  52.     } 
  53.     ret = select(0, &fdread, NULL, NULL, NULL); 
  54.     if(0 == ret) 
  55.     { 
  56.       continue
  57.     } 
  58.     for(i=0; i<g_iTotalConn; ++i) 
  59.     { 
  60.       if(FD_ISSET(g_CliSocketArr[i], &fdread)) 
  61.       { 
  62.         ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0); 
  63.         if(0 == ret || (ret==SOCKET_ERROR && WSAGetLastError()==WSAECONNRESET)) 
  64.         { 
  65.           printf("Client socket %d closed.\n", g_CliSocketArr[i]); 
  66.           closesocket(g_CliSocketArr[i]); 
  67.           if(i<g_iTotalConn-1) 
  68.           { 
  69.             g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn]; 
  70.           } 
  71.         } 
  72.         else 
  73.         { 
  74.           szMessage[ret] = '\0'
  75.           send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0); 
  76.         } 
  77.       } 
  78.     } 
  79.   }