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
原创粉丝点击