TCP 与 UDP

发布时间 2023-12-25 10:41:55作者: archaique

TCP 与 UDP

TCP 头部

  • 目标和源端口
  • 序列号:自己的,在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
  • 确认应答号:下一次「期望」收到的 对方的 数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
  • 控制位:
    • ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
    • RST该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
    • SYN该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
    • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。

TCP 四元组可以唯一的确定一个连接,四元组包括如下:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

服务端通常固定在某个本地端口上监听,等待客户端的连接请求。

因此,客户端 IP 和端口是可变的,其理论值计算公式如下:

当然,服务端最大并发 TCP 连接数远不能达到理论上限,会受以下因素影响:

  • 文件描述符限制,每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。
  • 内存限制,每个 TCP 连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生 OOM。

 

UDP 头部

  • 目标和源端口:主要是告诉 UDP 协议应该把报文发给哪个进程。
  • 包长度:该字段保存了 UDP 首部的长度数据的长度之和。
  • 校验和:校验和是为了提供可靠的 UDP 首部和数据而设计,防止收到在网络传输中受损的 UDP 包。

为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?

  • 原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化(8字节)的,无需多一个字段去记录 UDP 的首部长度。

 

TCP UDP 区别

1. 连接

  • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
  • UDP 是不需要连接,即刻传输数据。

2. 服务对象

  • TCP 是一对一的两点服务,即一条连接只有两个端点。
  • UDP 支持一对一、一对多、多对多的交互通信

3. 可靠性

  • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达(超时重传)。
  • UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议,具体可以参见这篇文章:如何基于 UDP 协议实现可靠传输?(opens new window)

4. 拥塞控制、流量控制

  • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
  • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。

5. 首部开销

  • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
  • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。

6. 传输方式

  • TCP 是流式传输,面向字节流(byte-stream-oriented),没有边界但保证顺序和可靠。
  • UDP 是一个包一个包的发送(面向报文 message-oriented),是有边界的,但可能会丢包和乱序。

7. 分片不同

  • TCP 的数据大小如果大于 MSS 大小,则会在 本传输层进行分片目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
  • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层(上一层)进行分片目标主机收到后,在 IP 层组装完数据,接着再传给传输层。

 

TCP 和 UDP 可以使用同一个端口吗?

可以的。

传输层的「端口号」的作用,是为了区分 【同一个主机上】【同一传输层协议】【 不同应用程序】 的数据包。

传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。

当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。

 

为什么说 UDP 是面向报文的?

这个面向报文是针对操作系统来说的。

当用户消息通过 UDP 协议传输时,操作系统不会对消息进行拆分,也就是每个 UDP 报文就是一个用户消息的边界。

每次发送 UDP 时,用户数据部分可以无限长,IP 层进行分片,当收到时,也是 IP 层进行合并。

所以操作系统无感知,发出的是一个包含 message 所有信息的报文,收到的也可能是这个包含 message 所有消息的报文(可能会丢失和乱序)。

操作系统在收到 UDP 报文后,会将其插入到队列里,队列里的每一个元素就是一个 UDP 报文,这样当用户调用 recvfrom() 系统调用读数据的时候,就会从队列里取出一个数据,然后从内核里拷贝给用户缓冲区。

 

 

TCP 的面向字节流怎么理解?

当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文,也就是一个完整的用户消息被拆分成多个 TCP 报文进行传输。

我们不能认为一个用户消息对应一个 TCP 报文,正因为这样,所以 TCP 是面向字节流的协议。

粘包问题:两个消息的某个部分内容被分到同一个 TCP 报文时,就是我们常说的 TCP 粘包问题,这时接收方不知道消息的边界的话,是无法读出有效的消息。

 

TCP 如何解决 粘包?

  • 固定长度的消息:比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息。不灵活,实际很少用
  • 特殊字符作为边界:例如 HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界。如果刚好消息内容里有这个特殊字符,我们要对这个字符转义,避免被接收方当作消息的边界点而解析到无效的数据。
  • 自定义消息结构:可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。

HTTP 以回车换行等特殊字符作为边界

 自定义消息结构

struct { 
    u_int32_t message_length; 
    char message_data[]; 
} message;