各位编程界的 “老司机” 们,今天咱们来盘一盘盘 Windows 系统里一个能让文件传输速度 “飙车” 的狠角色 ——TransmitFile。这玩意儿可不是那种慢吞吞的 “小电驴”,而是文件传输界的 “高铁”,一旦用上,效率直接拉满!
先整个接地气的类比:外卖界的 “直达专送”
想象你是一家奶茶店老板,要给顾客送 100 杯奶茶:
- 普通操作(read+send):你得自己一杯杯打包,再叫 100 个外卖小哥,一人送一杯 —— 累到直不起腰,还容易送错。
- TransmitFile 操作:你直接联系外卖站,用一辆冷链车把 100 杯奶茶整箱拉走,全程一站直达,你只需要在店里吹着空调等签收。
TransmitFile就相当于操作系统内核里的 “冷链直达车”,它跳过了用户态和内核态之间的 “反复横跳”(不用你手动 “打包奶茶”),直接从文件系统把数据怼到网络协议栈,效率高到让你怀疑人生。
啥时候该请这位 “快递大神” 出山?
简单说:只要你在 Windows 上用 socket 发文件(比如做个视频网站、搭个 FTP 服务器),用它就对了。尤其是大文件,普通方法传起来像 “蜗牛爬”,它却能跑得比 “博尔特” 还快。
上车前必备的 “车票”(前置知识)
- Winsock 初始化:网络编程的 “启动钥匙”,没它连不上网。
- socket 创建:相当于 “电话线”,没它没法通信。
- 文件句柄(HANDLE):要发送的文件得先 “登记身份证”,不然系统不认。
- TransmitFile 参数:记几个关键的就行,后面案例会手把手教。
案例 1:基础款 “文件高铁”—— 服务器发文件,客户端接文件
目标:服务器用TransmitFile发个文本文件,客户端乖乖接住。
步骤 1:服务器代码(Server.cpp)
cpp
运行
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
// 链接Winsock库,少了它编译会报错(相当于给手机插SIM卡)
#pragma comment(lib, "ws2_32.lib")
int main() {
// 1. 初始化Winsock(开机启动网络)
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "Winsock启动失败!错误码:" << WSAGetLastError() << std::endl;
return 1;
}
// 2. 创建socket(买根电话线)
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET) {
std::cerr << "socket创建失败!错误码:" << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
// 3. 绑定端口(选个电话号码,这里用8080)
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY; // 所有本机IP都能接到
serverAddr.sin_port = htons(8080); // 端口号转网络格式
if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "端口绑定失败!错误码:" << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return 1;
}
// 4. 监听连接(开机等电话)
if (listen(serverSocket, 5) == SOCKET_ERROR) { // 最多同时接5个电话
std::cerr << "监听失败!错误码:" << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return 1;
}
std::cout << "服务器已启动,端口8080,坐等客户端上车..." << std::endl;
// 5. 接受客户端连接(接电话)
SOCKET clientSocket;
sockaddr_in clientAddr;
int clientAddrSize = sizeof(clientAddr);
clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &clientAddrSize);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "接电话失败!错误码:" << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return 1;
}
std::cout << "客户端已上车,准备发车送文件!" << std::endl;
// 6. 打开要发送的文件(把要寄的货装箱)
HANDLE hFile = CreateFileA(
"story.txt", // 文件名
GENERIC_READ, // 只读模式
FILE_SHARE_READ, // 允许别人同时读
NULL, // 默认安全设置
OPEN_EXISTING, // 只发已存在的文件
FILE_ATTRIBUTE_NORMAL, // 普通文件
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
std::cerr << "文件打不开!错误码:" << GetLastError() << std::endl;
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
return 1;
}
// 7. 用TransmitFile发车(高铁启动!)
DWORD bytesSent;
if (!TransmitFile(
clientSocket, // 发给哪个客户端
hFile, // 要发的文件
0, // 0表示发完整文件
0, // 系统自动选每次发多少
NULL, // 不用异步操作
NULL, // 不发额外数据
0 // 默认模式
)) {
std::cerr << "文件发送失败!错误码:" << WSAGetLastError() << std::endl;
CloseHandle(hFile);
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
return 1;
}
// 8. 收尾工作(到站下车)
std::cout << "文件发送成功!" << std::endl;
CloseHandle(hFile); // 关文件
closesocket(clientSocket); // 挂电话
closesocket(serverSocket); // 关服务器
WSACleanup(); // 断网
return 0;
}
步骤 2:客户端代码(Client.cpp)
cpp
运行
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <fstream>
#pragma comment(lib, "ws2_32.lib")
int main() {
// 1. 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "Winsock启动失败!错误码:" << WSAGetLastError() << std::endl;
return 1;
}
// 2. 创建socket
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "socket创建失败!错误码:" << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
// 3. 连接服务器(拨号打电话)
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
if (inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr) <= 0) { // 连接本机
std::cerr << "IP地址无效!" << std::endl;
closesocket(clientSocket);
WSACleanup();
return 1;
}
if (connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "连不上服务器!错误码:" << WSAGetLastError() << std::endl;
closesocket(clientSocket);
WSACleanup();
return 1;
}
std::cout << "已连上服务器,准备接收文件..." << std::endl;
// 4. 接收文件并保存(收货开箱)
std::ofstream outFile("received_story.txt", std::ios::binary);
if (!outFile.is_open()) {
std::cerr << "创建接收文件失败!" << std::endl;
closesocket(clientSocket);
WSACleanup();
return 1;
}
char buffer[4096]; // 用大一点的缓冲区,接得快
int bytesRead;
while ((bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0)) > 0) {
outFile.write(buffer, bytesRead);
}
if (bytesRead == SOCKET_ERROR) {
std::cerr << "接收出错!错误码:" << WSAGetLastError() << std::endl;
} else {
std::cout << "文件接收完毕,已保存为received_story.txt" << std::endl;
}
// 5. 收尾
outFile.close();
closesocket(clientSocket);
WSACleanup();
return 0;
}
步骤 3:准备工作
- 在代码文件夹里建一个story.txt,随便写点内容(比如 “今天天气不错,适合写代码”)。
- 准备好编译器:推荐用 Visual Studio(免费的 Community 版就行),或者 MinGW-w64。
步骤 4:编译运行(以 VS 为例)
- 打开 VS,新建两个 “控制台应用” 项目(一个叫 Server,一个叫 Client)。
- 把上面的代码分别复制到对应项目的.cpp 文件里。
- 右键项目→“属性”→“链接器”→“输入”→“附加依赖项”,确保有ws2_32.lib(VS 通常自带,没有就手动加)。
- 先运行 Server(会显示 “坐等客户端上车”)。
- 再运行 Client(会显示 “接收完毕”)。
- 此时文件夹里会多出received_story.txt,打开看看 —— 内容和story.txt一样,搞定!
案例 2:带 “自动关门” 功能的高铁 —— 发完文件自动断连接
有时候发完文件想自动挂断连接,不用手动closesocket。给TransmitFile加个TF_DISCONNECT标志就行,相当于 “到站自动开门”。
修改服务器发送部分代码:
cpp
运行
// 最后一个参数改成TF_DISCONNECT
if (!TransmitFile(
clientSocket,
hFile,
0,
0,
NULL,
NULL,
TF_DISCONNECT // 发完自动断连接
)) {
// 错误处理...
}
// 这里就不用手动closesocket(clientSocket)了,系统会自动处理
案例 3:发文件前先 “打个招呼”—— 带额外数据的发送
有时候需要先给客户端发点 “小纸条”(比如文件名、文件大小),再发文件。TransmitFile的lpTransmitBuffers参数就能搞定,相当于 “先寄封信说明情况,再发货”。
服务器代码片段:
cpp
运行
// 定义要先发的“小纸条”
CHAR header[] = "即将发送:story.txt,大小:1024字节";
WSABUF headerBuf;
headerBuf.len = strlen(header);
headerBuf.buf = header;
// 准备传输缓冲区
TRANSMIT_FILE_BUFFERS tfb;
ZeroMemory(&tfb, sizeof(tfb));
tfb.lpSendBuffer = &headerBuf; // 要先发的数据
tfb.nSendBuffers = 1; // 只有1个缓冲区
// 发送时带上这个缓冲区
if (!TransmitFile(
clientSocket,
hFile,
0,
0,
NULL,
&tfb, // 传入额外数据
0
)) {
// 错误处理...
}
为啥这货这么快?
普通发送流程:用户态读文件→拷贝到内核→内核发网络,就像 “跨栏比赛”,每一步都要减速。
TransmitFile流程:内核直接读文件→发网络,全程 “直线冲刺”,少了 N 次数据拷贝和状态切换 —— 这就是速度的秘诀!
标题
- 《Windows 文件传输 “高铁”:TransmitFile 让速度飞起来》
- 《从 “步行” 到 “超音速”:TransmitFile 实战指南》
简介
本文用 “高铁快递” 的趣味类比,通俗讲解 Windows API 中 TransmitFile 函数的工作原理,通过 3 个完整案例演示其用法,详细说明编译运行步骤,帮助开发者轻松掌握这一高效文件传输工具。
关键词
#TransmitFile #Windows 编程 #socket 文件传输 #高效 IO #Winsock 技巧
