簡述
TransmitFile
是一個擴展的 API,它允許在套接字連接上發送一個打開的文件。這使得應用程序可以避免親自打開文件,重復地在文件執行讀入操作,再將讀入的那塊數據寫入套接字。相反,已打開的文件的句柄和套接字連接一起給出的,在套接字上,文件數據的讀入和發送都在模式下進行。這就避免了多次的用戶/內核模式切換。與linux的sendfile
函數類似。
TransmitFile通過已經連接的SOCKET句柄傳輸文件,使用操作系統的緩沖管理器來接收數據並提供高質量的文件傳輸。
https://msdn.microsoft.com/en-us/library/windows/desktop/ms740565(v=vs.85).aspx
參數
- hSocket
一個連接的套接字句柄。函數將文件數據寫向這個套接字。其必須是面向連接
(TCP)的SOCKET。如果hFile為NULL,lpTransmitBuffers
將被傳輸 - hFile
已打開的文件句柄。由系統內核讀取文件數據,可以通過FILE_FLAG_SEQUENTIAL_SCAN
提高處理緩存性能。 - nNumberOfBytesToWrite
要傳送的字節數。0值表示傳送整個文件。發生錯誤時,以已發送數據為准。 - nNumberOfBytesPerSend
每次傳送的數據塊的大小。0值表示使用SOCKET LAYER
的默認值。 - lpOverlapped
指向OVERLAPPED
結構的指針。如果hSocket
以打開重疊(默認),可指定這個參數,以實現一個重疊IO操作(異步)。NULL值表示不開啟overlapped(重疊) I/O模式。 - lpTransmitBuffers
指向TRANSMIT_FILE_BUFFERS結構指針。NULL值表示僅僅傳輸文件。
dwFlags
用於修改TransmitFile
函數調用行為的標識。可以包含下表中的定義(在mswscok.h文件中)
標識 | 含義 |
---|---|
TF_DISCONNECT | 在所有文件數據已排隊等待傳輸之后,啟動傳輸層斷開連接 |
TF_REUSE_SOCKET | 准備要重復使用的socket句柄。 此標志僅在指定了TF_DISCONNECT時有效。 當TransmitFile請求完成時,套接字句柄可以傳遞到以前用於建立連接的函數調用,例如AcceptEx或ConnectEx。 這種重用是互斥的; 例如,如果為套接字調用了AcceptEx函數,則僅允許重復使用對AcceptEx函數的后續調用,並且不允許對ConnectEx的后續調用。 注意套接字級文件傳輸受底層傳輸的行為的影響。 例如,TCP套接字可能受到TCP TIME_WAIT狀態的影響,導致TransmitFile調用被延遲。 |
TF_USE_DEFAULT_WORKER | 指示要使用系統的默認線程來處理長 TransmitFile 請求的 Windows 套接字服務提供程序。可以使用以下注冊表參數作為 REG_DWORD 調整系統默認線程︰ HKEY_LOCAL_MACHINE\CurrentControlSet\Services\AFD\Parameters\TransmitWorker |
TF_USE_SYSTEM_THREAD | 指示Windows Sockets服務提供程序使用系統線程來處理長的TransmitFile請求。 |
TF_USE_KERNEL_APC | 指示驅動程序使用內核異步過程調用(APC)而不是工作線程來處理長的TransmitFile請求。 Long TransmitFile請求定義為需要從文件或緩存中進行多次讀取的請求; 因此請求取決於文件的大小和發送數據包的指定長度。 使用TF_USE_KERNEL_APC可以提供顯着的性能優勢。 然而,可能(雖然不太可能),啟動上下文TransmitFile的線程正用於大量計算; 這種情況可能會阻止APC發射。 請注意,Winsock內核模式驅動程序使用正常的內核APC,每當線程處於等待狀態時啟動,這與用戶模式APC不同,每當線程處於用戶模式啟動的警報等待狀態時啟動。 |
TF_WRITE_BEHIND | 立即完成TransmitFile請求,無待處理。 如果指定此標志並且TransmitFile成功,則數據已被系統接受,但不一定由遠程端確認。 不要在指定TF_DISCONNECT和TF_REUSE_SOCKET標志時使用此設置。 |
示例代碼
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"Ws2_32.lib")
#include <MSWSock.h>
#pragma comment(lib, "Mswsock.lib ")
int main(int c, char** v)
{
if (c != 4) {
printf("用法:%s filename ip port\n", v[0]);
return 0;
}
// 創建並初始化winsock數據變量
WSADATA wsaData = { 0 };
int iResult = 0;
SOCKET hSocket = INVALID_SOCKET;
int iFamily = AF_INET;
int iType = SOCK_STREAM;
int iProtocol = IPPROTO_TCP;
// 初始化 Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return -1;
}
// 打開socket
hSocket = socket(iFamily, iType, iProtocol);
if (hSocket == INVALID_SOCKET) {
printf("socket function failed with error = %d\n", WSAGetLastError());
WSACleanup();
return -2;
}
do {
// 打開文件
HANDLE hFile = CreateFile(v[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if (hFile == INVALID_HANDLE_VALUE){
iResult = -3;
break;
}
// 獲取文件大小
//GetFileSize(hFile, NULL);
LARGE_INTEGER liFileSize;
if (GetFileSizeEx(hFile, &liFileSize) == FALSE) {
iResult = -4;
break;
}
// 設置遠程段地址
sockaddr_in remoteAddr;
remoteAddr.sin_family = AF_INET;
//remoteAddr.sin_addr.s_addr = inet_addr(v[2]);
inet_pton(AF_INET, v[2], &remoteAddr.sin_addr);
remoteAddr.sin_port = htons(atoi(v[3]));
// 連接到遠程端
iResult = connect(hSocket, (SOCKADDR *)& remoteAddr, sizeof(remoteAddr));
if (iResult == SOCKET_ERROR) {
printf("connect function failed with error: %ld\n", WSAGetLastError());
iResult = -5;
break;
}
// 使用TransmitFile發送文件
if (TransmitFile(hSocket, hFile, 0, 0, NULL, NULL, TF_USE_DEFAULT_WORKER) == FALSE) {
printf("TransmitFile function failed with error: %ld\n", WSAGetLastError());
iResult = -6;
break;
}
} while (0);
// 關閉socket
iResult = closesocket(hSocket);
if (iResult == SOCKET_ERROR) {
printf("closesocket failed with error = %d\n", WSAGetLastError());
iResult = -7;
}
// 清理
WSACleanup();
return iResult;
}