windows socket擴展函數

来源:http://www.cnblogs.com/milanleon/archive/2016/12/09/4657730.html
-Advertisement-
Play Games

1、AcceptEx() AcceptEx()用於非同步接收連接,可以取得客戶程式發送的第一塊數據。 [cpp] view plaincopy BOOL AcceptEx( _In_ SOCKET sListenSocket, //監聽套接字句柄 _In_ SOCKET sAcceptSocket, ...


1、AcceptEx()

AcceptEx()用於非同步接收連接,可以取得客戶程式發送的第一塊數據。

 

[cpp] view plaincopy  
  1. BOOL AcceptEx(  
  2.   _In_  SOCKET       sListenSocket,        //監聽套接字句柄  
  3.   _In_  SOCKET       sAcceptSocket,        //指定一個未被使用的套接字,在這個套接字上接收新的連接
  4.   _In_  PVOID        lpOutputBuffer,       //指定一個緩衝區,用來取得在新連接上接收到的第一塊數據、伺服器的本地地址、客戶端地址, 該參數必須指定  
  5.   _In_  DWORD        dwReceiveDataLength,  //lpOutputBuffer中數據緩衝區的大小,這一大小不包括伺服器的本地地址的大小也不包括客戶端的遠程地址大小,                                                                                為0表示AcceptEx將不等待接收任何數據,而是儘快建立連接。
  6.   _In_  DWORD        dwLocalAddressLength, //lpOutputBuffer緩衝區中為本地地址預留的長度。必須比最大地址長度多16  
  7.   _In_  DWORD        dwRemoteAddressLength,//lpOutputBuffer緩衝區中中為遠程地址預留的長度。必須比最大地址長度多16  
  8.   _Out_ LPDWORD      lpdwBytesReceived,    //接收到數據的長度,這個參數只在同步完成時有效,如果函數返回ERROR_IO_PENDING併在遲些時候完成操作,那                                                                   麽這個DWORD沒有意義,這時你必須獲得從完成通知機制中讀取操作位元組數  
  9.   _In_  LPOVERLAPPED lpOverlapped          //用來處理本請求的OVERLAPPED結構,不能為NULL  
  10. );  


AcceptEx()成功完成後執行了三個操作:1、接受了新的連接;2、新連接的本地地址和遠程地址都會返回;3、接收到了遠程主機發來的第一塊數據。

如果沒有錯誤發生,AcceptEx函數成功完成並返回TRUE。
如果函數失敗,AcceptEx返回FALSE。可以調用WSAGetLastError函數獲得擴展的錯誤信息,如果WSAGetLastError返回ERROR_IO_PENDING,那麼這次行動成功啟動並仍在進行中。

如果提供了數據接收緩衝區(dwReceiveDataLength不為0),AcceptEx()投遞的重疊操作直到接受到連接並且讀到數據之後才會完成。可以使用getsockopt的SO_CONNECT_TIME選項來檢查一個連接是否已經接受,如果它已被接受,你可以獲得連接已經建立了多長時間(秒數),如果套接字未連接,getsockopt返回0xFFFFFFFF。應用程式通過檢查重疊操作是否完成,並組合SO_CONNECT_TIME選項可以確定是否連接已建立了一段時間但沒有收到任何數據,我們建議您通過關閉連接來終止這些連接,從而使AcceptEx()完成操作並返回一個錯誤狀態。例如:

int seconds;
int bytes = sizeof(seconds);
int iResult = 0;
iResult = getsockopt(s, SOL_SOCKET, SO_CONNECT_TIME, (char *)&seconds, (PINT)&bytes);
if (iResult != NO_ERROR) 
{
    printf("getsockopt(SO_CONNECT_TIME) failed with error: %u\n", WSAGetLastError());
}
else 
{
    if (seconds == 0xFFFFFFFF)
        printf("Connection not established yet\n");
    else
        printf("Connection has been established %ld seconds\n", seconds);
}

較accept函數而言,程式使用AcceptEx可以更快連接到一個套接字。

AcceptEx()是一個Microsoft擴展函數,它是從Mswsock.lib庫中導出的,為了能夠直接調用它而不鏈接到Mswsock.lib庫(因為直接鏈接到這個庫的話會將程式綁定在MicrosoftWinsock提供者上),需要使用WSAIoctl()將AcceptEx()載入到記憶體。WSAIoctl()是ioctlsocket()的擴展,它可以使用重疊I/O,函數的第3個到第6個參數是輸入和輸出緩衝區,在這裡傳遞AcceptEx()函數的指針。具體如下:

// 載入擴展函數AcceptEx  
DWORD dwBytes;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
LPFN_ACCEPTEX lpfnAcceptEx = NULL;
int iResult = WSAIoctl(ListenSocket,
                        SIO_GET_EXTENSION_FUNCTION_POINTER,
                        &GuidAcceptEx,
                        sizeof(GuidAcceptEx),
                        &lpfnAcceptEx,
                        sizeof(lpfnAcceptEx),
                        &dwBytes,
                        NULL,
                        NULL);
if (iResult == SOCKET_ERROR) {
    printf("WSAIoctl failed with error: %u\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

......

//調用AcceptEx
BOOL bRetVal = lpfnAcceptEx(ListenSocket, 
                                AcceptSocket,
                                lpOutputBuf,
                                outBufLen - ((sizeof(sockaddr_in) + 16) * 2),
                                sizeof(sockaddr_in) + 16, 
                                sizeof(sockaddr_in) + 16,
                                &dwBytes, 
                                &olOverlap);
if (bRetVal == FALSE) {
    printf(L"AcceptEx failed with error: %u\n", WSAGetLastError());
    closesocket(AcceptSocket);
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}
 

2、GetAcceptExSockaddrs()

GetAcceptExSockaddrs()是專為AcceptEx()準備的,它粘貼從AcceptEx()獲得的數據,將本地和遠程地址傳遞到sockaddr結構。

void GetAcceptExSockaddrs( _In_ PVOID lpOutputBuffer,                //指向傳遞給AcceptEx()接收客戶第一塊數據的緩衝區, 與AcceptEx()的lpOutputBuffer參數相同 _In_ DWORD dwReceiveDataLength,    //上一個參數的大小,應與AcceptEx()的dwReceiveDataLength參數一致 _In_ DWORD dwLocalAddressLength,   //為本地地址預留的空間大小,應與AcceptEx()的dwLocalAddressLength參數一致 _In_ DWORD dwRemoteAddressLength,//為遠程地址預留的空間大小,應與AcceptEx()的dwRemoteAddressLength參數一致 _Out_ LPSOCKADDR *LocalSockaddr,   //用來獲得連接的本地地址 _Out_ LPINT LocalSockaddrLength,       //用來獲得連接的本地地址長度 _Out_ LPSOCKADDR *RemoteSockaddr,//用來獲得連接的遠程地址 _Out_ LPINT RemoteSockaddrLength     ////用來獲得連接的遠程地址長度 ); 當使用AcceptEx時,必須使用GetAcceptExSockaddrs函數將輸出緩衝區的內容解析到三個不同部分的緩衝區 (data, 
local socket address, and remote socket address)。 在windows XP 及隨後版本中,當 AcceptEx函數完成操 
作並且SO_UPDATE_ACCEPT_CONTEXT選項在被接受的socket中被設置時, 與被接受socket相關的本地地址(local 
address )可以使用getsockname函數獲得,類似的,與被接受socket相關的遠程端地址(the remote address)可 
以使用getpeername函數獲得。 使用WSAIoctl()載入GetAcceptExSockaddrs():
    DWORD dwBytes;
    GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
    LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = NULL;
    int Result = WSAIoctl(ListenSocket,
        SIO_GET_EXTENSION_FUNCTION_POINTER,
        &GuidGetAcceptExSockaddrs,
        sizeof(GuidGetAcceptExSockaddrs),
        &lpfnGetAcceptExSockaddrs,
        sizeof(lpfnGetAcceptExSockaddrs),
        &dwBytes,
        NULL,
        NULL);

3、TransmitFile

TransmitFile()函數使用操作系統的緩存管理器來發送文件數據,在套接字上提供高性能的文件數據傳輸,linux的sendfile()、sendfile64()與其類似:
BOOL PASCAL TransmitFile(
   SOCKET                  hSocket, //連接套接字,不能是SOCK_DGRAM或SOCK_RAM類型
   HANDLE                  hFile, //文件句柄,在CreateFile()打開文件的時候可以指定FILE_FLAG_SEQUENTIAL_SCAN標識來提高緩存性能
   DWORD                   nNumberOfBytesToWrite, //要傳輸的位元組,0為傳輸整個文件
   DWORD                   nNumberOfBytesPerSend, //每次發送的數據塊的大小,0為預設大小
   LPOVERLAPPED            lpOverlapped, //如果套接字是已重疊方式創建的,指定這個參數可以進行非同步I/O和指定文件偏移量,預設情況下套接字是以重疊方式創建的?
   LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, //指定在文件數據發送之前和之後要發送的數據
   DWORD                   dwFlags //標誌
);

typedef void (*LPFN_TRANSMITFILE)();

如果hFile設為NULL的話,lpTransmitBuffers將被傳輸。
lpOverlapped為NULL的話,傳輸會從當前文件指針開始,否則OVERLAPPED結構中的偏移量值將指定文件偏移值。
TransmitFile()一次只能發送2的32次方減1大小的文件(大約為2G),超過這個大小的話將nNumberOfBytesToWrite參數設為非0的合理值,多次調用TransmitFile()即可。
dwFlags可以為以下值的組合:
一般我們同時指定前兩個標誌,在這種情況下,當文件或緩衝區數據傳輸操作完成後套接字會斷開,而傳遞給此函數的套接字可以被AcceptEx()或ConnectEx()重覆使用,這樣可以節省套接字創建的開銷,因為套接字創建的開銷很大。
如果hFile和lpTransmitBuffers都設為NULL的話(同時指定了前兩個標誌),函數不會發送任何數據,只是設置套接字允許重用。
TransmitFile 著重於伺服器應用程式,因此只有在 Windows的伺服器版本上,其功能才能得到完全發揮。對於家庭版或專業版,在任何時候,只可以有兩個未完成的TransmitFile(或TransmitPackets)調用,如果超過這個數目,則多餘的將排除等候,直到正在執行的調用結束之後,才會被處理。
    SOCKET ConnectSocket = (SOCKET)lpParameter;
    HANDLE hFile = CreateFileA("file.data",
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING, 
        FILE_FLAG_SEQUENTIAL_SCAN,
        NULL
    );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        int iErrno = WSAGetLastError();
        printf("createfile() error:%d\n", iErrno);
        return -1;
    }

    /************************單次調用TransmitFile()發送一個文件,同步方式***********************/
    BOOL bRet = FALSE;
    bRet = TransmitFile(ConnectSocket, hFile, 0, 0, NULL/*&ov*/, NULL, TF_USE_DEFAULT_WORKER);
    if (!bRet)
    {
        int iErrno = WSAGetLastError();
        printf("TransmitFile() failed: %d\n", iErrno);
    }
    else
    {
        printf("transfer end\n");
    }


    /************************多次調用TransmitFile()發送一個文件,非同步方式***********************/
    OVERLAPPED ov;
    memset(&ov, 0, sizeof(ov));
    ov.hEvent = WSACreateEvent();

    BOOL bRet = FALSE;
    int unsendDataSize = GetFileSize(hFile, NULL);
    int blockSize = 8192;
    if (blockSize > unsendDataSize)
        blockSize = unsendDataSize;
    DWORD sendBytes, flags;
    
    while (1)
    {
        bRet = TransmitFile(ConnectSocket, hFile, blockSize, 0, &ov, NULL, TF_USE_DEFAULT_WORKER);
        if (!bRet)
        {
            int iErrno = WSAGetLastError();
            if (iErrno == WSA_IO_PENDING || iErrno == ERROR_IO_PENDING)
            {
                WSAGetOverlappedResult(ConnectSocket, &ov, &sendBytes, TRUE, &flags);
                if (!ov.Internal)
                {
                    unsendDataSize -= sendBytes;
                    if (unsendDataSize == 0)
                    {
                        printf("transfer end\n");
                        break;
                    }
                    ov.Offset += sendBytes;
                    if (blockSize > unsendDataSize)
                        blockSize = unsendDataSize;
                }
                else
                {
                    int iErrno = WSAGetLastError();
                    printf("TransmitFile() failed: %d\n", iErrno);
                    break;
                }
            }
            else
            {
                printf("TransmitFile() failed: %d\n", iErrno);
                break;
            }
        }
        else
        {
            if (!ov.Internal)
            {
                unsendDataSize -= sendBytes;
                if (unsendDataSize == 0)
                {
                    printf("transfer end\n");
                    break;
                }
                ov.Offset += sendBytes;
                if (blockSize > unsendDataSize)
                    blockSize = unsendDataSize;
            }
            else
            {
                int iErrno = WSAGetLastError();
                printf("TransmitFile() failed: %d\n", iErrno);
                break;
            }
        }
    }
    
    //關閉建立連接的套接字  
    closesocket(ConnectSocket);
    CloseHandle(hFile);
4、TransmitPackets()
TransmitPackets()與TransmitFile()功能類似,不同的是它可以發送多個文件或多個記憶體緩衝區中的數據。

BOOL PASCAL TransmitPackets(
SOCKET hSocket, //連接套接字,可以是SOCK_DGRAM
LPTRANSMIT_PACKETS_ELEMENT lpPacketArray, //封包元素數組
DWORD nElementCount, //lpPacketArray中封包元素的的數量
DWORD nSendSize, //每次發送數據的大小
LPOVERLAPPED lpOverlapped, //同TransmitFile()

DWORD dwFlags //同TransmitFile,不過不是以TF開頭而是以TP開頭
);

typedef void (*LPFN_TRANSMITPACKETS)();

lpPacketArray封包元素數組是LPTRANSMIT_PACKETS_ELEMENT結構類型的數組:

typedef struct _TRANSMIT_PACKETS_ELEMENT {
  ULONG dwElFlags; //指定此元素中包含的緩衝區類型:文件TP_ELEMENT_FILE或記憶體TP_ELEMENT_MEMORY或TP_ELEMENT_EOP
  ULONG cLength; //指定要傳輸文件的多少個位元組,0為傳輸整個文件
  union {
    struct {
      LARGE_INTEGER nFileOffset;//文件的偏移量,-1表示從當前文件指針傳輸
      HANDLE        hFile;//文件句柄
    };
    PVOID  pBuffer;//數據記憶體緩衝區
  };
} TRANSMIT_PACKETS_ELEMENT;
dwElFlags成員的TP_ELEMENT_EOP標誌可以和另外兩個標誌按位或組合,指示在發送中這個元素不應該和後面的元素混合起來,這是用來精確的控制面向數據報或消息的socket傳輸。
使用TransmitFile()和TransmitPackets()的除了可以提高發送文件的效率外的另一個好處就是可以通過指定TF_REUSE_SOCKET和TF_DISCONNECT標誌來重用套接字句柄。每當API完成數據的傳輸工作後,就會在傳輸層級別斷開連接,這樣這個套接字就又可以重新提供給AcceptEx()使用。採用這種優化的方法編程,將減輕那個專門做接受操作的線程創建套接字的壓力
5、ConnectEx()

ConnectEx()用來非同步連接調用,連接建立之後也可以發送數據。由於ConnectEx使用的是非同步通知機制,所以如果我們的客戶端程式需要多個連接的話使用ConnectEx就不用為每個連接使用一個線程來管理這個連接了。
BOOL PASCAL ConnectEx(
  _In_     SOCKET                s, //未連接的socket
  _In_     const struct sockaddr *name, //要連接的遠程地址
  _In_     int                   namelen, //遠程地址長度
  _In_opt_ PVOID                 lpSendBuffer, //建立連接後要發送的數據,NULL為不發送
  _In_     DWORD                 dwSendDataLength, //lpSendBuffer中數據長度
  _Out_    LPDWORD               lpdwBytesSent, //實際發送的位元組數
_In_     LPOVERLAPPED          lpOverlapped //重疊結構,必須指定);
連接可能不會立即成功,這時ConnectEx()返回FALSE,調用WSAGetLastError()返回ERROR_IO_PENDING表明連接正在進行。如果錯誤碼是 WSAECONNREFUSEDWSAENETUNREACH, 或 WSAETIMEDOUT那麼可以再次調用ConnectEx進行連接。
當連接成功或失敗後lpOverlapped指向的重疊結構會得到通知,可以使用事件或完成埠作為完成通知機制。GetQueuedCompletionStatus or GetOverlappedResult or WSAGetOverlappedResult函數的lpNumberOfBytesTransferred參數可以獲得發送的位元組數。
6、DisConnectEx()
DisConnectEx()用來關閉套接字上的連接,並允許重用套接字。
BOOL DisconnectEx(
  _In_ SOCKET       hSocket, //面向連接的套接字
  _In_ LPOVERLAPPED lpOverlapped, //如果套接字是以重疊方式創建的,指定這個參數以進行重疊I/O操作
  _In_ DWORD        dwFlags, //TF_REUSE_SOCKET或0,0為僅僅斷開連接,TF_REUSE_SOCKET為可重用套接字
_In_ DWORD        reserved //保留 );
如果這個函數接受了一個重疊結構,並且在要關閉的套接字上仍有未決操作,它會返回FALSE,出錯代碼是WSA_IP_PENDING,一旦套接字上的所有未決操作都返回,DisConnectEx()投遞的操作就會完成。
如果以阻塞方式調用這個函數的話,它將在所有未決I/O都完成後才返回。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 生成公鑰的私鑰: 執行結果: 使用公鑰和私鑰來加密和解密: 執行結果: PS: 某些RSA模塊生成的公鑰的頭為“ BEGIN PUBLIC KEY ”, 這種RSA頭無法正常導入生成public key,需要標準的“ BEGIN RSA PUBLIC KEY ”。 通過RSA加密後的密文通常很難使用 ...
  • 此文章是基於 搭建SpringMVC+Spring+Hibernate平臺 一. 準備工作 1. 點擊此找到並下載 commons-digester3-3.2.jar 2. 點擊此找到並下載 commons-beanutils-1.9.3.jar 目前最高版本 3. 將得到的jar包放到工程的 li ...
  • PHP開啟頁面報錯的方法很簡單,在<?php內加入下麵的代碼就可以了: 插入在頁面頂部就可以了。 ...
  • 如果在調用PHP查詢資料庫,在echo後返回的是Resource id #9,可能你的輸出方式是: 按以上方式直接輸出,由於mysql_query($sql)本身返回的就是一個resource,也就是返回的就是Resource id #9,因此在輸出之前,還需要加一個函數: ...
  • 電腦程式的思維邏輯 (1) - 數據和變數 電腦程式的思維邏輯 (2) - 賦值 電腦程式的思維邏輯 (3) - 基本運算 電腦程式的思維邏輯 (4) - 整數的二進位表示與位運算 電腦程式的思維邏輯 (5) - 小數計算為什麼會出錯? 電腦程式的思維邏輯 (6) - 如何從亂碼中恢復 ...
  • 一、異常處理: 1 AttributeError 試圖訪問一個對象沒有的樹形,比如foo.x,但是foo沒有屬性x 2 IOError 輸入/輸出異常;基本上是無法打開文件 3 ImportError 無法引入模塊或包;基本上是路徑問題或名稱錯誤 4 IndentationError 語法錯誤(的子 ...
  • ...
  • 好久沒寫了,都忘記博客了,趁著現在還在公司,寫的東西是經過驗證的,不是在家憑記憶力寫的,正確率有保障,就說說最近遇到的一件事情吧。 以前一直用的oracle資料庫,這次項目我負責的模塊所在的系統是用的mysql資料庫,結果當初建表時候,欄位什麼的全靠百度,實在是英語不行,然後有個欄位叫usage,是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...