C++服務器重疊I/O+事件通告模型

#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_PENDING
		int 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;
} 
相關文章
相關標籤/搜索