LinuxUDP协议与TCP协议
【Linux】UDP协议与TCP协议
一、端口号
(一)端口号划分
1~1023:知名端口号,HTTP、FTP与SSH等广为使用的应用层协议,其端口号是固定的。
- ssh服务器:22号端口
- ftp服务器:21号端口
- telnet服务器:23号端口
- http服务器:80号端口
- https服务器:443号端口
执行一下命令可以查看端口号:
cat /etc/services
102465535:操作系统分配的端口号,客户端程序的端口号。
**平时在使用时要避免使用11023号端口号。**
(二)端口号相关概念
端口号、协议与相应的进程实际是一体的,通过端口号就可以确定交付哪个协议,也可以确定交付给上层进程。 因此从端口号的角度看,端口号对进程是一一对应的关系,而进程是可以绑定多个端口号 。
一般情况下,一个进程可以绑定多个端口号,但一个端口号只能被一个进程绑定。
二、相关指令
(一)pidof
常用于查看进程 id
pidof process | xargs kill -9 //xargs用于将标准输入添加至命令行末尾
// kill -9 pid
(二)netstat
常用于查看网络状态
n 以数字的形式显式(例如端口号)
l 仅列出有在 Listen (监听) 的服务状态
p 显示建立相关链接的程序名
t (tcp)仅显示tcp相关选项
u (udp)仅显示udp相关选项
a (all)显示所有选项,默认不显示LISTEN相关
三、UDP协议
(一)UDP协议格式
16位端口号用于用于标识发送进程,并在接收方回送数据时确定源主机的目标进程;而16位目的端口号用于确定目标主机上的目标进程, 16位UDP长度是指得是整个UDP报文的长度(UDP首部+UDP数据 ),16位UDP检验和用于检验整个UDP报文是否出现错误(翻转),如果检验和检验出错,则丢弃整个报文。
struct udphdr {
uint16_t source; // 源端口号(16位)
uint16_t dest; // 目的端口号(16位)
uint16_t len; // UDP报文总长度(16位)
uint16_t check; // 校验和(16位)
};
(二)UDP如何进行封装、解包和分用
当应用层将数据向下交付给传输层, 将数据前添加UDP首部结构体再填写相应的内容即可完成封装 。
在自定义协议章节提到了协议通过 定长、特殊字符或自描述 的方式来确认完整报文。
UDP协议中首部字段长度为固定的8字节,而通过首部的UDP长度字段可以获取整个UDP数据报长度,从而获得UDP数据报数据部分的长度,从而完成获取一个完整报文。
UDP首部含有16位的目的端口号字段,通过该字段可以讲数据部分向上交付给进程,而进程则可以通过绑定的socket描述符的形式监听特定端口,读取到数据内容交给应用层具体的协议进行再处理。
(三)UDP的特点
- 无连接:了解接收方的IP地址和端口号即可直接进行传输,无需建立连接;
- 不可靠:没有确认机制,没有重传机制。如果因为网络问题导致数据报无法到达目的主机,UDP协议层也不会向应用层返回任何错误信息;
- 面向数据报:不能灵活的控制读写数据的次数与数量。 站在传输层的角度 ,当发送方每发送一次UDP报文,接收方都需要接收一次报文,面向数据报就表明无论是发送还是接收每次都必须是一个完整的数据报,并且不能对数据报进行任何拆分,例如发送方一次发送了100个字节的数据,接收方就必须一次读取100个字节,
UDP没有真正意义上的发送缓冲区 ,当调用 send() 进行发送时会直接交给内核,由内核将数据传递给网络层协议进行后续的传输动作。
UDP是具有接收缓冲区 ,因为UDP协议是不可靠的,因此接收缓冲区不保证接收的UDP报文的顺序和发送UDP报文的数据;如果缓冲区满了后续的UDP报文会直接被丢弃。
需要注意的是,UDP首部中有一个16位的UDP长度,这也说明了一个UDP报文的最大可传输长度为2^16即64KB(包含首部),如果要传输大于64KB的数据,需要在应用层手动分包,多次发送。
四、TCP协议
(一)TCP协议格式
- 16位源端口号 :用于用于标识发送进程,并在接收方回送数据时确定源主机的目标进程;
- 16位目的端口号 :用于确定目标主机上的目标进程;
- 32位序号/32位确认号 :用于可靠传输的确认机制;
- 4位首部长度 :表示TCP头部长度,单位4字节;
- 6位保留位 :保留未来使用;
- 六位控制标志 :
- URG :紧急数据标识;
- ACK :确认号有效标识;
- PSH :要求接收方立即推送数据至应用层;
- RST :强制重置异常连接;
- SYN :建立连接时的同步信号;
- FIN :请求终止连接
- 16位窗口大小 :接收方课接收的字节数,用于流量控制;
- 16位校验和 :覆盖TCP首部和数据部分,验证传输完整性;
- 16位紧急指针 :仅当URG=1时有效,标识紧急数据结束位置,默认数据为1个字节;
- 选项 :支持扩展功能,如最大报文段(MSS)、窗口扩大因子、时间戳等,长度最多40字节
TCP的报头也是一个结构体对象:
struct tcphdr {
__be16 source; // 源端口号(16位)
__be16 dest; // 目的端口号(16位)
__be32 seq; // 序列号(32位)
__be32 ack_seq; // 确认号(32位)
__u16 res1:4; // 保留位(4位)
__u16 doff:4; // 数据偏移(头部长度,以4字节为单位)
__u16 fin:1; // FIN标志(结束连接)
__u16 syn:1; // SYN标志(建立连接)
__u16 rst:1; // RST标志(重置连接)
__u16 psh:1; // PSH标志(立即推送数据)
__u16 ack:1; // ACK标志(确认字段有效)
__u16 urg:1; // URG标志(紧急指针有效)
__be16 window; // 窗口大小(16位)
__sum16 check; // 校验和(16位)
__be16 urg_ptr; // 紧急指针(16位,仅当URG=1时有效)
};
(二)TCP协议的封装、解包和分用
和UDP协议类似,当上层应用层将数据交给传输层TCP协议后,TCP协议将结构体添加至数据前并填写相应的内容即完成封装。
不同于UDP首部长度固定,TCP报文携带选项字段,因此TCP报文首部长度是不确定的。当收到TCP报文时,会首先读取报文的前20字节,其中包含了TCP首部长度字段,通过该字段可以得到TCP报文中数据的偏移量,从而可以获取TCP报文的选项字段的长度且数据部分的起始位置。
TCP报文首部包含了目的端口号,通过端口号即可向上交付数据给指定的进程,而进程可以通过绑定的socket描述符的形式监听特定端口,读取到数据内容交给应用层具体的协议进行再处理。
除此之外,因为网络传输距离长,路上会经过多个设备节点,因此路上可能会存在丢包、乱序、检验错误或报文重复等问题。TCP引入了大量的机制用于确保可靠性: 确认应答机制、超时重传机制、连接管理机制与流量控制等等 。
(三)确认应答机制
当客户端发送数据给服务器后,客户端本身是不知道服务器是否收到了发送的数据,只有服务器给客户端发送了确认报文,客户端才能确定服务器收到了发送数据 。
确认应答机制就是基于上述内容形成的,主要用于发送方确认数据到达了接受方。
TCP协议将每个字节数据都进行了编号,即序列号。 每个ACK确认都带有对应的确认序列号,其意义是在该序号前的所有数据都已收到,下次发送数据的序号应为确认序列号。
(四)超时重传与快重传机制
因为网络传输十分复杂,可能会出现丢包的情况,而TCP协议则采用超时重传与快重传的策略解决丢包的问题。
当发送方发送的数据丢失后,因超时重传机制,发送方等待特定的时间间隔后会重新将数据进行发送 。
当接收方发送的确认应带丢失后,因超时重传机制,发送方因没有接收到接收方的确认应答,等待特定的时间间隔后会重新将数据进行发送,当接收方再次接收到数据后,通过序列号发现数据重复会把数据直接丢弃掉同时再次向发送方发送确认应带。当发送方连续接收到三个重复确认应答后会立刻重传数据,无需等待超时重传。
那么超时重传机制中特定的时间间隔如何确定呢?
实际该时间间隔是动态的,理想情况下是找到一个最小时间,保证确认应答一定能在该时间间隔内返回。如果该时间过长,会影响整体的重传效率,如果该时间太短,会导致频繁发送重复的包。
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。
Linux中超时重传是以500ms为一个单位进行控制的,每次判定超时重发的时间都是500ms的整数倍。若重发一次没有收到应答,则等待2500ms时间后重传,若仍等待不到应答,则等待4500ms的时间进行重传,依次类推以指数的形式地递增。累次到一定的重传次数,TCP协议会认为网络或对端主机出现异常,强制关闭连接。
(五)连接管理机制
三次握手是建立TCP连接的基础,基于连接存在一个维护TCP连接的结构体,该结构体主要是用于维护超时重传、按序到达、流量控制与连接状态等保证可靠性的数据结构。
因为UDP是不需要建立连接的,因此也不存在维护连接的数据结构,故而无法实现以上的策略,因此UDP协议是不可靠的。
以上整个连接建立和释放的全过程,其中:
- CLOSED:表示TCP连接未建立或终止;
- LISTEN:表示正在监听等待连接请求;
- STN_SENT:表示TCP连接请求发送送给远程主机,等待确认;
- STN_REVD:表示TCP连接已被服务器接收并已恢复确认连接请求;
- ESTABLISHED:表明连接已建立,可以进行数据传输;
- FIN_WAIT_1:表明主动关闭方已发送关闭连接请求并等待确认,此后不再发送数据;
- CLOSE_WAIT:被动关闭方已接收对端的关闭请求并发送确认报文,等待应用层关闭连接;
- FIN_WAIT_2:主动关闭方接收到确认报文,等待被动关闭方的关闭连接请求;
- LAST_ACK:被动关闭方发送关闭连接请求,等待确认;
- TIME_WAIT:主动关闭方收到关闭连接请求并发送确认报文,正在等待所有相关网络报文被清除,通常经过特定时间后进入CLOSED状态;
1、三次握手
(1)概念
当客户端向服务器发送连接请求时进入SYN_SENT状态,它代表了已经发送了连接请求并等待对方发送确认报文的状态。在TCP三次握手建立连接的过程中,客户端发送建立连接报文(SYN)后,TCP状态会变为SYN_SENT。在该状态下,客户端等待服务器的确认报文,若长时间未收到相应,则会触发超时重传。
当服务器向客户端发送SYN-ACK响应后,服务器连接状态会由LISTEN进入SYN_RCVD状态,表明已接收客户端的连接请求,并向客户端发送SYN-ACK报文。在这个状态下,服务器会等待客户端的ACK包,以完成TCP三次握手连接的过程。如果在一段时间内没有收到客户端的ACK包,服务器会发起超时重传SYN+ACK报文。
三次握手是建立连接的基础,其设计目标是最大化连接建立的可靠性,但是三次握手并不保证连接一定建立成功的 ,是有可能失败的,例如服务器已经到达建立连接的上限无法继续建立连接。
(2)为什么是三次握手?
TCP协议采用三次握手是针对 可靠性、效率和资源管理 多方面的综合考量。
首先TCP连接是全双工的:
第一次握手(SYN) :验证客户端的发送能力,服务器验证接收能力;
第二次握手(SYN+ACK) :验证服务器发送能力,客户端收到应答后能够确认从客户端到服务器的信道是可达的;
第三次握手(ACK) :最终确认双方的收发能力均正常,服务器收到应答后能够确认从服务器到客户端的信道是可达的。
a、为什么不是一次握手?
如果仅仅通过一次握手便建立连接,首先TCP连接是全双工通信,当客户端发起连接建立请求后,客户端无法得知该请求报文是否到达了服务器,可能出现客户端单方面认为连接已建立,但服务器未收到请求报文而认为连接没建立,从而导致连接异常。
若通过一次握手便建立连接,若客户端因为网络延迟重复发送SYN报文,而服务器无法区分新旧连接,可能会建立无效链接。
b、为什么不是两次握手?
如果仅通过两次握手建立连接,首先TCP连接是全双工通信,客户端发送SYN报文后收到服务器的应答报文,但对于服务器而言无法得知应答报文是否到达了客户端,无法确认连接是否已经建立。
若服务器发送应答报文后直接认为连接建立完成,因为连接的维护是需要消耗系统资源的,服务器在收到SYN报文并发送应答报文后认为连接已经建立,为此立即分配资源,如此易容到SYN洪水攻击导致服务器资源长期被无效链接占用。
c、为什么不是四次或五次握手?
实际三次握手能够完成连接建立成功,那么四次或多次握手一定也能完成连接的建立。额外的握手实际就是网络传输通信,是会消耗网络资源的,而TCP协议设计追求可靠和效率,因此撒此握手已能满足需求,无需冗余步骤。
2、四次挥手
连接的断开是双方的事,因为TCP连接是全双工的,所以需要双方都关闭连接后,TCP连接才是被关闭。
第一次挥手 :客户端(主动关闭方)调用 close() 发送 FIN 报文,表示终止数据发送,客户端则进入 FIN_WAIT_1 状态,等待对方确认;
第二次挥手 :服务器接收客户端的 FIN 报文并发送ACK确认,同时进入 CLOSE_WAIT 状态;客户端收到服务器(被动关闭方)返回的ACK报文,客户端确认FIN报文已被接收。客户端进入 FIN_WAIT_2 状态,等待服务器发送FIN报文;
第三次挥手 :服务器完成数据发送后,调用 close() 发送 FIN 报文,服务器从CLOSE_WAIT 进入 LAST_ACK 状态,等待客户端的最终 ACK 确认;客户端接收到服务器的FIN报文,发送ACK确认。客户端进入 TIME_WAIT 状态,等待最后一个ACK到达服务器;
第四次挥手 :服务器收到客户端的ACK报文,服务器由 LAST_ACK 进入 CLOSED 状态,释放所有资源;客户端等待特定时间后并释放所有资源,关闭连接进入 CLOSED 状态。
当主动关闭方进入 TIME_WAIT 状态后需要等待一段时间后再关闭连接。这段时间是 2 * MSL。
MSL:一个TCP报文段在网络中的最长生存时间,超过这个时间,报文段将被丢弃。
为什么需要等待2 * MSL后再关闭连接呢?
客户端(主动关闭方)在收到 FIN 报文后会发送 ACK 确认报文,但无法确认 ACK 报文是否到达了服务器,如果该 ACK 报文丢失,服务器会因为超时重传 FIN 报文,当客户端再次收到 FIN 报文即可确认服务器未收到 ACK 报文并重新发送 ACK 报文,从而避免了因为 ACK 报文丢失导致服务器长时间处于LAST_ACK 状态使资源泄漏,因此主动关闭方在收到 FIN 报文后需要等待 2 * MSL 时间后再关闭连接。
除此之外,网络中可能还存在延迟或重复的旧连接报文(FIN)。等待 2 * MSL 时间可确保这些残留的报文会被主动关闭方处理,从而避免与新连接混淆。 例如:若客户端在发送 ACK 报文后立即关闭连接,之后再使用相同端口向服务器建立连接,而残留在网络中的 FIN 报文会被认为是新连接的管理报文,从而导致连接混乱。
(六)滑动窗口
1、基础概念
上文提到,TCP协议具有确认应答机制,对于发送端发送的每一个数据,接收端收到数据后都需要回送一个 ACK 确认报文,发送端收到 ACK 确认报文后再继续发送下一段数据。虽然这样保证了数据在网络传输中的可靠性,但如此效率太低,尤其是数据往返时间较长时性能较差。因此便引入了 滑动窗口机制 。
滑动窗口是一种动态调整处理范围的机制,其核心思想是通过定义可变大小的“窗口”,在数据流或序列上滑动,实现高效的数据管理与处理。 滑动窗口本质是发送缓冲区的一部分,发送缓冲区中已发送未应答的部分称为滑动窗口。
滑动窗口的移动本质是数据下标的移动。 在发送数据前,发送方会根据算法生成一个 初始化序列ISN ,之后将该初始序列号与缓冲区下标相加组成发送数据序号,当接收方回送确认报文后,发送方可根据 ACK 报文中的确认序号字段快速定位缓冲区的下标,从而完成下标 win_start 的移动,而 win_end 的值取决于回送报文中的窗口大小。 win_end = win_start + 窗口大小(实际的窗口大小是流量控制和拥塞控制协同作用决定的) 。
2、相关概念
滑动窗口不仅仅是对确认应答机制的优化,其也是流量控制与拥塞控制协同作用的体现。滑动窗口的大小是根据通信双方和网络状态共同决定的,其大小是以此动态调整的而不是一成不变。
那滑动窗口一定是向右滑动吗?在传输过程中如果并不是按序收到确认应答,而是收到窗口内中间或尾部数据的确认报文,应该如何处理呢?
滑动窗口可能会向右滑动或停止不停(例如接收方缓冲区已满), 但一定不会向左移动 ,因为左边的数据是已经发送且确认应答过的。 上文提到 ACK 报文中确认序号是表明在此序号前的所有数据都已被接收,也就是累计确认 。因此当接收到窗口中间或尾部数据的确认报文,直接进行窗口的滑动即可。
需要注意的是,为了方便叙述图中的缓冲区以数组的形式表现,但 实际上的缓冲区是环形的 。除此之外,虽然窗口大小取决于通信双方和网络状态,但 发送端并不是把窗口内的所有数据一次性全部进行发送 ,因为TCP会将窗口数据分割为适合下层发送的报文段(下层不支持一次发送大量数据)。
(七)流量控制
不同于UDP协议, TCP协议的双端都是具有缓冲区的 ,因此接收方处理数据的是有限的,当发送方数据发送速度过快,导致接收方的缓冲区被打满,这个时候如果发送方仍然持续发送数据,接收方会因为来不及接收新数据导致会直接将报文丢弃,继而会引起丢包重传等等一系列连锁反应消耗网络资源。
因此TCP报文中存在16位的窗口大小,除此之外TCP首部中包含一个窗口扩大的选项,通过设置窗口扩大因子M使得窗口字段的值左移M位 。 通过该窗口大小字段可用于双方交换缓冲区的大小,继而决定发送端发送数据的速度。该机制就叫做流量控制。 在三次握手阶段,除了建立连接外,连接双方也要完成协议初始窗口参数等等,为流量控制提供基础。
TCP 流量控制的本质是 接收方通过通告窗口大小控制发送方的发送速率。 在TCP连接建立完成数据交换中,双方都会动态调整窗口大小向对端发送报文以表明自己的接收数据能力,依此达到高效的数据通信。 流量控制不仅仅只会让发送方减缓发送数据的速度,也有可能加快发送方发送数据的速度。
(八)拥塞控制
上文提到 窗口大小不仅仅取决于通信双方(流量控制),也取决于网络状态(拥塞控制) 。
网络中包含了多台主机,如果大量主机同时进行TCP通信且发送大量的数据,会导致网络负载增大而网络拥塞,从而会出现大量的丢包行为,如果此时各个主机对大量丢失的报文进行超时重传,会更加增大网络的负载,极大地消耗网络资源。
因此引入了拥塞控制,如果说流量控制是站在通信双方的角度节省资源且提高效率,那么拥塞控制则是站在整个网络的角度提高网络传输的效率。
实际发送端发送数据的窗口大小还需要考虑 拥塞窗口,该窗口大小根据网络状态动态变化。
在刚开始数据发送时,拥塞窗口大小为1,在后续数据传输中每收到一个 AKC 应答,拥塞窗口大小+1。 发送方实际发送数据的窗口大小 = min{ 拥塞窗口, 接收方反馈的窗口大小 } 。少量的丢包可以通过超时重传即可解决,而大量的丢包就需要拥塞控制了。
- 慢开始 :首先需要发送少量数据探测网络状态,之后每收到一个ACK报文拥塞窗口 + 1,也就是拥塞窗口以指数增长的形式增长直到到达阈值;
- 拥塞避免 :避免发送速度过快导致网络拥塞,当拥塞窗口大小等于阈值后进入拥塞避免状态,此后拥塞窗口将以线性增长的形式增长直到发生网络拥塞;
- 快重传和快恢复 :当网络拥塞后收到三个重复的ACK报文,立刻重传丢失的报文且拥塞窗口减为当前拥塞窗口的一半且更新ssthresh的值,此后拥塞窗口将以线性增长的形式增长直到再次发生网络拥塞。
当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降; 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。
(九)延迟应答
上文提到 ACK报文确认应答是采用累计确认的方式 ,如果每收到发送方的数据报文就立即进行ACK确认会消耗一定的网络资源,而等待一定时间再进行ACK确认即可以达到确认的目的,还能节省资源。
除此之外如果接收方每次收到数据就立刻进行ACK应答可能会使返回的窗口较小,假设接收方缓冲区为1M,如果接收到了500K的数据立刻进行ACK应答,那么返回的窗口大小就是500M,但如果接收方处理数据很快,等待一段时间500K的数据被消费了,那么此时 ACK 报文返回的窗口大小就为1M,从而增加发送端发送数据的速度。
实际并不是所有的包都可以进行延迟应答 。
- 数量限制: 每隔N个包就应答一次;
- 时间限制: 超过最大延迟时间就应答一次;
具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms;
(十)捎带应答
捎带应答 是TCP协议中用于提升通信效率的一种机制。其核心思想是 将数据段的确认应答与反向传输的应用层数据合并发送 ,减少网络中单独的ACK报文数量,从而降低通信成本并提高吞吐量
例如一端给另一端发消息,另一端为了减少报文的传输,会把ACK标志位置1并且带上32位确认序号的同时,再加上有效载荷一并发送给对端。(携带有效载荷的应答,这也是TCP中比较常见的通信方式,能提高消息的传输效率)
(十一)面向数据报与面向字节流
UDP协议是面向数据报的,每个数据包含完整的边界且不可分割,发送端如果 send() 一个大小为100字节,接收方必须调用一次 resv() 完整接收,无法拆分读取。
TCP协议是面向字节流的,发送方和接收方的操作无需一一对应。例如发送方发送100字节的数据,接收方可分10读取,每次读取10个字节。当应用层进行读取时,TCP不会对数据报文进行解析,其会直接将指定的数据报文直接交付给上层,TCP只负责数据传输的可靠性,但应用层需自行解析报文。
因为TCP只负责数据传输的可靠性, 因此产生了粘包的问题,解决粘包的本质时明确报文之间的边界。
在自定义协议章节提及了三种能读取完整报文的设计:
定长 :可以固定数据包的大小,每次从缓冲区读取同样大小的数据即可完成报文分割;
特殊字符 :可以采用在每个报文之间插入特殊字符,读取时以特殊字符作为报文分隔符;
自描述 :可以在数据报头内包含一个总长度的字段,从而就得知完整报文的大小。
(十二)TCP连接面临异常的情况
在数据通信中,连接并不是每次都正常关闭的,可能存在一些特殊场景。
当TCP协议中的服务器或客户端非正常关闭时(程序崩溃或机器重启),操作系统会先退出进程,再发起四次挥手关闭连接。但在一些极端场景(机器断电或突然断网),发生异常的一段是来不及自动挥手关闭连接的,但另一端会定期发起询问,在多次无应答后会断开连接。
五、UDP/TCP协议总结
TCP协议是基于效率和可靠性的综合考量。例如校验和、序列号(按序到达)、确认应答、超时重传、连接管理、流量控制、拥塞控制都是为了保证可靠性,而滑动窗口、快速重传、延迟应答、捎带应答、流量控制、拥塞控制则是为了保证效率。
UDP用于对高速传输和实时性要求较高的通信领域, 例如, 直播、早期的QQ, 视频传输等. 另外UDP可以用于广播。
TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景。