UDP与TCP协议简介

本文主要介绍传输层的UDP与TCP协议。先阐述传输层基本概念和端口号划分,接着说明UDP是无连接、不可靠、面向数据报的协议,介绍其格式、缓冲区等。然后讲解TCP是面向连接、可靠传输、面向字节流的协议,涉及确认应答、超时重传等机制,还提及TCP连接管理、粘包问题及解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本节起,我们主要介绍关于传输层相关的两个著名协议,即 UDP 与 TCP 协议,那么就让我们开始从传输层说起。

传输层

基本概念

我们知道传输层位于网络层之上,网络层提供了主机之间的逻辑通道。那既然已经把一个数据包从一个主机发到另一个主机上面了,为什么还需要传输层呢?这是因为传输层提供了应用进程之间的端与端之间的数据传输功能。我们知道一个电脑可能有多个进程同时在使用网络连接,那么网络包达到主机之后,怎么区分自己属于那个进程?这就需要靠传输层的作用了。

端口号

端口号(Port)标识了一个主机上进行通信的不同的应用程序;(uint16_t)
在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看)。

netstat 命令是用来查看网络状态

  • n 拒绝显示别名,能显示数字的全部转化成数字
  • l 仅列出有在 Listen (监听) 的服務状态
  • p 显示建立相关链接的程序名
  • t (tcp)仅显示tcp相关选项
  • u (udp)仅显示udp相关选项
  • a (all)显示所有选项,默认不显示LISTEN相关

端口号划分

  • 0 - 1023: 熟知端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的.
  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.

常见的熟知端口号

  1. ssh服务器:22端口
  2. ftp服务器:21端口
  3. http服务器:80端口
  4. telnet服务器:23端口
  5. https服务器:443端口
  6. mysql服务器:3306端口

在Linux操作系统中使用命令cat /etc/services可以看到所有的知名端口。
了解了传输层的基本功能后,那么让我们再来看看传输层著名的协议。

UDP协议

基本描述

UDP协议是一个无连接、不可靠、面向数据报的一种协议。

  • 无连接:只知道对端的IP和端口号就可以发送,不需要实现建立连接。
  • 不可靠:没有确认机制, 没有重传机制。如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。
  • 面向数据报: 应用层交给UDP多长的报文, UDP原样发送既不会拆分,也不会合并。如果发送端调用一次sendto, 发送100个字节,那么接收端也必须调用对应的一次recvfrom, 接收100个 字节,而不能循环调用10次recvfrom, 每次接收10个字节。所以UDP不能够灵活的控制读写数据的次数和数量。

UDP协议格式

在这里插入图片描述

  • 16位数据包长度表示udp首部加udp数据的最大长度
  • 如果检验和出错,就会直接丢弃数据

UDP缓冲区

UDP的缓冲区:UDP存在接收缓冲区,但不存在发送缓冲区。

  1. UDP没有发送缓冲区,在调用sendto时会直接将数据交给内核,由内核将数据传给网络层协议进行后续的传输动作。为什么UDP不需要发送缓冲区?因为UDP不保证可靠性,它没有重传机制,当报文丢失时,UDP不需要重新发送,而TCP不同,他必须具备发送缓冲区,当报文丢失时,TCP必须保证重新发送,用户不会管,所以必须要具备发送缓冲区。
  2. UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报文的顺序和发送UDP报的顺序一致,如果缓冲区满了再到达的UDP数据报就会被丢弃。

常见基于UDP的应用层协议

  • NFS: 网络文件系统
  • TFTP: 简单文件传输协议
  • DHCP: 动态主机配置协议
  • BOOTP: 启动协议(用于无盘设备启动)
  • DNS: 域名解析协议

UDP注意事项

UDP是一种全双工通信协议。 UDP协议首部中有一个16位的大长度. 也就是说一个UDP能传输的报文长度是64K(包含UDP首部)。如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。

TCP协议

基本描述

TCP全称为 “传输控制协议(Transmission Control Protocol”),是一种面向连接、可靠传输、面向字节流的一种协议。

TCP协议格式

在这里插入图片描述

  • 源端口号/目的端口号:表示数据从哪个进程来,要到那个进程去
  • 32位序号:序号是可靠传输的关键因素。TCP将要传输的每个字节都进行了编号,序号是本报文段发送的数据组的第一个字节的编号,序号可以保证传输信息的有效性。比如:一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为401。
  • 32位确认序号:每一个ACK对应这一个确认号,它指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。
  • 4位首部长度(数据偏移): 表示该TCP头部有多少个32位bit(有多少个4字节),所以TCP头部大长度是15 * 4 =
    60。根据该部分可以将TCP报头和有效载荷分离。TCP报文默认大小为20个字节。
  • 6位标志位:
    URG:它为了标志紧急指针是否有效。
    ACK:标识确认号是否有效。
    PSH:提示接收端应用程序立即将接收缓冲区的数据拿走。
    RST:它是为了处理异常连接的, 告诉连接不一致的一方,我们的连接还没有建立好, 要求对方重新建立连接。我们把携带RST标识的称为复位报文段。
    SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段。
    FIN:通知对方, 本端要关闭连接了, 我们称携带FIN标识的为结束报文段。
  • 16位的紧急指针:按序到达是TCP协议保证可靠性的一种机制,但是也存在一些报文想优先被处理,这时就可以设置紧急指针,指向该报文即可,同时将紧急指针有效位置位1。
  • 16位窗口大小:如果发送方发送大量数据,接收方接收不过来,会导致大量数据丢失。然后接收方可以发送给发送发消息让发送方发慢一点,这是流量控制。接收方将自己接收缓冲器剩余空间的大小告诉发送方叫做16位窗口大小。发送发可以根据窗口大小来适配发送的速度和大小,窗口大小最大是2的16次方,及64KB,但也可以根据选项中的某些位置扩展,最大扩展1G。
  • 16位校验和:发送端填充,CRC校验。如果接收端校验不通过, 则认为数据有问题(此处的检验和不光包含TCP首部也包含TCP数据部分)。

TCP确认应答机制
在这里插入图片描述
主机A向主机B发送数据,接收端B收到一条报文后,向发送端A发送一条确认ACK,此ACK的作用就是告诉发送端,接收端B已经成功的收到了消息,并且希望收到下一条报文的序列号是什么。这个确认号就是期望的下一个报文的序号。

每一个ACK都带有对应的确认序列号,意思是告诉发送者,我们已经收到了哪些数据,下一个发送数据应该从哪里开始。 如上图,主机A给主机B发送了1-1000的数据,主机B回复ACK应答,携带了1001序列号。告诉主机A,我已经接受到了1-1000数据,下一次你从1001开始发送数据。

TCP超时重传机制
在这里插入图片描述
主机A发送给主机B数据后,可能由于网络拥堵原因,数据无法到达主机B,这时候就形成了一种丢包的问题,那么主机A就不会收到主机B的确认应答回复,隔一段时间后,主机A就会重发数据。当然,还有一种可能就是如下图所示;
在这里插入图片描述

主机A向主机B发送数据后,主机B接收到数据后,就会向主机A确认应答,但是也有可能这个确认应答ACK丢了,那么B就会收到很多重复的数据,那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的包丢弃掉,这时候我们可以利用前面提到的16位序列号, 就可以很容易做到去重的效果。

超时重传的时间应如何设置

在理想的情况下,可以找到一个小的时间来保证 "确认应答"一定能在这个时间内返回。但是这个时间的长短,随着网络环境的不同是有差异的。如果超时时间设的太长,会影响整体的重传效率。如果超时时间设的太短,有可能会频繁发送重复的包。TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。

Linux中超时时间以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。如果重发一次之后,仍然得不到应答,等待2500ms后再进行重传。如果仍然得不到应答,等待4500ms进行重传。依次类推,以指数形式递增,当累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。

TCP连接管理

在正常情况下,TCP要经过三次握手建立连接,经过四次挥手断开连接。具体示意图如下图所示。

在这里插入图片描述
服务端状态变化

  • [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接;
  • [LISTEN -> SYN_RCVD] 一旦监听到连接请求, 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文.
  • [SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行读写数据了.
  • [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT;
  • [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
  • [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接

客户端状态变化

  • [CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段
  • [SYN_SENT -> ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态, 开始读写数据;
  • [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1状态;
  • [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段;
  • [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK
  • [TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态

TIME_WAIT状态

当我们实现一个TCP服务器时,我们把这个服务器运行起来然后将服务器关闭掉,再次重新启动服务器会发现一个问题:就是不能马上再次绑定这个端口号和ip,需要等一会才可以重新绑定,其实等的这一会就是TIME_WAIT状态。通信双方建立TCP连接后,主动关闭连接的一方就会进入TIME_WAIT状态。客户端主动关闭连接时,会发送最后一个ack后,然后会进入TIME_WAIT状态,再停留2个MSL时间(后有MSL的解释),进入CLOSED状态。

为什么要等待2个MSL时间

MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。

  • 第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
  • 第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

如何解决由于TIME_WAIT状态引起Bind绑定失败的问题

在server的TCP连接没有完全断开之前不允许重新绑定,也就是TIME_WAIT时间没有过,但是这样不允许立即绑定在某些情况下是不合理的:

  1. 服务器需要处理非常大量的客户端的连接 (每个连接的生存时间可能很短,但是每秒都有很大数量的客户
    端来请求)这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃,就需要被服务器端主动清理掉),这样服务器端就会产生大量TIME_WAIT状态
  2. 如果客户端的请求量很大,就可能导致TIME_WAIT的连接数很多,每个连接都会占用一个通信五元组(源ip, 源端口, 目的ip,目的端口, 协议)。其中服务器的ip和端口和协议是固定的,如果新来的客户端连接的ip和端口号和TIME_WAIT占用的连接重复就造成等待。

解决方法:使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符即可。

滑动窗口机制

确认应答策略对每一个发送的数据段都要给一个ACK确认应答,接收方收到ACK后再发送下一个数据段,但是这样做有一个比较大的缺点,就是性能较差,尤其是数据往返的时间较长的时候。既然一发一收的方式性能较低,那么我们考虑一次发送多条数据,就可以大大的提高性能,它是将多个段的等待时间重叠在一起。窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。滑动窗口左边代表已经发送过并且确认,可以从发送缓冲区中删除了,滑动窗口里边代表发送出去但是没有确认,滑动窗口右边代表还没有发送的数据。通信双方通过协议字段中的窗口大小,双方进行协商,一次可以最多传输多少条数据,之后等待确认应答,不需要一一进行等待

快重传机制

每条数据的确认回复都要按序回复,若前边的数据没有到,则不会对后边的数据进行回复(这也就是乱序情况),也就意味着,若接收到一条回复,表示ACK确认序号之前的数据全部安全到达,不会因为前边的数据的ACK丢失而重传数据。若前便的数据丢失,则接收方接收到后发的数据,立即发送重传请求,并且连发三次,若发送方连续收到三次重传请求,则认为数据丢失,进行重发。

拥塞控制

虽然TCP有了滑动窗口这个大杀器能够高效可靠的发送大量的数据,但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题,因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下,贸然发送大量的数据是很有可能引起雪上加霜的,造成网络更加堵塞。TCP引入慢启动机制,先发少量的数据探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。
拥塞窗口增长速度,是指数级别的。"慢启动"只是指初使时慢,但是增长速度非常快。为了不增长的那么快,因此不能使拥塞窗口单纯的加倍,此处引入一个叫做慢启动的阈值当拥塞窗口超过这个阈值的时候,不再按照指数方式增长, 而是按照线性方式增长。
拥塞控制是防止过多的数据注入到网络中,可以使网络中的路由器或链路不致过载,是一个全局性的过程。 流量控制是点对点通信量的控制,是一个端到端的问题,主要就是权衡发送端发送数据的速率,以便接收端来得及接收。

延迟应答与捎带应答机制

  • 延迟应答是指接收方收到数据后不立即进行确认回复,而是等待一段时间,因为这段延时的时间内,有可能用户已经recv将缓冲区中的数据取走,窗口就可以保证最大大小,保证传输的吞吐量。
  • 捎带应答是指接收方对每一条数据的确认回复,都需要发送一个TCP数据报,但是空报头的传输会降低性能,因此会考虑在即将要发送的数据报头中包含有确认信息,可以少发一个报头。

面向字节流

当我们创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区。

  • 调用write时,内核将数据会先写入发送缓冲区中,如果发送的字节数太长,会被拆分成多个TCP的数据包发出,如果发送的字节数太短,就会先在缓冲区里等待,
    等到缓冲区长度达到设置长度,然后等到其他合适的时机发送出去。
  • 调用read接收数据的时候,
    数据也是从网卡驱动程序到达内核的接收缓冲区。然后应用程序可以调用read从接收缓冲区拿数据。TCP的一个连接,既有发送缓冲区,
    也有接收缓冲区,那么对于这一个连接,既可以读数据,也可以写数据。所以是全双工的。

由于缓冲区的存在,TCP程序的读和写不需要一一匹配。例如: 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节; 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次 read一个字节, 重复100次。

TCP的粘包问题

粘包问题中的 "包"是指的应用层的数据包。在TCP的协议头中,没有如同UDP一样的 "报文长度"这样的字段,但是有一个序号这样的字段。站在传输层的角度, TCP是一个一个报文过来的,按照序号排好序放在缓冲区中,但是站在应用层的角度,它看到的只是一串连续的字节数据。应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分结束是一个完整的应用层数据包,这就是粘包问题。

如何避免粘包问题呢?明确两个包之间的边界

对于定长的包,保证每次都按固定大小读取即可。例如一个Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可
对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置。
对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议是程序员自己来定义的, 只要保证分隔符不和正文冲突即可)。
对于UDP协议,如果还没有上层交付数据, UDP的报文长度仍然在。 同时UDP是一个一个把数据交付给应用层,这样就有存在明确的数据边界,站在应用层的角度, 使用UDP的时候要么收到完整的UDP报文要么不收,不会出现"半个"的情况。

TCP连接异常

  • 进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别。机器重启和进程终止一样。
  • 机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。应用层的某些协议, 也有一些这样的检测机制.例如HTTP长连接中, 也会定期检测对方的状态.Q在QQ 断线之后, 也会定期尝试重新连接。

当然在TCP这里还存在一系列的问题,不妨看看常见的面试题https://blog.youkuaiyun.com/qq_43503315/article/details/95398122

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值