日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WinSocket模型的探讨——完成端口模型

發布時間:2025/3/15 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WinSocket模型的探讨——完成端口模型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

眾所皆知,完成端口是在WINDOWS平臺下效率最高,擴展性最好的IO模型,特別針對于WINSOCK的海量連接時,更能顯示出其威力。其實建立一個完成端口的服務器也很簡單,只要注意幾個函數,了解一下關鍵的步驟也就行了。

這是篇完成端口入門級的文章,分為以下幾步來說明完成端口:?

  • 函數
  • 常見問題以及解答
  • 步驟
  • 例程
  • 1、函數:

    我們在完成端口模型下會使用到的最重要的兩個函數是:
    CreateIoCompletionPort、GetQueuedCompletionStatus

    CreateIoCompletionPort? 的作用是創建一個完成端口和把一個IO句柄和完成端口關聯起來:

    // 創建完成端口
    HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

    // 把一個IO句柄和完成端口關聯起來,這里的句柄是一個socket 句柄
    CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);

    其中第一個參數是句柄,可以是文件句柄、SOCKET句柄。
    第二個就是我們上面創建出來的完成端口,這里就把兩個東西關聯在一起了。
    第三個參數很關鍵,叫做PerHandleData,就是對應于每個句柄的數據塊。我們可以使用這個參數在后面取到與這個SOCKET對應的數據。
    最后一個參數給0,意思就是根據CPU的個數,允許盡可能多的線程并發執行。

    GetQueuedCompletionStatus 的作用就是取得完成端口的結果:

    // 從完成端口中取得結果
    GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)

    第一個參數是完成端口
    第二個參數是表明這次的操作傳遞了多少個字節的數據
    第三個參數是OUT類型的參數,就是前面CreateIoCompletionPort傳進去的單句柄數據,這里就是前面的SOCKET句柄以及與之相對應的數據,這里操作系統給我們返回,讓我們不用自己去做列表查詢等操作了。
    第四個參數就是進行IO操作的結果,是我們在投遞 WSARecv / WSASend 等操作時傳遞進去的,這里操作系統做好準備后,給我們返回了。非常省事!!

    個人感覺完成端口就是操作系統為我們包裝了很多重疊IO的不爽的地方,讓我們可以更方便的去使用,下篇我將會嘗試去講述完成端口的原理。

    2、常見問題和解答

    a、什么是單句柄數據(PerHandle)和單IO數據(PerIO)

    單句柄數據就是和句柄對應的數據,像socket句柄,文件句柄這種東西。

    單IO數據,就是對應于每次的IO操作的數據。例如每次的WSARecv/WSASend等等

    其實我覺得PER是每次的意思,翻譯成每個句柄數據和每次IO數據還比較清晰一點。

    在完成端口中,單句柄數據直接通過GetQueuedCompletionStatus 返回,省去了我們自己做容器去管理。單IO數據也容許我們自己擴展OVERLAPPED結構,所以,在這里所有與應用邏輯有關的東西都可以在此擴展。

    b、如何判斷客戶端的斷開

    我們要處理幾種情況

    1) 如果客戶端調用了closesocket,我們就可以這樣判斷他的斷開:

    if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, 。。。)
    {
    }
    if(BytesTransferred == 0)
    {
    ??? // 客戶端斷開,釋放資源
    }

    2) 如果是客戶端直接退出,那就會出現64錯誤,指定的網絡名不可再用。這種情況我們也要處理的:

    if(0 == GetQueuedCompletionStatus(。。。))
    {
    ?? if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
    ?? {
    ??????? // 客戶端斷開,釋放資源
    ?? }
    }

    3、步驟

    編寫完成端口服務程序,無非就是以下幾個步驟:

    ? 1、創建一個完成端口
    ? 2、根據CPU個數創建工作者線程,把完成端口傳進去線程里
    ? 3、創建偵聽SOCKET,把SOCKET和完成端口關聯起來
    ? 4、創建PerIOData,向連接進來的SOCKET投遞WSARecv操作

    ? 5、線程里所做的事情:
    ?a、GetQueuedCompletionStatus,在退出的時候就可以使用PostQueudCompletionStatus使線程退出
    ?b、取得數據并處理

    4、例程

    下面是服務端的例程,可以使用《WinSocket模型的探討——Overlapped模型(一)》中的客戶端程序來測試次服務端。稍微研究一下,也就會對完成端口模型有個大概的了解了。

    /*

    ?? 完成端口服務器

    ?? 接收到客戶端的信息,直接顯示出來

    */

    #include "winerror.h"
    #include "Winsock2.h"
    #pragma comment(lib, "ws2_32")

    #include "windows.h"


    #include <iostream>
    using namespace std;


    /// 宏定義
    #define PORT 5050
    #define DATA_BUFSIZE 8192

    #define OutErr(a) cout << (a) << endl /
    ??????<< "出錯代碼:" << WSAGetLastError() << endl /
    ??????<< "出錯文件:" << __FILE__ << endl??/
    ??????<< "出錯行數:" << __LINE__ << endl /

    #define OutMsg(a) cout << (a) << endl;


    /// 全局函數定義


    ///
    //
    // 函數名?????? : InitWinsock
    // 功能描述???? : 初始化WINSOCK
    // 返回值?????? : void
    //
    ///
    void InitWinsock()
    {
    ?// 初始化WINSOCK
    ?WSADATA wsd;
    ?if( WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
    ?{
    ??OutErr("WSAStartup()");
    ?}
    }

    ///
    //
    // 函數名?????? : BindServerOverlapped
    // 功能描述???? : 綁定端口,并返回一個 Overlapped 的Listen Socket
    // 參數???????? : int nPort
    // 返回值?????? : SOCKET
    //
    ///
    SOCKET BindServerOverlapped(int nPort)
    {
    ?// 創建socket
    ?SOCKET sServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

    ?// 綁定端口
    ?struct sockaddr_in servAddr;
    ?servAddr.sin_family = AF_INET;
    ?servAddr.sin_port = htons(nPort);
    ?servAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    ?if(bind(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0)
    ?{
    ??OutErr("bind Failed!");
    ??return NULL;
    ?}

    ?// 設置監聽隊列為200
    ?if(listen(sServer, 200) != 0)
    ?{
    ??OutErr("listen Failed!");
    ??return NULL;
    ?}
    ?return sServer;
    }


    /// 結構體定義
    typedef struct
    {
    ?? OVERLAPPED Overlapped;
    ?? WSABUF DataBuf;
    ?? CHAR Buffer[DATA_BUFSIZE];
    } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;


    typedef struct
    {
    ?? SOCKET Socket;
    } PER_HANDLE_DATA, * LPPER_HANDLE_DATA;


    DWORD WINAPI ProcessIO(LPVOID lpParam)
    {
    ?HANDLE CompletionPort = (HANDLE)lpParam;
    ??? DWORD BytesTransferred;
    ??? LPPER_HANDLE_DATA PerHandleData;
    ??? LPPER_IO_OPERATION_DATA PerIoData;

    ?while(true)
    ?{
    ?
    ??if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
    ??{
    ???if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
    ???{
    ????cout << "closing socket" << PerHandleData->Socket << endl;
    ????
    ????closesocket(PerHandleData->Socket);
    ????
    ????delete PerIoData;
    ????delete PerHandleData;
    ????continue;
    ???}
    ???else
    ???{
    ????OutErr("GetQueuedCompletionStatus failed!");
    ???}
    ???return 0;
    ??}
    ??
    ??// 說明客戶端已經退出
    ??if(BytesTransferred == 0)
    ??{
    ???cout << "closing socket" << PerHandleData->Socket << endl;
    ???closesocket(PerHandleData->Socket);
    ???delete PerIoData;
    ???delete PerHandleData;
    ???continue;
    ??}

    ??// 取得數據并處理
    ??cout << PerHandleData->Socket << "發送過來的消息:" << PerIoData->Buffer << endl;

    ??// 繼續向 socket 投遞WSARecv操作
    ??DWORD Flags = 0;
    ??DWORD dwRecv = 0;
    ??ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
    ??PerIoData->DataBuf.buf = PerIoData->Buffer;
    ??PerIoData->DataBuf.len = DATA_BUFSIZE;
    ??WSARecv(PerHandleData->Socket, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);?
    ?}

    ?return 0;
    }

    void main()
    {
    ?InitWinsock();

    ?HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

    ?// 根據系統的CPU來創建工作者線程
    ?SYSTEM_INFO SystemInfo;
    ?GetSystemInfo(&SystemInfo);

    ?for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
    ?{
    ??HANDLE hProcessIO = CreateThread(NULL, 0, ProcessIO, CompletionPort, 0, NULL);
    ??if(hProcessIO)
    ??{
    ???CloseHandle(hProcessIO);
    ??}
    ?}

    ?// 創建偵聽SOCKET
    ?SOCKET sListen = BindServerOverlapped(PORT);


    ?SOCKET sClient;
    ?LPPER_HANDLE_DATA PerHandleData;
    ??? LPPER_IO_OPERATION_DATA PerIoData;
    ?while(true)
    ?{
    ??// 等待客戶端接入
    ??//sClient = WSAAccept(sListen, NULL, NULL, NULL, 0);
    ??sClient = accept(sListen, 0, 0);
    ??
    ??cout << "Socket " << sClient << "連接進來" << endl;
    ??
    ??PerHandleData = new PER_HANDLE_DATA();
    ??PerHandleData->Socket = sClient;

    ??// 將接入的客戶端和完成端口聯系起來
    ??CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);

    ??// 建立一個Overlapped,并使用這個Overlapped結構對socket投遞操作
    ??PerIoData = new PER_IO_OPERATION_DATA();
    ??
    ??ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
    ??PerIoData->DataBuf.buf = PerIoData->Buffer;
    ??PerIoData->DataBuf.len = DATA_BUFSIZE;

    ??// 投遞一個WSARecv操作
    ??DWORD Flags = 0;
    ??DWORD dwRecv = 0;
    ??WSARecv(sClient, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
    ?}

    ?DWORD dwByteTrans;
    ?PostQueuedCompletionStatus(CompletionPort, dwByteTrans, 0, 0);
    ?closesocket(sListen);
    }

    總結

    以上是生活随笔為你收集整理的WinSocket模型的探讨——完成端口模型的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。