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都完成後才返回。