c++ Windows 文件传输 “高铁”:TransmitFile 让速度飞起来



各位编程界的 “老司机” 们,今天咱们来盘一盘盘 Windows 系统里一个能让文件传输速度 “飙车” 的狠角色 ——TransmitFile。这玩意儿可不是那种慢吞吞的 “小电驴”,而是文件传输界的 “高铁”,一旦用上,效率直接拉满!

先整个接地气的类比:外卖界的 “直达专送”

想象你是一家奶茶店老板,要给顾客送 100 杯奶茶:



  • 普通操作(read+send):你得自己一杯杯打包,再叫 100 个外卖小哥,一人送一杯 —— 累到直不起腰,还容易送错。
  • TransmitFile 操作:你直接联系外卖站,用一辆冷链车把 100 杯奶茶整箱拉走,全程一站直达,你只需要在店里吹着空调等签收。



TransmitFile就相当于操作系统内核里的 “冷链直达车”,它跳过了用户态和内核态之间的 “反复横跳”(不用你手动 “打包奶茶”),直接从文件系统把数据怼到网络协议栈,效率高到让你怀疑人生。

啥时候该请这位 “快递大神” 出山?

简单说:只要你在 Windows 上用 socket 发文件(比如做个视频网站、搭个 FTP 服务器),用它就对了。尤其是大文件,普通方法传起来像 “蜗牛爬”,它却能跑得比 “博尔特” 还快。

上车前必备的 “车票”(前置知识)

  1. Winsock 初始化:网络编程的 “启动钥匙”,没它连不上网。
  2. socket 创建:相当于 “电话线”,没它没法通信。
  3. 文件句柄(HANDLE):要发送的文件得先 “登记身份证”,不然系统不认。
  4. 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:准备工作

  1. 在代码文件夹里建一个story.txt,随便写点内容(比如 “今天天气不错,适合写代码”)。
  2. 准备好编译器:推荐用 Visual Studio(免费的 Community 版就行),或者 MinGW-w64。

步骤 4:编译运行(以 VS 为例)

  1. 打开 VS,新建两个 “控制台应用” 项目(一个叫 Server,一个叫 Client)。
  2. 把上面的代码分别复制到对应项目的.cpp 文件里。
  3. 右键项目→“属性”→“链接器”→“输入”→“附加依赖项”,确保有ws2_32.lib(VS 通常自带,没有就手动加)。
  4. 先运行 Server(会显示 “坐等客户端上车”)。
  5. 再运行 Client(会显示 “接收完毕”)。
  6. 此时文件夹里会多出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 次数据拷贝和状态切换 —— 这就是速度的秘诀!

标题

  1. 《Windows 文件传输 “高铁”:TransmitFile 让速度飞起来》
  2. 《从 “步行” 到 “超音速”:TransmitFile 实战指南》

简介

本文用 “高铁快递” 的趣味类比,通俗讲解 Windows API 中 TransmitFile 函数的工作原理,通过 3 个完整案例演示其用法,详细说明编译运行步骤,帮助开发者轻松掌握这一高效文件传输工具。

关键词

#TransmitFile #Windows 编程 #socket 文件传输 #高效 IO #Winsock 技巧

原文链接:,转发请注明来源!