實現UDP IOCP心得-zt

http://h-lm.spaces.live.com/blog/cns!C523F565A10E3B66!824.entry

2008/11/11

實現UDP IOCP心得

當前許多資料都是介紹TCP的IOCP的實現,UDP的較少。
 
1.很多人在討論UDP是否需要IOCP。

通宵奮戰,搞定UDP+IOCP

     總算是沒有白費時間,搞定了UDP+IOCP。原來的程序採用的是多線程+阻塞的變種,效率一直不如意。本來想一開始就用IOCP的,但是在網上查閱資料的時候看到討論說UDP不需要用IOCP的,自己想想也覺得有點道理。於是就用最簡單的多線程+阻塞來實現了數據採集。後來測試的時候效率一直不如意,更改爲IOCP模型時思路錯了,一直更改不成功。昨晚突然想到了問題所在,一下就把UDP+IOCP實現了。呵呵。
      總算是小有收穫,工作線程更改用戶界面的問題也解決了,數據採集效率也還過得去了,相關測試程序都可以勉強運轉了,又恢復了些許信心了。感覺是度過了一個黎明前的黑夜了,只是不知道還有多少個更黑的夜晚需要經歷。
 
我的實現還沒有進行測試,之後再附上。
 
2.TCP的IOCP是在Accept之後,將Accept創建的套接字與完成端口綁定,而在UDP中,則是把WSASocket或Socket創建的套接字與完成端口綁定。
在實現UDP IOCP時,可以參考已有的TCP IOCP代碼,例如http://www.cppblog.com/niewenlong/archive/2007/08/17/30224.html
另外http://www.codeproject.com/KB/IP/iocp-multicast-udp.aspx可供下的源碼中的客戶端代碼是UDP IOCP實現
 
3.以下數據結構非常重要。
typedef struct _PER_IO_OPERATION_DATA
{
    OVERLAPPED Overlapped;
    WSABUF DataBuff;
    char Buff[24];
    BOOL OperationType;
}PER_IO_OPERATION_DATA,* LPPER_IO_OPERATION_DATA;
因爲在UDP中每次RecvFrom獲WSARecvFrom會傳回UDP數據來源的IP,因此可以將以上數據結構修改成:
typedef struct _PER_IO_OPERATION_DATA
{
    OVERLAPPED Overlapped;
    WSABUF DataBuff;
    char Buff[24];
    unsigned long recvBytes;      //存儲接收到的字節數
    SOCKADDR_IN remoteAddr; //存儲數據來源IP地址
    int remoteAddrLen;              //存儲數據來源IP地址長度
}PER_IO_OPERATION_DATA,* LPPER_IO_OPERATION_DATA;
 
4.實現過程
創建LPPER_IO_OPERATION_DATA數據結構並進行初始化(初始化很重要)
    LPPER_IO_OPERATION_DATA ioperdata;
    ioperdata = (LPPER_IO_OPERATION_DATA)malloc(sizeof(PER_IO_OPERATION_DATA));
    memset(&(ioperdata->Overlapped), 0, sizeof(OVERLAPPED));
   
    (ioperdata->DataBuff).len = 24;
    (ioperdata->DataBuff).buf = ioperdata->Buff;
    ioperdata->recvBytes = 24;
    ioperdata->remoteAddrLen = sizeof(ioperdata->remoteAddr);
創建完成端口
hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 
創建UDP socket
udpSocket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);
綁定UDP Socket
bind(udpSocket, (SOCKADDR*) & addr, sizeof(SOCKADDR_IN));
將完成端口與UDP Socket綁定
    CreateIoCompletionPort(
        (HANDLE)udpSocket,
        hCompletionPort,
        (DWORD)udpSocket,
        5
    );
根據CPU數量*2+2的原則創建工作者線程------- ------- --------------一直循環
CreateWorkers(m_dwThreads)
接收數據                                                                          獲得當前完成狀態  GetQueuedCompletionStatus(
WSARecvFrom(                                                                                     ComPort,
                udpSocket,                                                                          &BytesTransferred,
                &(ioperdata->DataBuff),                                                         (LPDWORD) & nSocket,
                1,                                                                                       (LPOVERLAPPED *) & PerIoData,
                &(ioperdata->recvBytes),                                                        INFINITE   );                   
                &flags,                                                           
                (SOCKADDR*) & (ioperdata->remoteAddr),                                
                &(ioperdata->remoteAddrLen),                
                &(ioperdata->Overlapped),                                
                NULL);                                                            處理接收到的數據
                                                                                    繼續執行投遞操作(類同接收數據操作)
 
5.WSAGetLastError錯誤代碼
通過WSAGetLastError的信息來測試程序中出現的問題,常見的錯誤有10055、10014、6等,最主要的是變量的初始化。
 
1- 不要爲每個小數據包發送一個IOCP請求,這樣很容易耗盡IOCP的內部隊列.....從而產生10055錯誤.
2- 不要試圖在發送出IOCP請求之後,收到完成通知之前修改請求中使用的數據緩衝的內容,因爲在這段時間,系統可能會來讀取這些緩衝.
 3- 爲了避免內存拷貝,可以嘗試關閉SOCKET的發送和接收緩衝區,不過代價是,你需要更多的接收請求POST到一個數據流量比較大的SOCKET,從而保證系統一直可以找到BUFFER來收取到來的數據.
4- 在發出多個接收請求的時候,如果你的WORKTHREAD不止一個,一定要使用一些手段來保證接收完成的數據按照發送接收請求的順序處理,否則,你會遇到數據包用混亂的順序排列在你的處理隊列裏.....
5- 說起工作線程, 最好要根據MS的建議, 開 CPU個數*2+2 個, 如果你不瞭解IOCP的工作原理的話.
6- IOCP的工作線程是系統優化和調度的, 自己就不需要進行額外的工作了.如果您自信您的智慧和經驗超過MS的工程師, 那你還需要IOCP麼....
7-發出一個Send請求之後,就不需要再去檢測是否發送完整,因爲iocp會幫你做這件事情,有些人說iocp沒有做這件事情,這和iocp的高效能是相悖的,並且我做過的無數次測試表明,Iocp要麼斷開連接,要麼就幫你把每個發送請求都發送完整。
8- 出現數據錯亂的時候,不要慌,要從多線程的角度檢查你的解析和發送數據包的代碼,看看是不是有順序上的問題。
9- 當遇到奇怪的內存問題時,逐漸的減少工作線程的數量,可以幫你更快的鎖定問題發生的潛在位置。
10-同樣是遇到內存問題時,請先去檢查你的客戶端在服務器端內部映射對象的釋放是否有問題。而且要小心的編寫iocp完成失敗的處理代碼,防止引用一個錯誤的內部映射對象的地址。
11- overlapped對象一定要保存在持久的位置,並且不到操作完成(不管成功還是失敗)不要釋放,否則可能會引發各種奇怪的問題。
12- IOCP的所有工作都是在獲取完成狀態的那個函數內部進行調度和完成的,所以除了注意工作線程的數量之外,還要注意,儘量保持足夠多的工作線程處在獲取完成狀態的那個等待裏面,這樣做就需要減少工作線程的負擔,確保工作線程內部要處理費時的工作。(我的建議是工作線程和邏輯線程徹底區分開)
13- 剛剛想起來,overlapped對象要爲每次的send和recv操作都準備一個全新的,不能圖方便重複利用。
14- 儘量保持send和recv的緩衝的大小是系統頁面大小的倍數,因爲系統發送或者接收數據的時候,會鎖用戶內存的,比頁面小的緩衝會浪費掉整個一個頁面。(作爲第一條的補充,建議把小包合併成大包發送)
KEY WORDS: UDP; IOCP; I/O; Completion Port
關鍵詞: UDP; IOCP; I/O; 完成端口

 

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