1、AcceptEx()
AcceptEx()用於異步接收連接,可以取得客戶程序發送的第一塊數據。
[cpp] view plaincopy
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,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都完成後才返回。