共有6种类型套接字I/O模型。blocking(阻塞),select(选择),WSAAsyncSelect(异步选择),WSAEventSelect(事件选择),overlapped(重叠),completionport(完成端口)。 1.select 之所以称select模型,是因为工作原理是利用select函数实现对I/O的管理。 select可用于判断套接字上是否存在数据,或者能否向一个套接字写入数据,之所以要设计这个函数,其目的是防止应用程序在套接字处于阻塞模式时,在I/O绑定调用(如send或recv)过程中进入阻塞状态;同时也放在套接字处于非阻塞模式中时,产生WSAEWOULDBLOCK错误,除非满足事先用参数规定的条件,否则select函数在进行I/O操作时会阻塞。 int select( int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR* exceptfds, const struct timeval FAR* timeout ); 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调用会无限期处于阻塞状态,直到至少有一个描述符与指定条件相符才结束。 struct timeval { long tv_sec;//秒为单位指定等待时间 long tv_usec;//以毫秒为单位指定等待时间 }; //若将时间设置为{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操作的套接字句柄。 示例代码 SOCKET s; fd_set fdread; int ret; //创建套接字,接受连接 //在套接字上管理I/O while(true) { //在调用select前,清楚读出集 FD_ZERO(&fdread); //将s添加到读出集 FD_SET(s, &fdread); if((ret=select(0, &fdread, NULL, NULL, NULL))==SOCKET_ERROR) { //条件出错 } if(ret>0) { // // if(FD_ISSET(s, &fdread)) { //套接字s上已发生了一个事件 } } } 使用select的优势是能够从当个线程的多个套接字上进行多重连接及I/O。 ======================================================================= 客户端代码: 这是一个最简单的客户端代码,负责发送数据,然后接受返回。 #include<stdio.h> #include<winsock2.h> #pragma comment(lib, "ws2_32.lib") #define SERVER_IP "192.168.1.222" #define PORT 5150 #define MSGSIZE 1024 int main() { WSADATA wsaData; SOCKET sClient; SOCKADDR_IN server; char szMessage[MSGSIZE]; int ret; WSAStartup(MAKEWORD(2,2), &wsaData); sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = inet_addr(SERVER_IP); connect(sClient, (sockaddr*)&server, sizeof(SOCKADDR_IN)); while(TRUE) { printf("Send:"); gets(szMessage); send(sClient, szMessage, strlen(szMessage), 0); ret = recv(sClient, szMessage, MSGSIZE, 0); szMessage[ret] = '