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