C++服务器重叠I/O+事件通告模型
来源:互联网 发布:疾病自测软件 编辑:程序博客网 时间:2024/05/01 02:34
#include<iostream>#include<WinSock2.h>#include <mswsock.h>#include<vector>using namespace std;#pragma comment(lib,"Ws2_32.lib")#pragma comment (lib, "mswsock.lib")const int nPort=10000;const int buf_len=1024;//对于同一个客户连接套接字,任意时刻只能有一个为完成的异步I/O操作,要么是//WSASend(IoWrite),要么是WSARecv(IoRead)typedef enum _IO_OPERATION{IoRead,IoWrite}IO_OPERATION;//每一个连接都有一个Connection对象Connection对象包含一个WSAOVERLAPPED结构//同时由于一个Connection只有一个WSAOVERLAPPED结构,并且由于一个I/O异步请求//必须有一个唯一的WSAOVERLAPPED结构,因此任意时刻对于一个连接只能有一个未完成的//异步I/O操作struct Connection{SOCKET hSocket;char Buffer[buf_len];int nBytes;//调用WSASend或者WSARecv是需要一个WSABUF结构的指针WSABUF wsaBuffer;WSAOVERLAPPED overlap;IO_OPERATION op;Connection(SOCKET socket):hSocket(socket),nBytes(0){wsaBuffer.buf=Buffer;wsaBuffer.len=buf_len;ZeroMemory(&overlap,sizeof(WSAOVERLAPPED));//由于程序使用事件完成通知,因此需要为WSAOVERLAPPED结构创建一个时间内核对象overlap.hEvent=WSACreateEvent();}};typedef vector<Connection*> ConnectionList;// 重置conns,把其中无效的套接字移除void ResetConns(ConnectionList& conns){ ConnectionList::iterator it = conns.begin(); while(it != conns.end()){ if((*it)->hSocket == INVALID_SOCKET){ delete (*it); it = conns.erase(it); } else ++it; }}// 为WSAWaitForMultipleEvents填充好需要等待的事件内核对象数组int FillEventArray(HANDLE hEvents[], HANDLE hListenEvent, ConnectionList& conns){ // 监听套接字的事件对象放在最前面,之后依次填入当前所有客户连接套接字// 的事件对象 hEvents[0] = hListenEvent; int nEvents = 1; ConnectionList::iterator it = conns.begin(); while(it != conns.end()){ // 使用WSAOVERLAPPED结构中的hEvent填充数组 hEvents[nEvents] = (*it)->overlap.hEvent; ++nEvents; ++it; } return (int)(conns.size() + 1);}// 异步AcceptEx请求已完成,获取结果bool HandleAccept(SOCKET hListenSocket, SOCKET hAcceptSocket, LPOVERLAPPED lpOverlapListen, ConnectionList& conns) { DWORD flags = 0; DWORD bytes = 0; // 获取异步I/O的结果 if(!WSAGetOverlappedResult(hListenSocket, lpOverlapListen, &bytes, FALSE, &flags)) { cout<<"WSAGetOverlappedResult error "<< WSAGetLastError() << endl; return false; }// 超出单线程所能处理的连接数 if(conns.size() + 1 >= WSA_MAXIMUM_WAIT_EVENTS){ cout << "exceed connection limit" << endl; // 关闭已接受的客户连接,即拒绝服务 closesocket(hAcceptSocket); return true; } // 为新接受的客户连接创建一个Connection对象 conns.push_back(new Connection(hAcceptSocket)); Connection* pConn = conns.back();// 第一次的异步I/O请求是IoRead,因为对于回显服务器来说,必须先接收到数据后 // 才能回显数据 pConn->op = IoRead; flags = 0; // 对这个新的客户连接发出第一个异步I/O请求 int nRet = WSARecv(pConn->hSocket, &(pConn->wsaBuffer), 1, NULL, &flags, &pConn->overlap, NULL); int lastErr = WSAGetLastError(); // 如果WSARecv失败并且错误代码不是ERROR_IO_PENDING if(nRet == SOCKET_ERROR && WSA_IO_PENDING != lastErr){ cout<<"WSARecv error "<< lastErr << endl; return false; } return true;}// 异步的WSASend或者WSARecv已完成,获取结果bool HandleSendRecv(Connection* pConn){ DWORD flags = 0; DWORD bytes = 0; // 获取异步I/O的结果 if(!WSAGetOverlappedResult(pConn->hSocket, &pConn->overlap, &bytes, FALSE, &flags)) { int lastErr = WSAGetLastError(); cout<<"WSAGetOverlappedResult error "<< lastErr << endl; // 连接被对方意外关闭 if(lastErr == WSAECONNRESET) cout<<"Connection was reset."<<endl; return false; } if(bytes == 0){// 对方正常关闭了连接 cout << "Connection closed by peer." << endl; return false; } // 如果当前已完成的异步I/O是WSARecv if(pConn->op == IoRead){ // 更新可用数据的大小 pConn->nBytes += bytes; // 为即将调用的WSASend准备好缓冲区参数 pConn->wsaBuffer.len = pConn->nBytes; pConn->wsaBuffer.buf = pConn->Buffer; flags = 0; // 由于WSARecv已成功接收了数据,现在可以发出异步WSASend请求来回显数据 pConn->op = IoWrite; int nRet = WSASend(pConn->hSocket, &(pConn->wsaBuffer), 1, NULL, flags, &pConn->overlap, NULL); int lastErr = WSAGetLastError(); if(nRet == SOCKET_ERROR && WSA_IO_PENDING != lastErr) { cout<<"WSASend error "<< lastErr << endl; return false; } } // 如果当前已完成的异步I/O是WSASend else if(pConn->op == IoWrite){ // 更新可用数据的大小 pConn->nBytes -= bytes; // 计算缓冲区空闲空间的大小 pConn->wsaBuffer.len = nBuffSize - pConn->nBytes; // 如果缓冲区还有剩余数据没有发送出去,则需要把它们移到缓冲区的头部 if(pConn->nBytes > 0) { memmove(pConn->Buffer, pConn->Buffer + bytes, pConn->nBytes); } // 计算缓冲区空闲空间的偏移 pConn->wsaBuffer.buf = pConn->Buffer + pConn->nBytes; flags = 0; pConn->op = IoRead; // 发出异步WSARecv请求 int nRet = WSARecv(pConn->hSocket, &(pConn->wsaBuffer), 1, NULL, &flags, &pConn->overlap, NULL); int lastErr = WSAGetLastError(); if(nRet == SOCKET_ERROR && WSA_IO_PENDING != lastErr) { cout<<"WSARecv error "<< lastErr << endl; return false; } } return true;}//创建一个WSA_FLAG_OVERLAPPED套接字SOCKET CreateOverlappedSocket(){SOCKET hSocket=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);if(hSocket==INVALID_SOCKET){cout<<"WSASocket 错误"<<WSAGetLastError()<<endl;}return hSocket;}//返回一个用于异步I/O的监听套接字进入监听状态SOCKET BindListenOverlapped(){//创建一个用于异步I/O的监听套接字SOCKET sd=CreateOverlappedSocket();if(sd==INVALID_SOCKET){return INVALID_SOCKET;}//填充本地套接字地址sockaddr_in saListen;saListen.sin_family=AF_INET;saListen.sin_addr.s_addr=htonl(INADDR_ANY);saListen.sin_port=htons(nPort);//调用bind把本地套接字地址绑定到监听套接字if(bind(sd,(sockaddr*)&saListen,sizeof(sockaddr_in))==SOCKET_ERROR){cout<<"绑定失败"<<WSAGetLastError()<<endl;closesocket(sd);return INVALID_SOCKET;}//开始监听if(listen(sd,5)==SOCKET_ERROR){cout<<"监听失败"<<WSAGetLastError()<<endl;closesocket(sd);return INVALID_SOCKET;}return sd;}//调用AcceptEx时需要用到的缓冲区,这个缓冲区用来保存本地和远程地址char bAcceptBuffer[2*(sizeof(SOCKADDR_IN)+16)];DWORD dwAcceptBytes=0;//发出异步AcceptEx请求SOCKET StartAccept(SOCKET hListenSocket,HANDLE hListenEvent,LPOVERLAPPED lpOverlapListen){//创建一个异步套接字hAcceptSocket,并传给AcceptEx。当异步的AcceptEx完成时//即当WSAWaitForMultipleEvents成功返回其返回值表示出现信号的事件是//监听套接字的事件时,在此处创建的hAcceptSocket就代表成功接受的客户连接SOCKET hAcceptSocket=CreateOverlappedSocket();if(hAcceptSocket==INVALID_SOCKET){return INVALID_SOCKET;}//初始化监听套接字的WSAOVERLAPPED结构ZeroMemory(lpOverlapListen,sizeof(WSAOVERLAPPED));lpOverlapListen->hEvent=hListenEvent;//发出异步AcceptEx请求if(!AcceptEx(hListenSocket,hAcceptSocket,bAcceptBuffer,0,sizeof(SOCKADDR_IN)+16,sizeof(SOCKADDR_IN)+16,&dwAcceptBytes,lpOverlapListen)){//如果AcceptEx失败并且错误代码不是ERROR_IO_PENDINGint lastErr=WSAGetLastError();if(lastErr!=ERROR_IO_PENDING){cout<<"AcceptEx 错误"<<lastErr<<endl;closesocket(hAcceptSocket);return INVALID_SOCKET;}}return hAcceptSocket;}// OverlappedEventServer的主体函数void DoWork() { // 定义事件内核对象句柄数组 HANDLE hEvents[WSA_MAXIMUM_WAIT_EVENTS]; ConnectionList conns; // 获取一个用于异步I/O的监听套接字 SOCKET hListenSocket = BindListenOverlapped(); if(hListenSocket == INVALID_SOCKET) goto cleanup; // 为监听套接字创建一个事件内核对象 HANDLE hListenEvent = WSACreateEvent(); // 用于监听套接字的WSAOVERLAPPED结构 WSAOVERLAPPED overlapListen; // 开始监听套接字的异步AcceptEx请求 SOCKET hAcceptSocket = StartAccept(hListenSocket, hListenEvent, &overlapListen); if(hAcceptSocket == INVALID_SOCKET) goto cleanup; // 主循环 while(true){ // 从客户连接列表中去掉无效的连接,即那些已关闭或者发生了错误的连接 ResetConns(conns); // 用监听套接字的事件和所有有效客户连接的事件填充一个事件数组 int nEvents = FillEventArray(hEvents, hListenEvent, conns); // 等待任一(或一些)事件出现信号 int nRet = WSAWaitForMultipleEvents(nEvents, hEvents, FALSE, WSA_INFINITE, FALSE); if(nRet == WSA_WAIT_FAILED){ cout<<"WSAWaitForMultipleEvents "<< WSAGetLastError() << endl; goto cleanup; } // 获取所有出现信号的事件中最小的索引值 nRet = nRet - WSA_WAIT_EVENT_0; // 检查每一个可能的事件,看其有没有信号 for(int nIndex = nRet; nIndex < nEvents; ++nIndex) { // 测试索引值为nIndex的事件是否出现信号 nRet = WSAWaitForMultipleEvents(1, &hEvents[nIndex], true, 0, FALSE); // 没有信号则继续主循环 if(nRet == WSA_WAIT_FAILED || nRet == WSA_WAIT_TIMEOUT) continue; // 重置出现信号的事件,以便下一次进入主循环等待时其状态为无信号 WSAResetEvent(hEvents[nIndex]); // nIndex为0代表监听套接字的事件出现信号 if(nIndex == 0){ // 监听套接字的异步AcceptEx已经完成,新的客户连接套接字是 // hAcceptSocket。调用HandleAccept来执行异步I/O完成后的工作 if(!HandleAccept(hListenSocket, hAcceptSocket, &overlapListen, conns)) goto cleanup; // 开始监听套接字的下一个异步AcceptEx请求 hAcceptSocket = StartAccept(hListenSocket, hListenEvent, &overlapListen); if(hAcceptSocket == INVALID_SOCKET) goto cleanup; } // nIndex大于0代表客户连接的套接字事件出现信号 else{ // 找到客户连接的Connection对象 Connection* pConn = conns[nIndex-1]; // 调用HandleSendRecv来执行异步I/O完成后的工作 if(!HandleSendRecv(pConn)){ closesocket(pConn->hSocket); pConn->hSocket = INVALID_SOCKET; WSACloseEvent(pConn->overlap.hEvent); } } } } // 释放资源cleanup: ConnectionList::iterator it = conns.begin(); for(;it != conns.end();++it){ closesocket((*it)->hSocket); WSACloseEvent((*it)->overlap.hEvent); delete (*it); } if(hListenSocket != INVALID_SOCKET) closesocket(hListenSocket); WSACloseEvent(hListenEvent);}int main(int argc, char* argv[]){ WSAData wsaData;int nCode; if ((nCode = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) {cout << "WSAStartup error " << nCode << endl; return -1; } DoWork(); WSACleanup(); return 0;}
0 0
- C++服务器重叠I/O+事件通告模型
- 重叠I/O模型
- 重叠I/O模型
- 重叠I/O模型
- 重叠I/O模型
- 重叠I/O模型
- 重叠I/O模型
- Socket I/O模型之重叠I/O(overlapped I/O)--事件通知
- WinSock IO模型四: 重叠I/O (事件通知)
- 事件通知方式实现的重叠I/O模型
- 基于事件通知的重叠I/O网络模型
- 转载(重叠I/O模型)
- 重叠(Overlapped)I/O模型
- 重叠I/O模型(概念)
- 重叠I/O模型实例
- WinSock重叠I/O模型
- 重叠I/O模型(1)
- 重叠I/O模型(1)
- php创建数据URL
- 耐心
- [51]跑马灯
- 在R中使用XGBoost算法
- 关于ADS8361量程
- C++服务器重叠I/O+事件通告模型
- Wiggle Sort II
- Android ViewGroup/View 事件分发机制详解
- 如何走出囚徒困境?
- 操作手册(GB8567——88)
- House Robber II
- arcgis api for js共享干货系列之一自写算法实现地图量算工具
- Visual EmbedLinux Tools 0.2.7
- mysql 入门教程