0%

传输层

讲一下tcp头部

传输层-2025-11-07-19-46-50

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

讲一下TCP三次握手

TCP是面向连接的协议,所以使用TCP前必须建立连接,建立连接是通过三次握手进行的

一开始,客户端和服务端都处于close状态,服务端主动监听某个端口,处于LISTEN状态

第一次握手

客户端随机初始化序列号,将此序号置于TCP首部的序列号字段,同时把SYN标记为1,表示SYN报文,接着把第一个SYN报文发送给服务器,表示向服务器请求连接,该报文不含应用层数据,接下来客户端处于SYN-SENT状态

第二次握手

服务端收到客户端的SYN报文后,服务端也随机初始化自己的序列号,将此序列号填入TCP首部的序号字段,其次把TCP首部的确认应答号字段填入client_isn+1,接着把SYN和ACK置为1。最后把该报文发送给客户端,该报文不包含应用层数据,之后服务端处于SYN-RCVD状态

第三次握手

客户端收到服务端报文后,向服务器回应应答报文,该应答报文的ACK位置为1,确认应答号填入server_isn+1,最后把报文发送给服务器,这次报文可以携带客户到服务端的数据,之后客户端处于ESTABLISHED状态

服务端收到客户端应答报文后,也处于ESTABLISHED状态

三次握手完成,连接建立,客户端和服务端就能互相发送数据了

TCP为什么需要三次握手建立连接

三次握手原因:

  • 阻止重复历史连接的初始化(主要原因)
  • 同步双方的初始序列号
  • 避免资源浪费

阻止重复历史连接的初始化

为什么两次握手不能避免这一问题?

两次握手情况下,服务器收到SYN报文后就进入ESTABLISHED状态,意味着这时候可以给客户端发送数据,但客户端没有进入ESTABLISHED状态,客户端识别到此次为历史连接,返回RST报文来中断连接,但服务器已经进入了连接状态,浪费了服务器资源

同步双方初始序列号

序列号是可靠传输的关键因素:

  • 接收方可以去除重复数据
  • 接收方可以根据数据包的序列号按序接收
  • 可以标识发送出去的数据包中,哪些已经被对方接收

两次握手只能保证一方的序列号被对方成功接收

避免资源浪费

如果没有三次握手,客户端发送的SYN报文在网络中阻塞了,客户端没有收到报文,就会重新发送SYN,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的ACK报文,所以服务器每收到一个SYN就建立一个连接,建立不必要的冗余连接,造成资源浪费

TCP三次握手,客户端第三次发送的确认包丢失了会发生什么

客户端收到服务端的SYN-ACK报文后,就会给服务器回一个ACK,此时客户端进入ESTABLISHED状态

如果服务器长时间没有收到确认报文,就会触发超时重传机制,重新发送SYN-ACK报文,直到收到收到第三次握手或者达到重传最大次数

ACK报文是不会有重传的,当ACK丢失了,就由对方重传对应的报文

三次握手和accept的关系

tcp完成三次握手后,连接会被保存到内核的全连接队列,调用accept就是把连接取出来给用户程序使用

服务器没有收到客户端发送的SYN报文怎么办

客户端发送SYN报文后,迟迟收不到服务器的SYN+ACK报文,就会触发超时重传机制,重传SYN报文,而且重传的SYN报文都是一样的

在linux,客户端的SYN报文重传最大次数有tcp_syn_retries内核参数控制,每一次超时的时间是上一次的两倍

达到最大重传次数后,等待一段时间,如果还是没有收到服务端的第二次握手,客户端就会断开连接

第二次握手的SYN+ACK报文丢失会发生什么

客户端接收ACK超时会发生重传,服务端超时未接收到第二次握手SYN报文的ACK也会触发重传机制,直到重传最大次数,若客户端在重传次数耗尽前,服务器的SYN+ACK重传成功到达,客户端会发送第三次ACK,握手完成。

假如客户端重传SYN报文,服务端收到了重复的SYN报文怎么办

不新建连接,仅重传 SYN+ACK,本质是配合客户端的重试机制,确保握手能在网络丢包场景下完成

第一次握手,客户端发送SYN报文后,服务端回复ACK报文,那这个过程中服务端进行了哪些工作

服务端收到客户端发送的SYN请求后,内核会把连接存储到半连接队列,并向服务端响应SYN+ACK报文,接着客户端发送ACK报文,服务端收到第三次握手的ACK报文后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到accept队列,等待进程调用accept函数取出连接

大量SYN包发给服务端会发生什么

可能会导致TCP半连接队列打满,TCP半连接队列满后,后续再收到SYN报文会丢弃,导致客户端无法和服务端建立连接

避免SYN攻击的方式:

  • 调大netdev_max_log(增大内核接收队列的最大长度)
  • 增大TCP半连接队列
  • 开启net.ipv4.tcp_syncookies(在不使用半连接队列的情况下建立连接)
  • 减少SYN+ACK重传次数(加快处理时间)

在不使用半连接队列的情况下建立连接:

  • 当SYN队列满后,后续的服务端收到SYN包不再丢弃,而是计算出一个cookie
  • 将cookie值放到第二次握手报文的序列号里,然后服务端返回ACK+SYN
  • 服务端接收到客户端的应答报文,服务端检查ACK的合法性。如果合法,将连接对象放入accept队列

讲一下TCP四次挥手

第一次挥手

客户端主动调用关闭连接的函数,于是发送FIN报文,这个FIN报文代表客户端不会再发送数据

第二次挥手

服务端收到FIN报文,马上回复ACK确认报文,此时服务器进入CLOSE_WAIT状态。收到FIN报文后,TCP协议栈会为FIN包插入一个文件结束符EOF到接收缓冲区中,服务端应用程序通过read调用来感知这个FIN包,这个EOF会被**放在已排队等待的其他已接收数据后,所以要继续read接收缓冲区已接受的数据

第三次挥手

接着服务器read数据读到EOF后,read会返回0,这时服务端应用程序如果有数据要发送,就发完数据后调用关闭连接的函数,否则直接调用关闭连接函数。服务端发一个FIN包,表示服务器不再发送数据,之后处于LAST_ACK状态

第四次挥手

客户端接收到服务器的FIN包,发送ACK给服务端,此时客户端进入TIME_WAIT状态
服务端收到ACK后进入CLOSE状态
客户端经过2MSL(两倍的最长报文段寿命)后进入CLOSE状态

为什么四次握手中间两次不能变成一次

服务器收到客户端的FIN报文,内核会立即返回一个ACK应答,但服务端应用程序可能还有数据要发送,所以不能马上发送FIN报文,而是发送将FIN报文的控制权交给服务端应用程序

  • 如果服务端应用程序没有数据要发送,就直接调用关闭连接的函数
  • 否则发送发送完数据后调用关闭连接函数

第二次挥手和第三次挥手能合并吗

当被动关闭方在TCP挥手过程中,没有数据要发送开启了TCP延迟确认机制,那么第二次和第三次挥手就会合并,这样就出现了三次挥手

第三次挥手一致没发,会发生什么

当主动方收到ACK报文后。会进入FIN_WAIT2状态,就表示主动方的发送通道已经关闭,接下来将等待对方发送FIN报文,关闭对方的连接通道。
这是,如果连接是用shundown函数关闭的,连接可以一直处于FIN_WAIT2状态,因为他可能还可以接收数据。但对于close关闭的孤儿连接,由于无法接收或发送数据,所以这个状态可能无法持续太久(默认60s)
这意味着对于孤儿连接,如果60s后没有收到FIN报文,连接会直接关闭

第二次挥手和第三次挥手之间,主动断开的一方能干什么

如果主动关闭的一方是调用shutdown 函数来关闭连接,并且只选择了关闭发送能力而没有关闭接收能力,那么主动断开的一方还能接收数据

断开连接时客户端FIN包丢失,服务端的状态是什么

客户端调用close函数后,就会向服务端发送FIN报文,试图与服务端断开连接,此时客户端进入FIN_WAIT1状态

如果第一次挥手丢失,客户端迟迟收不到服务端的ACK,就会出现超时重传机制,重传FIN报文直到最大重传次数

当重传次数超过最大重传次数后,就不会再发送FIN报文,而是等待一段时间,如果还是没有收到第二次挥手,客户端直接进入close状态,服务端还是established状态

为什么四次挥手之后要等2MSL?

MSL:最长报文段寿命,任何报文在网络上存在的最长时间,超
过这个时间报文将被丢弃。

网络中可能存在来自发送方的数据包,当这些发送
方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。

比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。
可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。

服务端出现大量的timewait有哪些原因?

什么场景下服务端会主动断开连接呢?

  • HTTP 没有使用长连接
  • HTTP 长连接超时
  • HTTP 长连接的请求数量达到上限

HTTP 没有使用长连接

HTTP/1.0 中默认是关闭的,如果浏览器要开启 Keep-Alive,它必须在请求的 header 中添加:Connection: Keep-Alive,后当服务器收到请求,作出回应的时候,它也被添加到响应中 header 里

TCP 连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个 TCP 连接。这一直继续到客户端或服务器端提出断开连接。

HTTP/1.1 开始, 就默认是开启了 Keep-Alive,如果要关闭 HTTP Keep-Alive,需要在 HTTP 请求或者响应的 header 里添加 Connection:close 信息

根据大多数 Web 服务的实现,不管哪一方禁用了 HTTP Keep-Alive,都是由服务端主动关闭连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。

HTTP 长连接超时

HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。

如果客户端在完后一个HTTP 请求后,在最大超时时间内都没有再发起新的请求,定时器的时间一到,服务器就会触发回调函数来关闭该连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。

可以往网络问题的方向排查,比如是否是因为网络问题,导致客户端发送的数据一直没有被服务端接收到,以至于 HTTP 长连接超时。

HTTP 长连接的请求数量达到上限

Web 服务端通常会有个参数,来定义一条 HTTP 长连接上最大能处理的请求数量,当超过最大限制时,就会主动关闭连接。

在高QPS,keepalive-request不高的情况下,就会频繁关闭连接,导致大量的time_wait

TCP和UDP的区别

  • 连接:TCP 是面向连接的传输层协议,传输数据前先要建立连接;UDP 是不需要连接,即刻传输数据
  • 服务对象:TCP 是一对一的两点服务,即一条连接只有两个端点。UDP 支持一对一、一对多、多对多的交互通信
  • 可靠性:TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。UDP 是尽最大努力交付,不保证可靠交付数据
  • 拥塞控制、流量控制:TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率
  • 首部开销:TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。UDP 首部只有 8 个字节,并且是固定不变的,开销较小
  • 传输方式:TCP 是流式传输,没有边界,但保证顺序和可靠。UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序

TCP为什么是可靠传输

  • 连接管理:即三次握手和四次挥手。连接管理机制能够建立起可靠的连接,这是保证传输可靠性的前提。
  • 序列号:TCP将每个字节的数据都进行了编号,这就是序列号。序列号能够保证可靠性,既能防止数据丢失,又能避免数据重复。能够保证有序性,按照序列号顺序进行数据包还原。能够提高效率,基于序列号可实现多次发送,一次确认。
  • 确认应答:接收方接收数据之后,会回传ACK报文,报文中带有此次确认的序列号,用于告知发送方此次接收数据的情况。在指定时间后,若发送端仍未收到确认应答,就会启动超时重传。
  • 超时重传:超时重传主要有两种场景:数据包丢失:在指定时间后,若发送端仍未收到确认应答,就会启动超时重传,向接收端重新发送数据包。确认包丢失:当接收端收到重复数据(通过序列号进行识别)时将其丢弃,并重新回传ACK报文。
  • 流量控制:接收端处理数据的速度是有限的,如果发送方发送数据的速度过快,就会导致接收端的缓冲区溢出,进而导致丢包。为了避免上述情况的发生,TCP支持根据接收端的处理能力,来决定发送端的发送速度。这就是流量控制。流量控制是通过在TCP报文段首部维护一个滑动窗口来实现的。
  • 拥塞控制:拥塞控制就是当网络拥堵严重时,发送端减少数据发送。拥塞控制是通过发送端维护一个拥塞窗口来实现的。可以得出,发送端的发送速度,受限于滑动窗口和拥塞窗口中的最小值。

怎么用udp实现http

UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输,在http3 就用了 quic 协议。

  • 连接迁移:QUIC支持在网络变化时快速迁移连接,例如从WiFi切换到移动数据网络,以保持连接的
    可靠性。
  • 重传机制:QUIC使用重传机制来确保丢失的数据包能够被重新发送,从而提高数据传输的可靠性。
  • 前向纠错:QUIC可以使用前向纠错技术,在接收端修复部分丢失的数据,降低重传的需求,提高可
    靠性和传输效率。
  • 拥塞控制:QUIC内置了拥塞控制机制,可以根据网络状况动态调整数据传输速率,以避免网络拥塞和丢包,提高可靠性

tcp粘包怎么解决

粘包的问题出现是因为不知道一个用户消息的边界在哪

固定长度的消息

这种方式灵活性不高,实际中很少用。

特殊字符作为边界

HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界。
有一点要注意,这个作为边界点的特殊字符,如果刚好消息内容里有这个特殊字符,我们要对这个字符
转义,避免被接收方当作消息的边界点而解析到无效的数据。

自定义消息结构

我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。

TCP的拥塞控制介绍一下

拥塞控制的目的就是避免「发送方」的数据填满整个网络

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念

拥塞窗口cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。发送窗口 swnd和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd =min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大
  • 但网络中出现了拥塞,cwnd 就减少

那么怎么知道当前网络是否出现了拥塞呢?

只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。

慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量

慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加1

可以看出慢启动算法,发包的个数是指数性的增长。

那慢启动涨到什么时候是个头呢?

有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。

  • 当 cwnd < ssthresh 时,使用慢启动算法
  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」

拥塞避免算法

拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd

拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶
段,但是增长速度缓慢了一些

就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。

当触发了重传机制,也就进入了「拥塞发生算法」。

拥塞发生

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

  • 超时重传
  • 快速重传

超时重传

当发生了「超时重传」,则就会使用拥塞发生算法。
这个时候,ssthresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2,
  • cwnd 恢复初始值

重新开始慢启动

快速重传

当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;
  • 进入快速恢复算法

快速恢复算法

  • 拥塞窗口 cwnd = ssthresh + 3 (3的意思是确认有3个数据包被收到了);
  • 重传丢失的数据包;
  • 如果再收到重复的 ACK,那么 cwnd 增加 1;
  • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态