UDP协议 TCP协议(格式 超时重传 滑动窗口 拥塞控制...)

news2025/3/12 15:15:31

UDP协议

格式

UDP协议头部格式由8个字节组成,由4个2字节大小的字段组成。

  • 源端口(Source Port,16 位)

    • 发送端的端口号,标识数据从哪个端口发出。
    • 如果不需要,则可以填 0。
  • 目标端口(Destination Port,16 位)

    • 接收端的端口号,表示数据要传输到哪个端口。
  • 长度(Length,16 位)

    • UDP 数据报的总长度(包括 UDP 头部和数据部分)
    • 最小值为 8(仅有头部,无数据)。
  • 校验和(Checksum,16 位)

    • 用于数据完整性检查,计算方式基于伪首部(Pseudo Header)。
    • 如果计算结果为 0,则在 IPv4 下可以填 0;IPv6 必须计算。

UDP是如何完成分用,如何进行解包的?
1.根据报头中的目标端口号,将数据包交给上层。完成分用

2.因为UDP报头的大小是固定8字节,报头中还包含数据包的总长度(报头长8字节+数据大小),这样就可以获得数据的大小,完成解包。

根据检测校验和检查 UDP 数据报传输过程中是否发生错误。

添加UDP报头的过程:

  • 拷贝应用层数据sk_buff 的数据缓冲区。
  • 在数据前面申请 UDP 头部空间head -= sizeof(struct udphdr))。
  • 填充 UDP 头部信息(源端口、目标端口、长度、校验和)。

协议的本质就是结构体。

当我们从该主机的端口号1234发送数据,先到传输层,发送数据的本质就是拷贝数据,找一块缓冲区把数据拷贝下来,再去考虑添加报头。

对应每一个数据报都有对应的struct sk_buff结构体来管理,里面有一个指针指向它的缓冲区,还有两个指针指向数据的开头和结尾。

如何在数据前面添加报头呢?本质就是在数据前面申请一块struct udphdr大小的空间,并填写相关信息。

1.head*指向数据开头,head-=sizeof(struct udphdr) 申请空间

2.(struct udphdr*)head->source=1234; 填写相关信息

(struct udphdr*)head->dest=1235; 

   ... 

UDP 的特点

1.无连接: 知道对端的 IP 和端口号就直接进行传输, 不需要建立连接;
2.不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方,UDP 协议层也不会给应用层返回任何错误信息;
3.面向数据报: 不能够灵活的控制读写数据的次数和数量;(如果发送端调用一次 sendto, 发送 100 个字节, 那么接收端也必须调用对应的一次 recvfrom, 接收 100 个字节; 而不能循环调用 10 次 recvfrom, 每次接收 10 个字节) 发几次,收几次

UDP 的缓冲区

发送缓冲区:UDP 没有真正意义上的 发送缓冲区. 调用 sendto 会直接交给内核, 由内核将数
据传给网络层协议进行后续的传输动作。(发完不会进行保存,所以没收到不会重发,只能手动重新发送)
接收缓冲区:UDP 具有接收缓冲区. 但是这个接收缓冲区不能保证收到的 UDP 报的顺序和
发送 UDP 报的顺序一致; 如果缓冲区满了, 再到达的 UDP 数据就会被丢弃;(保存接收到的报文,但不一定按顺序 不可靠)

我们注意到, UDP 协议首部中有一个 16 位的最大长度. 也就是说一个 UDP 能传输的数
据最大长度是 64K(包含 UDP 首部).

然而 64K 在当今的互联网环境下, 是一个非常小的数字.
如果我们需要传输的数据超过 64K, 就需要在应用层手动的分包, 多次发送, 并在接收端
手动拼装;(更建议用TCP)

TCP协议

格式

  1. 源端口号(16位):发送端应用程序的端口号。
  2. 目标端口号(16位):接收端应用程序的端口号。
  3. 序列号(32位):表示发送方发送的数据的顺序号,确保数据可以按顺序接收。
  4. 确认号(32位):如果ACK标志位为1,则表示接收到的数据包的下一个期望字节的序列号。
  5. 4位首部长度(数据偏移)(4位):指示TCP报文段的头部长度,单位是32位字(4字节)。
  6. 保留(3位):用于未来的扩展,目前值为0。
  7. 标志位(9位):标志位用于控制TCP的行为。9个标志位分别为:
  8. 标志位缩写作用
    紧急(Urgent)URG表示该数据是紧急数据,需优先处理
    确认(Acknowledgment)ACK该数据包携带有效的 ACK 号
    推送(Push)PSH立即交付数据给应用层
    复位(Reset)RST立即重置连接
    同步(Synchronization)SYN用于建立连接(TCP 三次握手)
    终止(Finish)FIN终止连接(TCP 四次挥手)
    回显(ECE)ECE显式拥塞通知
    拥塞窗口减少(CWR)CWR发送方已减少拥塞窗口
    非拥塞回显(NS)NSECN 扩展
  9. 窗口大小(16位):接收方的接收窗口大小,用于流量控制。
  10. 校验和(16位):用于保证数据在传输过程中没有被篡改。
  11. 紧急指针(16位):如果URG标志位为1,则该字段有效,指示紧急数据的偏移。
  12. 选项(可选,长度可变):可以包含一些附加的TCP选项,如最大段大小(MSS)等。
  13. 数据(可变长度):TCP报文段的有效载荷部分,包含应用层传输的数据。

4位首部长度(4位):

固定的20字节+选项大小。

4位可以表示的范围[0,15],怎么表示20以上的大小呢?

其实4位首部长度的基本单位是4字节,报头长度=4位首部长度*4。所以最大可表示60

TCP如何完成解包和分用的:
解包:通过4位首部长度,接收端可以计算出数据部分的起始位置,并正确地解包数据。

分用:通过目标端口号。

序号和确认序号:

当客户端给服务端发送消息时,怎么知道我的消息有没有发到服务端。服务端再给客户端发个确认信息就好了(只确认收到,一般就只发报头)。但,又怎么确认服务端发的确认信息客户端有没有收到呢?客户端再发个确认确认信息,这样就会进入死循环。

所以没有100%的可靠性,因为总有一条信息是没有确认的。

序号的作用,可以给收到的报文进行排序,按顺序接收,具有可靠性。

确认序号=序号(收到报头中的)+1

比如:我给客户端发消息,报头中序号为10,客户端返回报头中确认序号为11。这就表明客户端收到11序号前的所以报文,并期待收到序号11的报文。

实际上我们并不会发完一条报文,等收到确认报头时,再发下一条。而是一次性发一批报文,客户端再一条一条返回。

况且客户端在会信息时,不一定只发个报头表示收到信息,还有可能带有数据。下面就谈谈为什么要同时存在序号和确认序号。

当客户端收到报文,想发确认和其它信息时,如果只有一个序号,怎么和只确认的进行区分呢?

这就需要存在两个序号,序号和确认序号。

确认序号:告诉对方“我已经收到你发过来的数据,接下来我期待收到哪个字节”。

序号:告诉对方“这是我正在发送的数据,起始字节的位置是这个序号”。

窗口大小:

主机AB相互发报文时,如何判断对方缓冲区还有空间呢?

主机AB缓冲区是否还有空间,只有它们自己最清楚,所以在给对方发报文时,会在报头中填入自己缓冲区剩余的大小,这个字段就是窗口大小

16位窗口大小能表示最大的字节数为65,535,实际的窗口大小最大就是65535吗? 

实际的窗口大小=窗口大小* (2^窗口扩大因子)

窗口扩大因子位于TCP报头选项中的3字节的字段 ,用于增加窗口大小的标度。

(实际窗口大小是 窗口字段的值左移 窗口扩大因子值 位;)

起到流量控制的作用,通过告知接收方缓冲区的剩余空间,帮助维持高效、稳定的数据传输。

1.窗口大小显示自己缓冲区剩余空间的大小,如果剩余空间少可以让对方减少发送报文的速度。

2.反之剩余空间多,可以让对方加快速度,起到流量控制的作用。

为什么TCP报头中没有表示整个报文长度的字段?

TCP 是 面向字节流 的协议。TCP 只提供可靠的、按序的字节流传输,并不会维护单个报文的边界。由于 TCP 只关心数据流的顺序和完整性,而不关心单个数据块的大小,因此没有必要为每个 TCP 报文段专门定义一个总长度字段。

标志位:PSH(清理缓存) PST(重建连接) URG(紧急处理)

1. PSH(Push)标志

  • 作用

    • 用于提示接收方的应用层立即处理当前数据,而不必等待缓冲区填满。
    • 发送方设置 PSH 标志后,接收方通常会将数据直接传递给应用程序,而不是先缓冲一部分再处理。
  • 适用场景

    • 交互式应用,如 Telnet、SSH、HTTP 请求等,数据需要尽快传递到应用程序。
    • 传输小数据包,如即时消息或按键输入,以减少延迟。
  • 工作机制

    • 当 PSH 置位时,接收方 TCP 协议栈不会等到缓冲区填满,而是立即将数据推送到应用程序。
    • 一般来说,TCP 连接关闭时,系统也会自动推送数据,即使 PSH 未设置。

2. RST(Reset)标志

  • 作用

    • 立即终止 TCP 连接,通常用于异常情况或拒绝连接。
    • 通知对方:当前连接无效,不再进行数据传输。
  • 适用场景

    • 服务器端口未监听,而客户端发起连接(服务器会返回 RST)。三次握手失败(发送端发送ACK,接收方没收到。导致在接收方没有认为建立连接时,发送方就发送数据)
    • 非法数据包导致异常,主机决定终止连接。
    • 通信一方崩溃或意外关闭,导致连接状态失效。
  • 工作机制

    • 发送 RST 后,连接直接被关闭,未确认的数据也会丢失。
    • 发送 RST 的一方不会进行四次挥手(正常 TCP 断开流程)。
    • TCP 端口扫描工具(如 nmap)也会利用 RST 来检测端口状态。

3. URG 标志的作用

  • URG 标志:指示当前 TCP 报文段中包含紧急数据。
  • 紧急指针(Urgent Pointer):指明 紧急数据的结束位置(相对于当前序列号)。紧急指针的值 = 相对于序列号的偏移量,表示紧急数据的最后一个字节
    • 当前 TCP 序列号 = 1000
    • 发送 10 个字节数据(包含 5 个紧急字节)
    • 紧急指针 = 1005 (表示紧急数据到 1005 号字节 结束)
  • 目的:让接收方的 TCP 优先处理这部分数据,而不是按正常 TCP 机制排队缓冲。

紧急数据一般位于报文数据的开头假设紧急指针=2,当前报文序号为(SEQ)=1000,发送了10个字节,其中前3个是紧急数据,紧急数据的范围是1000~1002,紧急数据的最后一个字节的序列号1002。

虽然紧急数据可以在中间,但这样只能知道紧急数据的end位置,起始位置无法确定。但也可以通过约定紧急数据的长度来算出,紧急数据的起始位置。

eg.在 Telnet/SSH 这样的协议中,应用层约定紧急数据是单字节(比如 Ctrl+C)。应用层可以检查紧急指针,并向前推测 1 个字节的位置

超时重传机制

超时重传有两种情况,一种数据丢失没传到,另一种传到了,但确认信息没收到。

这两种情况发送端都不能确定有没有传到,剩余如果在特定时间间隔后,还没收到的话就会再传一次。

对于第二种,收到信息,确认应答丢包了。主机B会如何处理重新发过来的数据呢?
主机B维护一个 期望的序列号,用于检查数据包是否是新数据还是重复数据。

如果收到的数据段的序列号 < 期望的序列号:说明这个数据段是重复的,主机B会 丢弃该数据段,但仍然会 重新发送 确认应答(ACK 确认它已经收到过该数据段。

TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.
Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控
制, 每次判定超时重发的超时时间都是 500ms 的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接.

连接管理机制(3次握手,4次挥手)

TCP一般要经过3次握手建立连接,4次挥手断开连接。

三次握手过程

阶段客户端(Client)服务器(Server)
第一次握手发送 SYN 包,指定初始序列号 Seq = x
第二次握手发送 SYN = 1, ACK = 1,确认 ACK = x+1,同时发送 Seq = y
第三次握手发送 ACK = y+1,确认服务器的 SYN连接建立,开始数据传输

四次挥手过程

阶段客户端(Client)服务器(Server)
第一次挥手发送 FIN = 1, Seq = m,表示不再发送数据
第二次挥手发送 ACK = m+1,确认 FIN
第三次挥手发送 FIN = 1, Seq = n,服务器关闭连接
第四次挥手发送 ACK = n+1,确认服务器的 FIN连接完全关闭

为什么断开连接需要进行4次?

因为TCP是全双工的,客户端和服务端可以互相发信息。客户端给服务端断开连接,服务端也可以不断开连接 继续发消息,也可以选择断开连接。

为什么建立连接需要进行3次?
建立连接本质上也是4次,只不过确认和连接标志位同时为1,合并在一起。

3次握手1.可以建立双方通信的共识。2.双方验证全双工信道的流畅性。

为什么断开连接的中间两次ACK+FIN不合并一块?可以,如果两方都需要断开。但一般情况下,当客户端断开连接时,服务端一般只会回确认,因为还有没发完的数据。等服务端发完数据才会断开。

分析4次挥手,主动关闭的一方和后关闭的一方所处的状态

我们把左边客户端当作主动关闭的一方,右边服务器作为后关闭的一方

TCP 连接断开时,通信双方都需要关闭各自的数据流通道,整个过程如下:

步骤状态变化(客户端)状态变化(服务器)说明
① 客户端发送 FINESTABLISHEDFIN-WAIT-1ESTABLISHEDCLOSE-WAIT客户端 发送 FIN(Finish),表示 "我不再发送数据了"
② 服务器回复 ACKFIN-WAIT-1FIN-WAIT-2CLOSE-WAIT服务器 回复 ACK,表示 "我知道你要关闭发送数据的通道了"
③ 服务器发送 FINFIN-WAIT-2CLOSE-WAITLAST-ACK服务器 可能还有数据要发送,等发送完毕后,再发送 FIN 关闭
④ 客户端回复 ACK,进入 TIME-WAITFIN-WAIT-2TIME-WAITLAST-ACKCLOSED客户端 确认 FIN 后,发送 ACK,等待 2MSL 后真正关闭
⑤ 连接完全关闭TIME-WAITCLOSEDCLOSED连接彻底关闭

 1.左边主动关闭,发送FIN,变为FIN-WAIT-1. (左边不再发送数据,但可接收)

 右边接收到FIN,变为CLOSE-WAIT.(右边仍可以发送数据),发送ACK

2.左边接收ACK,变 FIN-WAIT-2.

(如果左边没收到 ACK,左边会一直停留在 FIN-WAIT-1 状态,直到超时或重传 FIN。)

3.右边断开连接,发送FIN,变LAST-ACK

(如果右边没有进行close(),断开连接会一直处于CLOSE-WAIT,导致socket资源泄漏)

左边接收到FIN,发送ACK,TIME-WAIT。等待2MSL时长后,进入CLOSED状态。

(如果左边没收到FIN,会一直处于FIN-WAIT-2,在Linux下等待60s 仍没有就会直接断开,发送ACK,变为TIME-WAIT)

4.右边收到ACK,变CLOSED

LAST-ACK 状态下,右边若未收到 ACK,会重传 FIN,最终超时关闭。

为什么主动关闭的一端(左边),TIME-WAITCLOSED 需要等待2MSL时长

1.防止旧的重复数据包干扰。(等待历史的游离报文在网络中消失)

如果知道数据包会在网络中存在一定的延迟。我们双方关闭连接之后又重新建立了新连接,此时旧数据包才传到对方,就会被对方当成新的数据包处理。

MSL是指一个数据包从发送到从网络中消失的最大时间。2MSL是为了确保所有的数据包(包括FIN和ACK包)在网络中有足够的时间“消失”,避免任何晚到的包对新连接产生影响。

2.确保对方收到关闭连接的ACK  (完成正常的4次挥手断开)

当我们发送ACK确认信息时,有可能会丢包,对方没收到就会重传FIN。这段时间就可以处理收到的FIN,并返回ACK。

如果被动关闭的一方没有调用close(),连接就不会发送回 FIN 包,导致连接永远停留在 CLOSE-WAIT 状态。这种情况会导致套接字资源(比如文件描述符)无法释放,从而造成资源泄漏,影响服务器的性能和稳定性。

主动方调用close(),发送FIN,状态ESTABLISHED → FIN-WAIT-1,在变为CLOSED前为什么还可以接收数据close()不是关闭了读端写端了吗?

应用层 close()关闭的是应用对 socket 的访问权限,而非立即终止协议交互。

调用close()后,应用层无法主动读取数据((因为 close() 已释放 fd)),但如果对方在收到 FIN 后仍有数据要发送,内核会接收这些数据并回复 ACK(确保对方知道数据已收到)。

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应.因此 TCP 支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制。

当主机A接收到的报头中窗口大小为0,意味着主机B的缓冲区已经满了。主机A就不会再给B发报文,那什么时候能发呢?

主机A会定期发送一个窗口探测数据段(实际就是单个报头), 使接收端把窗口大小告诉发送端。

一开始怎么知道接收方缓冲区是否为满了?

3次握手,就相当于完成了获取窗口大小。

滑动窗口

之前我们说发一个报文,等ACK确认信息返回后,再发一个。这中模式比较慢,不如一次性发多个报文,效率更高。

但我们怎么知道这一次性发的报文,没有超过接收方缓冲区的容量呢?

这就需要TCP接收缓冲区滑动窗口的范围

发送方会有一个内部的缓冲区(通常称为 发送缓冲区)。在发送数据之前,发送方将数据放入这个缓冲区。

我们可以把发送缓冲区分为3个部分,1.已经发送完且确认完的2.可直接发送3.待发送

中间可以直接发送的部分就是滑动窗口的范围,怎么算出来的呢?
其实滑动窗口的大小就是对方发来报头中窗口大小,即对方缓冲区的剩余大小。(滑动窗口的大小不止和对方报头的窗口大小(剩余缓冲区大小)有关,还和拥塞窗口大小有关(即网络情况))

滑动窗口开始位置start=确认序号(期待收到的起始字节)

结束位置end=start+窗口大小

所以在滑动窗口的数据可以直接发,不用担心会溢出接收方的缓冲区。

(为什么不发一个报文,包含滑动窗口的所以数据 ?数据链路层不允许发大的报文)

确认序号是一直增大的,所以滑动窗口一直向右边移动。滑动窗口一直向右移动,到了末尾会不会越界?不会,可以覆盖前面发送完且确认完的数据,把接收缓冲区理解为环形。

丢包

那么如果出现了丢包, 如何进行重传? 这里分两种情况讨论.

一:ACK丢了

二:数据包丢了

不管哪种情况主机A的处理方法都是一样的。

比如上图,主机A发1~7000分7份发,其中1000~2000丢了。那主机B返回的所有报头中确认序号都为1001(期待下一个数据从1001开始),发送方在收到连续 3 个相同的 ACK 时,就会触发快重传,立即重传丢失的数据包,而不等超时重传。

如果中间丢了多个呢?1~1001 2001~3001都丢了,同样也是都返回1确认序号,补齐1~1001,再看其它是否缺少。

  • 快重传机制会 按顺序重传每个丢失的数据段,并且每次快重传的触发都依赖于重复 ACK 的接收。
  • 不会一次性重传所有丢失的段,即使中间丢失多个段,发送方会依次重传每一个丢失的段。

拥塞控制

虽然 TCP 有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开
始阶段就发送大量的数据, 仍然可能引发问题.

因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络
状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.

TCP 引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按
照多大的速度传输数据;

像上面这样的拥塞窗口增长速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增长速
度非常快.

为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.此处引入一个叫做慢启动的阈值当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长,这个阶段就是拥塞避免。

阶段发送数据的条件增长方式
慢启动收到 ACK 后增加 cwnd 并发送更多数据指数增长(×2)
拥塞避免到达ssthresh值后 增加 cwnd 并发送更多数据线性增长(+1 MSS)
拥塞发生检测到丢包(超时或 3 个重复 ACK)进入慢启动或快速恢复,更新ssthres=拥塞窗口最大值/2
重新慢启动从 1 MSS 开始 重新探测带宽重新指数增长

因此我们发送数据大小时,不能只看接收方缓冲区的剩余大小还要看网络的传输情况,即

滑动窗口大小=min(接收方缓冲区剩余大小,拥塞窗口大小)

拥塞控制, 归根结底是 TCP 协议想尽可能快的把数据传输给对方, 但是又要避免给网络
造成太大压力的折中方案.

延迟应答

如果接收数据的主机立刻返回 ACK 应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为 1M. 一次收到了 500K 的数据; 如果立刻应答, 返回的窗口就是 500K;
但实际上可能处理端处理的速度很快, 10ms 之内就把 500K 数据从缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会再应答, 比如等待 200ms 再应答, 那么这个时候返回的窗口大小就是 1M;

在 TCP 协议中,接收方在收到数据后不立即发送 ACK,而是稍作等待,希望能合并多个 ACK,从而减少 ACK 报文的数量,优化网络效率。这种机制称为 延迟应答。

 TCP 延迟应答一般有两个作用:

1.合并 ACK(减少 ACK 数据包的数量)

延迟 ACK 机制 允许 TCP 等待一小段时间(通常 40~200ms),看看是否会收到第二个数据包:

  • 如果在这个时间内收到了第二个数据包,TCP 就会合并 ACK,一次性确认多个数据包,减少 ACK 的数量。
  • 如果没有收到第二个数据包,就会在超时时间到达时发送 ACK,确保数据不会被误认为丢失。

2. 让返回的窗口大小(rwnd)变大

延迟 ACK 让接收方有时间处理部分数据,并释放缓冲区,这样:

  • 当接收方最终发送 ACK 时,可以报告一个更大的 rwnd,让发送方继续发送更多数据。
  • 提高吞吐量,减少不必要的发送速率下降

窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;

捎带应答

捎带应答(Piggyback Acknowledgment) 是 TCP 为了优化网络通信,在双向数据传输时将 ACK(确认报文)捎带在返回的数据包里,而不是单独发送 ACK。

比如说三次握手:接收方返回SYN+ACK也算捎带应答。

捎带应答的优缺点

优点缺点
减少 ACK 包数量,降低网络开销可能会增加延迟(如果 B 迟迟没有数据要发,ACK 可能会被延迟)
提高带宽利用率,减少不必要的小数据包适用于双向数据传输,但对单向数据流(如文件下载)没有帮助
提升吞吐量,特别是在双向通信频繁的场景(如视频会议、VoIP)需要应用层的配合,有些协议(如 Telnet)不适用

面向字节流

创建一个 TCP 的 socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
调用 write 时, 数据会先写入发送缓冲区中;
如果发送的字节数太长, 会被拆分成多个 TCP 的数据包发出;
如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了,包合并成一个发送, 或者其他合适的时机发送出去;
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
然后应用程序可以调用 read 接收缓冲区拿数据;


另一方面, TCP 的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工

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

粘包问题

粘包 是 TCP 面向字节流传输 的一个常见问题,它指的是 多个 send() 发送的数据被 TCP 合并到一个 recv() 读取的缓冲区,导致接收方无法区分数据边界。

1.数据包合并 2.发送速度太快导致合并 3.recv()不及时 都会导致接收方无法区分数据边界。

那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界。

对于定长的包, 保证每次都按固定大小读取即可; 例如上面的 Request 结构, 是固定大小的, 那么就从缓冲区从头开始按 sizeof(Request)依次读取即可;
对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了
的结束位置
;
对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可)

对于 UDP 协议来说, 是否也存在 "粘包问题" 呢?

UDP 不会发生 TCP 的“粘包”问题,因为 UDP 是“面向报文”的协议,每个 send() 发送的数据就是一个独立的数据报,不会被合并或拆分

UDP 是面向报文的,每个 sendto() 发送的数据在接收端 recvfrom() 读取时仍然是完整的一个报文
UDP 具有以下特点:

  • 不会合并多个 sendto() 发送的数据
  • 不会把一个 sendto() 发送的数据拆分给多个 recvfrom() 读取
  • 接收方 recvfrom() 一次最多读取一个 UDP 数据报,如果缓冲区小于数据报大小,超出部分会被直接丢弃(不会分成多个包)。
  • 站在应用层的角度, 使用 UDP 的时候, 要么收到完整的 UDP 报文, 要么不收. 不会出现"半个"的情况.

TCP 异常情况

进程终止: 进程终止会释放文件描述符, 仍然可以发送 FIN. 和正常关闭没有什么区别.
机器重启: 和进程终止的情况相同.
机器掉电/网线断开: 接收端认为连接还在,。

一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行 reset.

即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.

TCP/UDP 对比

特性TCP(面向连接)UDP(面向无连接)
连接模式面向连接(需要 三次握手 建立连接)无连接(直接发送,不建立连接)
可靠性可靠,有超时重传、确认机制、流量控制、拥塞控制不可靠,无重传机制,丢包不会被检测
数据传输方式面向字节流,数据是连续的,无边界面向报文每个数据报是独立的
传输顺序保证按序到达不保证顺序,数据可能乱序
流量控制有流量控制(滑动窗口)无流量控制
拥塞控制有拥塞控制(慢启动、拥塞避免等)无拥塞控制
传输速度较慢(需要握手、确认、重传)(无握手、无确认机制)
首部大小20~60 字节8 字节
是否支持广播/组播❌ 不支持支持广播、组播
适用场景可靠传输(HTTP、文件传输、数据库、邮件)实时性要求高(视频、语音、DNS、游戏)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2313191.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爱普生温补晶振 TG5032CFN高精度稳定时钟的典范

在科技日新月异的当下&#xff0c;众多领域对时钟信号的稳定性与精准度提出了极为严苛的要求。爱普生温补晶振TG5032CFN是一款高稳定性温度补偿晶体振荡器&#xff08;TCXO&#xff09;。该器件通过内置温度补偿电路&#xff0c;有效抑制环境温度变化对频率稳定性的影响&#x…

【网络安全工程】任务11:路由器配置与静态路由配置

目录 一、概念 二、路由器配置 三、配置静态路由CSDN 原创主页&#xff1a;不羁https://blog.csdn.net/2303_76492156?typeblog 一、概念 1、路由器的作用&#xff1a;通过路由表进行数据的转发。 2、交换机的作用&#xff1a;通过学习和识别 MAC 地址&#xff0c;依据 M…

Webservice创建

Webservice创建 服务端创建 3层架构 service注解&#xff08;commom模块&#xff09; serviceimpl&#xff08;server&#xff09; 服务端拦截器的编写 客户端拦截器 客户端调用服务端&#xff08;CXF代理&#xff09; 客户端调用服务端&#xff08;动态模式调用&a…

使用VS Code remote ssh进行远程开发的笔记

本文是在VS Code中使用 remote ssh 进行开发的笔记。 安装插件 打开VS Code&#xff0c;在扩展区找到remote相关插件&#xff0c;安装之。下图中红色框出来的是已经安装了的插件&#xff08;圆圈处即为Remote Explorer&#xff09;。 实践 连接服务器 新建连接&#xff1a…

C语言每日一练——day_3(快速上手C语言)

引言 针对初学者&#xff0c;每日练习几个题&#xff0c;快速上手C语言。第三天。&#xff08;会连续更新&#xff09; 采用在线OJ的形式 什么是在线OJ&#xff1f; 在线判题系统&#xff08;英语&#xff1a;Online Judge&#xff0c;缩写OJ&#xff09;是一种在编程竞赛中用…

PostgreSQL - Windows PostgreSQL 下载与安装

Windows PostgreSQL 下载与安装 1、PostgreSQL 下载 下载地址&#xff1a;https://www.enterprisedb.com/downloads/postgres-postgresql-downloads 2、PostgreSQL 安装 启动安装程序 -> 点击 【Next】 指定安装路径 -> 点击 【Next】 默认勾选 -> 点击 【Next】 指…

JVM 的主要组成部分及其作用?

创作内容丰富的干货文章很费心力&#xff0c;感谢点过此文章的读者&#xff0c;点一个关注鼓励一下作者&#xff0c;激励他分享更多的精彩好文&#xff0c;谢谢大家&#xff01; JVM包含两个子系统和两个组件&#xff0c;两个子系统为Class loader(类装载)、Execution engine(执…

华为eNSP:配置P2P网络类型

一、什么是P2P网络类型 P2P&#xff08;Point-to-Point&#xff09;网络类型 是 OSPF&#xff08;开放最短路径优先&#xff09;协议中的一种网络类型&#xff0c;用于描述两个路由器之间直接相连的点对点链路。P2P 网络类型通常用于串行链路&#xff08;如 PPP 或 HDLC 封装&…

通过数据集微调LLM后怎么调用

通过数据集微调LLM后怎么调用 1. 导入必要的库 from transformers import AutoTokenizer, AutoModelForCausalLMAutoTokenizer:这是 transformers 库中的一个实用类,它能够根据指定的模型名称或路径自动选择合适的分词器。分词器的主要作用是将输入的文本字符串转换为模型可…

thinkphp+mysql+cast解决text类型字段的文本型数字排序错误的方法 - 数据库文本字段排序ASC、DESC的失效问题

TP中使用cast order $lists AmdCommonTable::where(..............) ->field(*,CAST(w6 AS UNSIGNED) as sort) ->order(sort, asc) ->select() ->toArray(); 先转换为数字&#xff0c;再order by 效果对比 (1/2) 不ok - 直接order by 某字段 asc - 只能按照文本…

【Manus资料合集】激活码内测渠道+《Manus Al:Agent应用的ChatGPT时刻》(附资源)

DeepSeek 之后&#xff0c;又一个AI沸腾&#xff0c;冲击的不仅仅是通用大模型。 ——全球首款通用AI Agent的破圈启示录 2025年3月6日凌晨&#xff0c;全球AI圈被一款名为Manus的产品彻底点燃。由Monica团队&#xff08;隶属中国夜莺科技&#xff09;推出的“全球首款通用AI…

C++----红黑树map和set的封装

一、红黑树 1.概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出2倍&#xff0…

【报错】微信小程序预览报错”60001“

1.问题描述 我在微信开发者工具写小程序时&#xff0c;使用http://localhost:8080是可以请求成功的&#xff0c;数据全都可以无报错&#xff0c;但是点击【预览】&#xff0c;用手机扫描二维码浏览时&#xff0c;发现前端图片无返回且报错60001&#xff08;打开开发者模式查看日…

软考 数据通信基础——信道

信道特性 带宽 在模拟信号里频率的差&#xff0c;表示信道能通过的频率 在数字信号里表示最大传输速率&#xff0c;单位用bit/s 通常用W表示 波特率 即码元速率&#xff0c;码元可看作一个时间周期 码元速率B2W也可写成B1/T 码元种类n和码元信息量个数N存在以下关系 Nl…

Docker数据管理,端口映射与容器互联

1.Docker 数据管理 在生产环境中使用 Docker&#xff0c;往往需要对数据进行持久化&#xff0c;或者需要在多个容器之间进行数据共享&#xff0c;这必然涉及容器的数据管理操作。 容器中的管理数据主要有两种方式&#xff1a; 数据卷&#xff08;Data Volumns&#xff09;&a…

部署前后端项目

部署项目 liunx 软件安装 软件安装方式 在Linux系统中&#xff0c;安装软件的方式主要有四种&#xff0c;这四种安装方式的特点如下&#xff1a; 建议nginx、MySQL、Redis等等使用docker安装&#xff0c;会很便捷&#xff0c;这里只演示JDK、ngxin手动的安装 安装JDK 上述我…

1256:献给阿尔吉侬的花束--BFS多组输入--memset

1256&#xff1a;献给阿尔吉侬的花束--BFS多组输入--memset 题目 解析代码【结构体】用book标记且计步数的代码[非结构体法] 题目 解析 标准的BFS题目&#xff0c;在多组输入中要做的就是先找到这一组的起点和终点&#xff0c;然后将其传给bfs&#xff0c;在多组输入中最易忘记…

【JavaEE】SpringBoot快速上手,探秘 Spring Boot,搭建 Java 项目的智慧脚手架

1.Spring Boot介绍 在学习SpringBoot之前, 我们先来认识⼀下Spring &#xff0c;我们看下Spring官⽅的介绍 可以看到&#xff0c;Spring让Java程序更加快速, 简单和安全。 Spring对于速度、简单性和⽣产⼒的关注使其成为世界上最流⾏的Java框架。 Spring官⽅提供了很多开源的…

【C】初阶数据结构9 -- 直接插入排序

前面我们学习了数据结构二叉树&#xff0c;接下来我们将开启一个新的章节&#xff0c;那就是在日常生活中经常会用到的排序算法。 所谓排序算法就是给你一堆数据&#xff0c;让你从小到大&#xff08;或从大到小&#xff09;的将这些数据排成一个有序的序列&#xff08;这些数据…

Lottie与LottieFiles:快速为前端Web开发注入精美动画的利器

目录 Lottie与LottieFiles&#xff1a;快速为前端Web开发注入精美动画的利器 一、Lottie是什么&#xff1f;从GIF到JSON的动画技术演进 1、传统动画臃肿的Gif 2、Lottie的突破性创新 二、Lottie的核心组件解析&#xff08;Lottie的技术架构&#xff09; 1、Lottie核心三要…