服务器IO模型之Select
来源:互联网 发布:js new image 编辑:程序博客网 时间:2024/06/06 19:51
阻塞与非阻塞:
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个套接字,所以开销较大。
- 服务器IO模型之Select
- IO多路复用模型之Select
- Winsock IO模型之select模型
- Winsock IO模型之select模型
- Winsock IO模型之select模型
- C/S通信---服务器IO多路复用模型之select的使用
- Linux IO复用模型之select
- linux网络编程异步IO模型之—select模型 .
- 网络编程五种IO模型之select模型
- io/select模型
- select ---IO 模型
- select ---IO 模型
- IO多路复用 Select模型
- IO复用select模型
- 阻塞IO服务器模型之单线程服务器模型
- winsock IO模型 select模型
- winsock IO模型 select模型
- Unix IO 复用模型之 select & poll & epoll
- 类对象的引用转换
- VC++事件与消息
- Cocos2dx 3.0 过渡篇(一) 初体验
- 最长有效括号的长度
- MFC中SendMessage的实例测试
- 服务器IO模型之Select
- discuz 二次开发后台调用编辑器的方法!
- 【译】愿逝者安息,UEFI先驱——BIOS
- stream.js
- PGA_AGGREGATE_TARGET参数的理解
- split.density=false
- magento PHP Extensions “0” must be loaded?
- jconsole当地连接失败
- Single Number II