基于 TCP 的文件传输:开发者实用指南

发布: (2025年12月22日 GMT+8 05:43)
10 min read
原文: Dev.to

Source: Dev.to

介绍

每次下载文件、观看视频流或在设备之间同步数据时,可靠的文件传输都在后台悄悄进行。支撑这种可靠性的核心是 TCP(传输控制协议),它是一种网络协议,确保你的数据完整且按顺序到达。

对于开发者来说,了解如何实现 TCP 文件传输不仅是理论知识——它是支撑从 HTTP 到自定义实时应用程序的一切基础。在本指南中,我们将使用 Node.js 构建一个 实用的 TCP 文件传输系统,探讨套接字和流是如何协同工作,以可靠地在网络间移动数据。

阅读完本指南后,你将拥有可运行的 TCP 服务器和客户端代码,能够实现文件的无缝传输。

TCP 与套接字,简化版

TCP 想象成一种可靠的快递服务,用来传输你的数据。不同于 UDP(就像把明信片扔过围墙,盼望它们能到达),TCP 能保证数据送达、保持顺序,并在数据包丢失时进行重传。

套接字(Sockets) 是通信端点——可以把它们想象成两个应用之间的专用电话线。当你的客户端套接字连接到服务器套接字时,它们就建立了一条可靠的双向数据通道。

关键概念: 在 Node.js 中,net 模块提供了这种套接字编程能力,让你能够创建 TCP 服务器和客户端,而无需处理底层网络细节。

Source:

构建 TCP 服务器

下面演示如何构建一个接收客户端文件的 TCP 服务器:

import { createWriteStream } from "node:fs";
import net from "node:net";

const clientsList = [];

const server = net.createServer((socket) => {
  clientsList.push(socket);

  const writeStream = createWriteStream("OpsDev.png");
  socket.pipe(writeStream);

  socket.on("close", () => {
    console.log(socket.remoteAddress, ": Client disconnected");
  });

  socket.on("error", () => {
    console.log("Client Lost");
  });

  console.log("Client Connected", socket.remoteAddress);
});

server.listen(8080, "0.0.0.0", () => {
  console.log("Server started on port 8080");
});

逐项解析

  • net.createServer(callback) – 创建一个 TCP 服务器。每当有客户端连接时,回调函数会被调用,并提供该连接的 socket 对象。
  • clientsList 数组 – 用于跟踪所有已连接的客户端。在需要向多个客户端广播消息或管理连接的场景中非常有用。
  • socket.pipe(writeStream) – 将网络套接字收到的数据直接管道到文件写入流。Node.js 流会自动处理缓冲和背压,无需手动分块。
  • 事件监听器
    • 'close' 在客户端正常断开连接时触发。
    • 'error' 处理网络故障等连接问题。
  • server.listen(8080, "0.0.0.0") – 将服务器绑定到所有网络接口的 8080 端口,使其可以被其他机器访问。

工作流程: 客户端连接 → 服务器创建 socket → 传入的数据自动写入 OpsDev.png → 传输完成后关闭连接。

构建 TCP 客户端

现在让我们构建发送文件的客户端:

import { createReadStream, createWriteStream } from "node:fs";
import net from "node:net";

process.stdin.on("data", (input) => {
  const inputString = input.toString().trim();

  if (inputString === "send") {
    const readStream = createReadStream("/Users/sudipos/Desktop/DevOps.png");
    readStream.pipe(socket);

    readStream.on("end", () => {
      console.log("File ended");
    });
  }
});

const socket = net.createConnection({ host: "172.168.29.148", port: 8080 });

const writeStream = createWriteStream("/Users/sudipos/Desktop/OpsDev.png");

socket.on("error", () => {
  console.log("Server Lost");
});

细分说明

  • net.createConnection() – 在指定的 IP 地址和端口上建立 TCP 套接字连接。这是客户端专用的通道。
  • process.stdin.on('data') – 监听终端输入。当你输入 send 并回车时,会触发文件传输,让你手动控制何时发送文件。
  • createReadStream(filePath) – 打开一个可读流,从源文件中读取数据。流会以块的形式读取,能够在处理大文件时保持低内存占用。
  • readStream.pipe(socket) – 将文件数据直接管道到 TCP 套接字。文件读取的每个块都会自动作为 TCP 包发送到服务器。
  • readStream.on('end') – 当整个文件读取完毕并发送完毕时触发,确认传输已完成。

流程: 客户端连接 → 你输入 send → 文件分块读取 → 数据通过套接字流动 → 服务器接收并写入磁盘。


如何整体配合

以下是 TCP 文件传输的完整数据流程:

客户端createReadStream 读取文件字节 → pipe() 将块发送到套接字 → TCP 层将数据拆分为数据包

网络层 → TCP 处理数据包传输、确认、重传和排序

服务器端 → TCP 重新组装数据包 → 套接字接收字节流 → pipe() 将块写入 createWriteStream → 文件保存到磁盘

可视化流程

[Source File] → [Read Stream] → [Client Socket]

                               [TCP Network]

[Destination File] ← [Write Stream] ← [Server Socket]

Node.js 流的优势在于它们会自动处理 背压。如果服务器写入磁盘的速度不够快,客户端的读取流会暂停,直到服务器跟上——不会丢失数据。


开发者关键考虑事项

  • 错误处理: 始终为套接字和流附加 'error' 监听器,以避免未捕获的异常。
  • 优雅关闭: 在传输完成或收到终止信号时,干净地关闭套接字和流(socket.end()stream.close())。
  • 安全性: 使用 TLS(tls 模块)进行加密传输,尤其是在不受信任的网络上。
  • 可扩展性: 对于大量并发上传,考虑限制并发连接数或使用队列系统。
  • 文件完整性: 验证传输的文件(例如校验和),确保在传输过程中未发生损坏。

错误处理

  • 健壮的错误处理 – 如果文件不存在怎么办?如果网络在传输中途掉线怎么办?
    始终将操作包装在 try…catch 块中,并处理流错误。

安全

  • 这个纯 TCP 实现会发送 unencrypted(未加密)的数据。网络上的任何人都可以拦截数据包。
    在生产环境中,请使用 TLS/SSL(通过 Node 的 tls 模块)或更高级的协议,如 HTTPSSFTP

文件完整性

  • 考虑添加校验和(MD5,SHA‑256),以验证接收的文件与原始文件匹配。
    TCP 可以防止损坏,但应用层验证提供了另一层安全保障。

可扩展性

  • clientsList 数组会随连接数量增长。
    对于高流量服务器,实施连接池和清理逻辑以防止内存泄漏。

协议演进

  • 这一本质阐释了 HTTPFTPWebSocket 等协议在底层的工作方式。
    例如,HTTP 只是在 TCP 上传输结构化的文本消息。
    理解原始套接字有助于调试和优化更高级别的协议。

结论

通过 TCP 传输文件可能看起来很复杂,但正如你所看到的,Node.js 使用 net 模块和流让这变得异常简单。你现在拥有一个可工作的客户端‑服务器系统,能够可靠地在网络间移动数据——这正是你日常使用的应用程序所采用的基本方法。

下一步

  • 尝试代码 – 尝试传输不同类型的文件。
  • 添加进度指示器
  • 实现断点续传功能,用于中断的传输。

每项增强都会加深您对网络编程和流媒体架构的理解。掌握 TCP 和套接字编程的最佳方式是构建、破坏、再构建。从这些示例开始,您很快就能构建出健壮的实时网络应用程序。

Back to Blog

相关文章

阅读更多 »