c++ 通信演進level4 ----多線程異步非阻塞通信(AIO)

  今天,將最後一個流模型例子給記錄一下,代碼同樣來自於網上。由於一些原因,導致心情不是很好,還是按照既定計劃,將該demo的筆記記錄一下。源碼地址:地址

   它是基於  windows的iocp完成的,所以是異步非阻塞io。 最近看了很多的關於io說明的,各種帖子看的我頭大,始終還是沒徹底搞懂,估計是沒接觸過太多,境界還沒到。但是毫無疑問的是,windows的iocp是屬於異步非阻塞io的。 

  代碼結構:

 服務端流程:

#include "main.h"
int main(){
    CServerSocket* serverSocket = new CServerSocket();
    serverSocket->prepareEnvironment();
    delete serverSocket;
}

  之前是想將裏面的關鍵方法進行抽象來着,但是後面精力問題,就直接放到一個方法裏面的了。 

  服務端關鍵代碼:

......  
    //創建 IOCP的內核對象
    //參數說明:已經打開的文件句柄或者空句柄(一般是客戶端的句柄),已經存在的IOCP句柄,完成鍵,真正併發同時執行最大線程數
    HANDLE  completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
......
  //基於處理器的核心數量創建線程
    for(DWORD i=0;i<(mySysInfo.dwNumberOfProcessors*2);++i){
        //創建服務器工作線程,並將完成端口傳遞到該線程
        HANDLE threadHandle = CreateThread(NULL,0,ServerWorkThread,completionPort,0,NULL);
        
        CloseHandle(threadHandle);
    }
......
int bindResult = bind(srvSocket,(SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
......
 //將socket設置爲監聽模式
    int listenResult = listen(srvSocket,10);
......
//創建用於發送數據的線程
    HANDLE sendThread = CreateThread(NULL,0,ServerSendThread,0,0,NULL);
......

 主要作用是: 在程序啓動之初,根據cpu的數量,創建相應的信息接收線程,信息接收線程要與一個完成端口進行綁定。  另外,啓動一個信息發送線程,目前程序的實現是向所有連接的客戶端發送信息。 但是也很容易做到向特定的 套接字客戶端發送。 

  信息接收線程的核心代碼如下:

 while(true){
        bRet = GetQueuedCompletionStatus(completionPort, &bytesTransferred, (PULONG_PTR)&perHandleData, (LPOVERLAPPED*)&ipOverLapped, INFINITE);

        LpperIOData perIoData=NULL;
        perIoData = (LpperIOData)CONTAINING_RECORD(ipOverLapped,PerIOData,overlapped);
......

  第二行中,是阻塞等待通知的。  它的通知機制是: 通過io完成端口,利用overlapped的結構。 其中,getQueuedCompletionStatus中,第一個參數爲完成端口倒數第二個參數 爲完成端口攜帶消息的介質。倒數帶三個參數 相當於傳遞的消息類型,這裏爲包含socket的結構體。 

  此外要注意,重疊機制獲取完數據後,需要設置重疊狀態:

//爲下一個重疊調用 建立單 I/O 操作數據
        perIoData = (LpperIOData)GlobalAlloc(GPTR, sizeof(LpperIOData));
        ZeroMemory(&(perIoData->overlapped), sizeof(OVERLAPPED)); //清空內存
        perIoData->databuff.len= 1024;
        perIoData->databuff.buf=perIoData->buffer;
        perIoData->operationType=0;//read;
        WSARecv(perHandleData->socket, &(perIoData->databuff), 1, (&recvBytes), &flags, &(perIoData->overlapped), NULL);

  代碼中的順序是不能亂序的,在我第一次寫這個代碼的時候,當時對c++指針,內存分配啥的還不是很清楚,結果導致這個項目中原來的代碼運行不成功。  之後各種嘗試,最後試成功了。 同時,當時遇到一個奇怪的現象,那就是運行是正常的,但是調試偶爾會出錯。 現在想來,可能跟完成端口有關係。 

   我本地的cpu核心數是4核,所有在當前例子中,開了八個信息接收線程,這八個線程會同時阻塞在完成端口。根據我完成端口狀態,一次只能允許一個併發,所以當完成端口有信號時,只會觸發一個線程響應。 這個是可以控制的

   接着看看信息發送線程,比較簡單:

//發送信息的線程執行函數
DWORD WINAPI ServerSendThread(LPVOID ipParam){
    while (1){
        char talk[200];
        gets(talk);
        if (talk ==""){
            return 0;
        }

        WaitForSingleObject(hMutex,INFINITE);
        for(int i=0;i<clientGroup.size();++i){
            send(clientGroup[i]->socket,talk,200,0);
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

  以上的信息接收時的打印,和 信息輸入時的控制檯錄入,我最開始使用的時 std::cout,於std::cin的標準c++庫函數。 但是經測試,並不能正確獲取值。  當時網上查找說的是 ,它們不是線程安全的原因。  但是我現在想了下,可能恰恰因爲它們是線程安全的才導致這樣的問題。   控制檯對一個程序來說,資源是唯一的,由於該資源被其它資源佔用,所有導致當前使用線程不能正確的得到想要的結果。 

   客戶端的代碼也比較簡單: 

int main(){
    CClientSocket* clientSocket= new CClientSocket();
    clientSocket->prepareEnvironment();
    delete clientSocket;
}

   同樣的理由,並未對方法進行拆分。  

  HANDLE sendThread = CreateThread(NULL,0,SendMessageThread,NULL,0,NULL);
  HANDLE receiveThread = CreateThread(NULL,0,ReceiveMessageThread,NULL,0,NULL);

  此時客戶端採用了 讀寫均單獨開始一個線程,這樣的好處在於,可以拜託類似於 http應用層協議那種 一收一發的固定通信模式。 當前,它也可支持原來的通信模式,這主要看使用者意願。 

   發送線程代碼:

DWORD WINAPI SendMessageThread(LPVOID ipParam){
    while (1){
        string  talk;
        getline(cin,talk);
        WaitForSingleObject(bufferMutex,INFINITE);
        if("quit"==talk){
            talk.push_back('\0');
            send(clientSocket,talk.c_str(),200,0);
            break;
        }
        std::cout<<"\nI Say:(\"quit\" to exit):"<<talk<<"\n";
        send(clientSocket,talk.c_str(),200,0);
        ReleaseSemaphore(bufferMutex,1,NULL);
        
        Sleep(5000);
    }
    return 0;
}

  接收線程代碼:

DWORD WINAPI ReceiveMessageThread(LPVOID ipParam){
    while (1){
        char recvBuf[300];
        recv(clientSocket,recvBuf,200,0);
        WaitForSingleObject(bufferMutex,INFINITE);

        std::cout<<"Server Says:"<<recvBuf<<std::endl;

        ReleaseSemaphore(bufferMutex,1,NULL);
        if(recvBuf[0]=='\0'){
            cout<<"接收線程關閉!";
            break;
        }
    }
    return  0;
}

  注意到這兩個線程,實際上是對線程退出時機做了預設的。 這個層面來說,它實際上便相當於一個特殊意義上的應用層協議了

運行效果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章