网络编程(51)—— Windows下使用select进行IO复用

来源:互联网 发布:诺依曼枕头 知乎 编辑:程序博客网 时间:2024/06/06 09:59

        本文主要介绍在Windows系统下使用select搭建回声服务端的方法。在之前的《网络编程(16)—— IO复用技术之select》一文中我们介绍了在Linux使用Select进行IO复用的方法。本文对其原理不再详述,旨在通过对比使用加强对select的理解和应用。整个Windows版的select服务端的代码如下,稍后我们将对关键代码进行解释。

// SelectServ.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include "stdio.h"#include "winsock2.h"#pragma comment(lib,"ws2_32.lib")#define BUF_SIZE 30#define SOCK_SIZE 20void ErrorHandler(const char* message);int _tmain(int argc, _TCHAR* argv[]){SOCKET servSock,clntSock;SOCKADDR_IN servAddr,clntAddr;int clntAddrSz;int strLen;char buf[BUF_SIZE];fd_setfds,copyRead;SOCKET socks[SOCK_SIZE];TIMEVAL tm;int sockNum = 0;unsigned long ul=1; WSADATAwsaData;if(WSAStartup(MAKEWORD(2,2),&wsaData)==SOCKET_ERROR){ErrorHandler("WSAStartUp Error");}servSock=socket(AF_INET,SOCK_STREAM,0);if(servSock==INVALID_SOCKET)ErrorHandler("socket error");//将socket设置成非阻塞模式//ioctlsocket(servSock,FIONBIO,&ul);memset(&servAddr,0,sizeof(servAddr));servAddr.sin_family=AF_INET;servAddr.sin_addr.s_addr=htonl(INADDR_ANY);servAddr.sin_port=htons(atoi("8888"));if(bind(servSock,(const sockaddr*)&servAddr,sizeof(servAddr))==SOCKET_ERROR){ErrorHandler("bind error");}if(listen(servSock,SOCK_SIZE)==SOCKET_ERROR){ErrorHandler("listen error");}//遍历socks,将所有的元素置于无效的socketfor(int i=0;i<SOCK_SIZE;i++)socks[i] = INVALID_SOCKET;sockNum += 1;socks[0]=servSock;FD_ZERO(&fds);FD_SET(servSock,&fds);tm.tv_sec=100000;tm.tv_usec=0;while(1){copyRead=fds;int selResult = select(sockNum,&copyRead,0,0,&tm);printf("select return...\n");if(selResult==-1)puts("select error");else if(selResult==0)puts("timeout!");else{//先判断是否是有新的客户端连接if(FD_ISSET(socks[0],&copyRead)){clntAddrSz = sizeof(clntAddr);clntSock = accept(servSock,(SOCKADDR*)&clntAddr,&clntAddrSz);//将socket设置成非阻塞模式ioctlsocket(clntSock,FIONBIO,&ul);for(int i=0;i<SOCK_SIZE;i++){//遍历socks,在元素为无效的socket处插入客户端的socketif(socks[i] == INVALID_SOCKET){FD_SET(clntSock,&fds);socks[i]=clntSock;sockNum++;break;}}}//遍历所有的客户端socket,0的位置为服务端的socket,所以从1开始for (int i=1;i<SOCK_SIZE;i++){//如果是无效的socket 不必处理if(socks[i]==INVALID_SOCKET) continue;if(FD_ISSET(socks[i],&copyRead)){strLen=recv(socks[i],buf,BUF_SIZE,0);if(strLen <= 0)//客户端断开了连接{closesocket(socks[i]);//从fds删除客户端socketFD_CLR(socks[i],&fds);//将对应的位置再次置为无效socketsocks[i] == INVALID_SOCKET;sockNum--;}else if(strLen > 0){send(socks[i],buf,strLen,0);}}}}}closesocket(servSock);return 0;}void ErrorHandler(const char* message){fputs(message,stderr);fputc('\n',stderr);exit(1);}

第22~26行,声明使用select相关的变量。包括存放socket的集合fd_set和它的副本copyRead;声明超时tm,以及用来进行socket计数的sockNum;ul在后面设置非阻塞socket时将会用到。
第53~55行,遍历数组socks,将每个数组元素都置为无效的socket,为的是后面的循环判断哪个socket有需要接受的数据时,遇到无效的socket直接跳过,可参照第98行代码。
第56行,因为已经创建了一个服务端的socket,所以sockNum要加1。
第58~62行,将服务端的socket代码放到socks的第一个元素的位置,然后利用宏对fds进行清空,并将servSock注册到fds中,并设置超时时间。
第66行,复制fds至copyRead。每次select之后,select监视的socket集合都会发生状态的变化,且不能复原,因此都需要我们在select之前传递一个集合的副本,以防止传递集合时数据被破坏。
第67行,使用select返回接收到数据的socket
第76行,因为我们将servSock放到了socks中的第一个元素,所以先判断是不是socks[0]发生状态变化,也就是说先判断是不是有新的客户端连接。
第81行,将客户端的socket设置成非阻塞模式,这点很重要,否则在101行读取客户端的数据时就会被阻塞,无法同时和多个客户端进行通信。
第82~91行,遍历socks,只在包含无效socket的位置插入socket。同时,每接收到一个新的客户端的连接,sockNum都要加1.
第95~114行,遍历socks,找到接收到数据的客户端的socket,并进行相应的读写。因为socks[0]存放的是servSock,我们之前已经处理过,所以这里的下标i从1开始。



Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL51



0 0