服务器IO模型之Select
2014-01-09 23:04
363 查看
阻塞与非阻塞:
widows下创建套接字默认都是阻塞型的,阻塞型的好处是处理简单,理解容易,但是处理多个套接字时,就必须创建多个线程,即一个连接socket使用一个线程。而非阻塞模式比如在处理发送和接收数据时,会立即返回,不管是否有有效的数据,这就需要不断测试返回代码,来确定套接字在什么时候可读/可写,也就是确定网络事件何时发生,比如中断默认就是一种事件触发型,比如菜单按钮也是事件触发性,但是比如快递邮寄包裹,他其实使用的是一种任务制(提前规定好的)。windows也提供了众多的非阻塞I/O模型,如select、WSAAsyncSelect、WSAEventSelect、overlapped、completion
port,比如select就可以设置时间,按规定时间去查询事件是否被触发,像WSAAsyncSelect就是事件驱动型的,等,这里主要用在socket端开发服务器程序。
select模型目的:主要是避免在套接字调用上阻塞的应用程序有能力管理多个套接字,即是单一线程模式下只能处理一个套接字的问题,这样可以避免线程膨胀。
select模型函数:
参数说明:
nfds [in]:忽略,仅是为了兼容Berkeley套接字
readfds [in, out]:用来检查可读的套接字组合
writefds [in, out]:用来检查可写的套接字组合
exceptfds [in, out]:用来检查异常的套接字组合
timeout [in]:等待的时间, 如果为NULL,等待的时间为无穷大
返回值:select返回那些即将要被处理的socket总和,假如时间超时,将会返回SOCKET_ERROR,可以使用WSAGetLastError获得出错的原因
Select处理过程:假设以read为例,在这里windows主要是先将套接字s添加到readfds集合中,然后等待select函数返回,在select函数里面会移除没有未决的I/O操作的套接字句柄,即移除未响应的IO套接字句柄,然后看s是否认仍然还是readfs集合中,在就说明s可读了
应用程序:
注意:可读操作包括有未处理的连接请求,数据可读,连接关闭/重启/中断
首先第一个判断的就是未处理的连接请求,如果有就建立新的连接通道,加入fdSocket;如果是数据可读,就读取数据;连接关闭会在下面进行测试
客户端程序:使用的是以前的一个简易客户端程序,如下
这里需要先运行服务器端,然后再开启客户端程序,服务器端会建立新的连接,并读取客户端发过来的数据然后显示出来,客户端是没有数据的,因为这里服务器端并没有发送数据,如下服务器数据:
再次开启一个客户端的效果如下:
这里并没有进行换行,两行数据在一起了,并不影响测试结果,这里还可以再强制关闭客户端后的结果,如下
这里看到error的代码为10054,我们在winerror.h里面找到如下定义:
Select不足:其实添加到fd_set套接字数量是有限制的,winsock2.h定义的64,自定义也不超过1024,因为值太大,会对服务器的性能有影响,假设有1000个的话,在调用select之前就必须设置这1000个套接字,select返回之后,还必须检查这1000个套接字,所以开销较大。
widows下创建套接字默认都是阻塞型的,阻塞型的好处是处理简单,理解容易,但是处理多个套接字时,就必须创建多个线程,即一个连接socket使用一个线程。而非阻塞模式比如在处理发送和接收数据时,会立即返回,不管是否有有效的数据,这就需要不断测试返回代码,来确定套接字在什么时候可读/可写,也就是确定网络事件何时发生,比如中断默认就是一种事件触发型,比如菜单按钮也是事件触发性,但是比如快递邮寄包裹,他其实使用的是一种任务制(提前规定好的)。windows也提供了众多的非阻塞I/O模型,如select、WSAAsyncSelect、WSAEventSelect、overlapped、completion
port,比如select就可以设置时间,按规定时间去查询事件是否被触发,像WSAAsyncSelect就是事件驱动型的,等,这里主要用在socket端开发服务器程序。
select模型目的:主要是避免在套接字调用上阻塞的应用程序有能力管理多个套接字,即是单一线程模式下只能处理一个套接字的问题,这样可以避免线程膨胀。
select模型函数:
int select( _In_ int nfds, _Inout_ fd_set *readfds, _Inout_ fd_set *writefds, _Inout_ fd_set *exceptfds, _In_ const struct timeval *timeout );
参数说明:
nfds [in]:忽略,仅是为了兼容Berkeley套接字
readfds [in, out]:用来检查可读的套接字组合
writefds [in, out]:用来检查可写的套接字组合
exceptfds [in, out]:用来检查异常的套接字组合
timeout [in]:等待的时间, 如果为NULL,等待的时间为无穷大
返回值:select返回那些即将要被处理的socket总和,假如时间超时,将会返回SOCKET_ERROR,可以使用WSAGetLastError获得出错的原因
Select处理过程:假设以read为例,在这里windows主要是先将套接字s添加到readfds集合中,然后等待select函数返回,在select函数里面会移除没有未决的I/O操作的套接字句柄,即移除未响应的IO套接字句柄,然后看s是否认仍然还是readfs集合中,在就说明s可读了
应用程序:
CInitSock initsock; sockaddr_in addr; USHORT usPort = 6000; SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP ); if (sListen == INVALID_SOCKET ) { TRACE("create socket error:%d\n", WSAGetLastError()); return -1; } addr.sin_family = AF_INET; addr.sin_port = htons(usPort); addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); if (::bind(sListen, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { /*WSAEFAULT 10014:The system detected an invalid pointer address in attempting to use a pointer argument in a call.*/ TRACE("bind socket error: %d\n", WSAGetLastError()); return -1; } ::listen(sListen, 5); fd_set fdSocket; FD_ZERO(&fdSocket); FD_SET(sListen, &fdSocket); while (true) { fd_set fdRead = fdSocket; int nRet = select(0, &fdRead, NULL, NULL, NULL); if (!nRet || nRet == SOCKET_ERROR ) { TRACE("select error: %d\n", WSAGetLastError()); return -1; } for (unsigned int i = 0; i < fdSocket.fd_count; i++) { if (FD_ISSET(fdSocket.fd_array[i], &fdRead)) //这里选择了fdSocket,是因为下一次循环还要使用fdSocket { if (fdSocket.fd_array[i] == sListen) { if (fdSocket.fd_count < FD_SETSIZE) { sockaddr_in addrRemote; int nAddrLen = sizeof(addrRemote); SOCKET sNewClient = ::accept(sListen, (sockaddr*)&addrRemote, &nAddrLen); if (sNewClient == INVALID_SOCKET ) { TRACE("accept new client socket error: %d\n", WSAGetLastError()); break; } FD_SET(sNewClient, &fdSocket); TRACE("new client: %s\n", inet_ntoa(addrRemote.sin_addr)); } } else { char szText[256]; int nRecv = ::recv(fdSocket.fd_array[i], szText, sizeof(szText), 0); if (!nRecv || nRecv == SOCKET_ERROR ) { TRACE("recv data error: %d\n", WSAGetLastError()); closesocket(fdSocket.fd_array[i]); FD_CLR(fdSocket.fd_array[i], &fdSocket); break; } else { szText[nRecv] = '\0'; TRACE("recv data: %s", szText); } } }//判断fdsocket里面的socket是否得到处理 } } closesocket(sListen); sListen = INVALID_SOCKET; return 0;这里我使用了CInitSock, 因为在使用socket之前要加载Ws2_32.lib,这里我定义了一个类如下:
#pragma once #include<winsock2.h> #include <ws2tcpip.h> #pragma comment(lib,"Ws2_32.lib") class CInitSock { public: CInitSock(BYTE minVer = 2, BYTE majVer = 2); ~CInitSock(void); };
CInitSock::CInitSock(BYTE minVer, BYTE majVer) { int nResult; WSADATA wsadata; WORD wVerReq = MAKEWORD(minVer, majVer); if (nResult = ::WSAStartup(wVerReq, &wsadata)) { TRACE("WSAStartup Load DLL Failed: %d!\n", nResult); } } CInitSock::~CInitSock(void) { /*In a multithreaded environment, WSACleanup terminates *Windows Sockets operations for all threads. */ ::WSACleanup(); }默认构造函数里面有一个默认加载的版本,这里在析构函数里面将之前加载的dll资源进行释放,基础的socket服务器模型通常要进行socket创建,绑定到本地地址和端口,监听客户端的连接,一旦有客户端连接,默认会放入fdSocket中,然后将此函数加入fd_set可读的套接字集合中,select返回后,未响应的socket会被移除,即将要被处理的socket会保留下来,然后从fdSocket判断,到底是哪些socket发生了可读操作:
注意:可读操作包括有未处理的连接请求,数据可读,连接关闭/重启/中断
首先第一个判断的就是未处理的连接请求,如果有就建立新的连接通道,加入fdSocket;如果是数据可读,就读取数据;连接关闭会在下面进行测试
客户端程序:使用的是以前的一个简易客户端程序,如下
WORD wVersionRequested; //请求的版本 WSADATA wsaData; int nErr; //协商版本号 wVersionRequested = MAKEWORD(1,1); nErr = WSAStartup(wVersionRequested, &wsaData); if(nErr != 0) { return; } if( LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1 ) { WSACleanup(); return; } //创建socket端口 SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //绑定端口号 connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //收发数据 char recvBuf[100], sendBuf[100]; memset(recvBuf, 0, 100); memset(sendBuf, 0, 100); sprintf_s(sendBuf,"hello world"); send(sockClient, sendBuf, strlen(sendBuf)+1, 0); recv(sockClient, recvBuf, 100, 0); printf("%s\n", recvBuf); //关闭socket通信 closesocket(sockClient); WSACleanup(); Sleep(1000);测试结果:
这里需要先运行服务器端,然后再开启客户端程序,服务器端会建立新的连接,并读取客户端发过来的数据然后显示出来,客户端是没有数据的,因为这里服务器端并没有发送数据,如下服务器数据:
再次开启一个客户端的效果如下:
这里并没有进行换行,两行数据在一起了,并不影响测试结果,这里还可以再强制关闭客户端后的结果,如下
这里看到error的代码为10054,我们在winerror.h里面找到如下定义:
// // MessageId: WSAECONNRESET // // MessageText: // // An existing connection was forcibly closed by the remote host. // #define WSAECONNRESET 10054L从这里也能看出的确是强制关闭,注意服务器端里面的TRACE要给我printf才可以,TRACE默认是在调试下使用的输出语句
Select不足:其实添加到fd_set套接字数量是有限制的,winsock2.h定义的64,自定义也不超过1024,因为值太大,会对服务器的性能有影响,假设有1000个的话,在调用select之前就必须设置这1000个套接字,select返回之后,还必须检查这1000个套接字,所以开销较大。
相关文章推荐
- C/S通信---服务器IO多路复用模型之select的使用
- (五十三)高并发服务器——多路IO转接机制Select模型
- 很幽默的讲解六种Socket IO模型 Delphi版本(自己Select查看,WM_SOCKET消息通知,WSAEventSelect自动收取,Overlapped I/O 事件通知模型,Overlapped I/O 完成例程模型,IOCP模型机器人)
- Linux下select, poll和epoll IO模型的详解
- (转)套接字IO模型(一) Select模型
- select ---IO 模型
- 几种并发服务器模型的实现:多线程,多进程,select,poll,epoll - rail
- Winsock IO模型之select模型
- IO模型及select、poll、epoll和kqueue的区别
- 利用 select 模型,实现一个 I/O 复用模式的服务器
- 12 IO复用服务器模型
- winsock IO模型 WSAEventSelect
- IO模型及select、poll、epoll和kqueue的区别
- Select I/O模型来实现一个并发处理多个客户端的TCP服务器
- 【Linux网络编程】基于TCP协议 I/O多路转接(select) 的高性能回显服务器客户端模型
- WinSock---WSAAsyncSelect IO模型和select模型的比较
- 可伸缩的IO完成端口服务器模型(IOCP)(中文版)
- WSAAsyncselect 模型中,服务器与客户端消息的收发
- 基于select模型tcp服务器的掉线处理
- 网络编程五种IO模型之select模型