跨网段主机的文件传输过程

引言

在计算机网络中,跨网段的主机之间进行文件传输,本质上是一个跨越不同网络层、经过路由转发、最终在应用层完成数据交换的过程。本文将从网络分层的角度,梳理跨网段文件传输的完整流程。

封装

下图展示了跨网段文件传输从应用层封装到数据链路层封装,再经路由器转发、逐层解封装的完整流程:

flowchart TD A[应用层封装] --> B[传输层加 TCP/UDP 头] B --> C[网络层加 IP 头] C --> D[数据链路层加帧头帧尾] D --> E[路由器逐跳转发] E --> F[逐层解封装] F --> G[应用层还原文件]

从图中可以看出,数据在源主机侧逐层向下封装,经过路由器逐跳转发,再在目标主机侧逐层向上解封装,最终在应用层还原出原始文件。

跨网段主机的文件传输,是应用层数据经过逐层封装、路由转发、逐层解封装,最终在目标主机还原的完整过程。理解这一过程,有助于排查网络故障和优化传输性能。

不同的协议层对数据包有不同的称谓,在 传输层 叫做 段(segment),在 网络层 叫做 数据报(datagram),在 链路层 叫做 帧(frame)。

应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部 (header),称为封装(Encapsulation)。

首部信息中包含了一些类似于首部有多长,载荷 (payload)有多长,上层协议是什么等信息。

数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的"上层协议字段"将数据交给对应的上层协议处理。

网络编程

在了解网络通信传输的过程

那以编码方式实现网络通信,就是网络编程。

在进行一次网络数据传输时

发送端 与 接收端

发送端

数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。

接收端

数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。

请求 与 响应

一般地 网络资源获取时 涉及到两次⽹络数据传输 即 先请求数据的发送 再响应数据的发送

客⼾端 与 服务端

服务端

在常⻅的⽹络数据传输场景下,把提供服务的⼀⽅进程,称为服务端,可以提供对外服务

客⼾端

获取服务的⼀⽅进程,称为客⼾端

Socket套接字

定义

Socket 套接字是由系统提供、用于 网络通信 的技术,是基于 TCP/IP 协议 的网络通信基本操作单元。基于 Socket 套接字的网络程序开发,就是 网络编程。

分类

字主要针对传输层协议划分为

原始套接字

原始套接字⽤于⾃定义传输层协议,⽤于读写内核没有处理的IP协议数据。

流套接字

使⽤传输层 TCP 协议

• 有连接

• 可靠传输

• ⾯向字节流

• 有接收缓冲区,也有发送缓冲区

• ⼤⼩不限

数据报套接字

使⽤ 传输层UDP协议

• ⽆连接

• 不可靠传输

• ⾯向数据报

• 有接收缓冲区,⽆发送缓冲区

• ⼤⼩受限:⼀次最多传输64k

流套接字与数据报套接字对比

对比维度 流套接字 数据报套接字
传输层协议 TCP UDP
连接方式 有连接 无连接
可靠性 可靠传输 不可靠传输
数据形式 面向字节流 面向数据报
缓冲区 有接收缓冲区,也有发送缓冲区 有接收缓冲区,无发送缓冲区
数据大小 大小不限 大小受限,一次最多传输 64k

简单来说,流套接字 基于 TCP ,提供可靠、有序、面向字节流的传输,适合对数据完整性要求高的场景;数据报套接字 基于UDP,提供无连接、不可靠的传输,但开销更小、实时性更好,适合对延迟敏感或允许少量丢包的场景。

TCP

TCP 常见方法签名

基于 TCP 的流套接字编程,服务端与客户端常用方法签名如下:

java 复制代码
// 服务端
ServerSocket serverSocket = new ServerSocket(int port);
Socket socket = serverSocket.accept();
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
socket.close();
serverSocket.close();

// 客户端
Socket socket = new Socket(String host, int port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
socket.close();
ServerSocket

ServerSocket 是 Java 中用于 TCP 服务端的 套接字类 ,负责在指定端口上监听客户端的连接请求。它本身不直接传输数据,而是通过 accept() 方法等待并接受客户端连接,返回一个用于实际读写数据的 Socket 对象。简单来说,ServerSocket 就是服务端"开门迎客"的入口。

|-----------------------------|------------------------------------------------------------|
| ⽅法签名 | ⽅法说明 |
| ServerSocket(int port ) | 创建⼀个服务端流套接字 Socket,并绑定到指定端⼝ |
| Socket accept() | 开始监听并接受客户端的连接请求,返回一个与该客户端通信的 Socket 对象;该方法会阻塞,直到有客户端连接到达 |
| void close() | 关闭此套接字 |

Socket

Socket 是 Java 中用于 TCP 客户端的 套接字类 ,负责与指定 IP 和端口上的服务端建立连接。它直接承载数据的读写,连接建立后,通过 getOutputStream() 发送数据、getInputStream() 接收数据。简单来说,Socket 就是客户端与服务端之间"通话"的通道。

|--------------------------------------|---------------------------------------------|
| 方法签名 | 方法说明 |
| Socket(String host, int port) | 创建一个客户端流套接字 Socket,并与对应 IP 的主机上、对应端口的进程建立连接 |
| InetAddress getInetAddress() | 返回此 Socket 连接到的远程主机的 IP 地址 |
| InputStream getInputStream() | 返回此 Socket 的输入流,用于读取服务端发来的数据 |
| OutputStream getOutputStream() | 返回此 Socket 的输出流,用于向服务端发送数据 |
| void close() | 关闭此 Socket,释放占用的连接资源 |

UDP

UDP 常见方法签名

基于 UDP 的数据报套接字编程,发送端与接收端常用方法签名如下:

java 复制代码
// 发送端
DatagramSocket socket = new DatagramSocket();
byte[] data = "hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("host"), port);
socket.send(packet);
socket.close();

// 接收端
DatagramSocket socket = new DatagramSocket(int port);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
String msg = new String(packet.getData(), 0, packet.getLength());
socket.close();

可以看出,TCP 编程需要先建立连接(accept/connect),再通过输入输出流读写数据;UDP 编程无需建立连接,直接通过 DatagramPacket 封装数据报进行发送和接收。

一次完整的 UDP 数据报发送与接收流程如下:

  1. 发送端准备数据:将要发送的内容转换为字节数组,并指定目标主机的 IP 地址和端口号。
  2. 发送端封装数据报 :通过 DatagramPacket 把字节数组、长度、目标 IP 和端口封装成一个数据报包裹。
  3. 发送端发送 :调用 DatagramSocket.send(packet),将数据报交给系统,由 UDP 协议直接发往目标主机,无需事先建立连接。
  4. 接收端准备接收 :接收端先创建绑定到指定端口的 DatagramSocket,并准备好一个空的 DatagramPacket(指定缓冲区)用于接收数据。
  5. 接收端接收 :调用 DatagramSocket.receive(packet),系统把收到的数据填入这个空包裹中,该方法会阻塞直到数据到达。
  6. 接收端解析数据:从包裹中取出字节数组,按需还原为字符串或其他格式,完成一次数据报传输。
DatagramSocket

DatagramSocket.receive(packet) 是 UDP 接收端的 核心方法,它的作用可以概括为三点:

  • 阻塞等待数据:调用后当前线程会一直阻塞,直到有数据报到达绑定的端口,才会继续向下执行。
  • 填充数据报包裹 :系统把收到的数据写入传入的 DatagramPacket 缓冲区中,同时更新该包裹的长度信息,便于后续读取。
  • 完成一次接收 :方法返回后,即可通过 packet.getData()packet.getLength() 取出实际收到的字节数据。

send 负责把数据 "发出去", receive 负责把数据" 接进来"。接收端必须先准备好一个空的 DatagramPacket 作为容器,再调用 receive 等待数据填入,整个过程无需与发送端建立连接。

整个过程无需连接,发送端只管发出,接收端只管接收,双方通过 IP 和端口完成寻址,因此开销小、实时性好,但不保证数据一定到达、也不保证到达顺序。

简单来说,DatagramPacket 就是 UDP 传输中用来装数据的一个"数据报包裹"。

发送时,把要发的字节数组、长度、目标 IP 和端口一起装进包裹里,交给 DatagramSocket 发出去;接收时,先准备好一个空包裹(指定缓冲区),再通过 DatagramSocket 接收,数据就会被装进这个包裹里,最后从包裹中取出字节数组还原成字符串。

TCP 就像打电话:先拨号建立连接,接通后双方再一句一句地通话,说完挂断;

UDP就像发短信:不用提前打招呼,直接把消息发出去就行,对方能不能收到、按什么顺序收到,都不保证。所以 TCP 更可靠但稍慢,UDP更快但可能丢消息。

回显服务器

回显服务器(Echo Server)是一种最简单的网络服务程序:客户端把数据发送给服务器,服务器收到后原封不动地把数据返回给客户端,因此称为"回显"。

回显服务器常用于验证网络通信链路是否正常、测试 Socket 编程的基本流程

其核心工作流程如下:

  1. 服务端启动 :创建 ServerSocket 并绑定端口,调用 accept() 等待客户端连接。
  2. 客户端连接 :客户端创建 Socket 连接服务端,建立 TCP 连接。
  3. 发送数据:客户端通过输出流把数据发送给服务端。
  4. 回显数据:服务端从输入流读取数据,再通过输出流原样写回客户端。
  5. 接收数据:客户端从输入流读取服务端返回的数据,完成一次回显。

回显服务器就是"你发什么,我回什么",它不处理业务逻辑 ,只负责验证数据能否在两端之间正确往返。

简单来说,回显服务器就像一个"回声":客户端喊一句,服务器原样喊回来。它本身不做任何加工,只是把收到的数据原封不动地返回,用来确认网络通路是否畅通、两端能否正常收发数据。

下面给出一段最简单的回显服务器代码,帮助理解它的工作方式:

java 复制代码
// 服务端:收到什么就原样返回什么
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();          // 等待客户端连接
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();

byte[] buffer = new byte[1024];
int len = in.read(buffer);                      // 读取客户端发来的数据
out.write(buffer, 0, len);                      // 原样写回客户端

socket.close();
serverSocket.close();

这段代码的核心只有两步:先用 in.read(buffer) 读取客户端发来的数据,再用 out.write(buffer, 0, len) 把读到的内容原样写回,从而实现"你发什么,我回什么"。

一个服务器程序的典型处理流程通常包含以下三个步骤:

  1. 读取请求并解析:接收客户端发来的请求数据,并解析出其中的方法、路径、参数等信息。
  2. 根据请求计算响应 :依据解析得到的请求内容,执行相应的业务逻辑,生成对应的响应数据。(但在回显服务器从而省去了这一步)
  3. 返回响应:将计算得到的响应数据发送回客户端,完成一次请求-响应交互。
相关推荐
Shadow(⊙o⊙)1 小时前
OTOL设计模式 One Thread One Loop
服务器·网络·设计模式
xiaoye-duck1 小时前
《Linux 网络编程》深入理解 IP 协议(一):网络层基础与 IP 协议头详解
linux·网络·ip
Wang's Blog1 小时前
Java 接入Redis: Redis简介与NoSQL定位
java·服务器·redis
程序猿乐锅1 小时前
【黑马点评 | 第四篇】Redis缓存雪崩
java·数据库·spring boot·redis·缓存
小尹哥-程序员1 小时前
第4集:让AI学会看文档:Spring AI RAG入门实战
java·人工智能·spring
ao-weilai1 小时前
Linux网络编程:Linux Socket TCP
linux·服务器·网络
是个西兰花1 小时前
UDP套接字编程
运维·服务器·网络
可乐配鸡翅1 小时前
day seven 猜数字小游戏
java
张小凡vip1 小时前
Java Web 设置 Session 超时时间:如何让 Session 永不失效
java·spring boot