程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> windows socket擴展函數,socket擴展函數

windows socket擴展函數,socket擴展函數

編輯:C++入門知識

windows socket擴展函數,socket擴展函數


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表明連接正在進行。如果錯誤碼是 WSAECONNREFUSED, WSAENETUNREACH, 或 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都完成後才返回。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved