TransmitFile函數的簡單使用


簡述

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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM