理解网络--Tcp/Udp

发布时间:2026/8/31 5:22:07
理解网络--Tcp/Udp 1.Tcp基本认识1.1.TCP 头格式有哪些序列号在建立连接时由计算机生成的随机数作为其初始值通过SYN包传给接收端主机每发送一次数据就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。确认应答号指下一次「期望」收到的数据的序列号发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。控制位ACK该位为 1 时「确认应答」的字段变为有效TCP规定除了最初建立连接时的SYN包之外该位必须设置为 1 。RST该位为 1 时表示TCP连接中出现异常必须强制断开连接。SYN该位为 1 时表示希望建立连接并在其「序列号」的字段进行序列号初始值的设定。FIN该位为 1 时表示今后不会再有数据发送希望断开连接。当通信结束希望断开连接时通信双方的主机之间就可以相互交换FIN位为 1 的TCP段。1.2.什么是 TCP TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。面向连接一定是「一对一」才能连接不能像 UDP 协议可以一个套接字同时向多个目标套接字发送消息。可靠的无论的网络链路中出现了怎样的链路变化数据以有序无重复无丢失方式被对方接收。字节流连接上传输以字节流形式的数据。建立一个 TCP 连接是需要客户端与服务端达成上述三个信息的共识。Socket由 IP 地址和端口号组成序列号用来解决乱序问题等窗口大小用来做流量控制1.3.TCP 连接建立TCP 三次握手过程是怎样的SYN标志占据一个数据序号。Seq Num表示包内信息对应的首个数据序号。ACK代表预期接收的下个数据序号。第三次握手是可以携带数据的前两次握手是不可以携带数据的。为什么是三次握手不是两次、四次1.避免历史连接两次连接下服务端收到SYN后即进入ESTABLISHED状态。我们考虑一个场景客户端先发送了SYNseq 90报文然后客户端宕机了而且这个SYN报文还被网络阻塞了服务端并没有收到接着客户端重启后又重新向服务端建立连接发送了SYNseq 100报文。如采用二次握手服务端收到SYNseq 90报文即进入连接。采用三次握手服务端收到SYNseq 90报文回复ACKSYN客户端收到并分析后反馈RST阻止连接建立。2.同步双方初始序列号第一次握手丢失了会发生什么SYN丢失客户端无法收到ACK会超时重传。在Linux里客户端的SYN报文最大重传次数由tcp_syn_retries内核参数控制这个参数是可以自定义的默认值一般是 5。#cat/proc/sys/net/ipv4/tcp_syn_retries5第二次握手丢失了会发生什么客户端由于无法收到SYN的ACK会重传SYN重传控制参数如上所述。服务端由于无法收到SYN的ACK也会重传SYNACK重传控制参数#cat/proc/sys/net/ipv4/tcp_synack_retries5什么是 SYN 攻击如何避免 SYN 攻击我们都知道TCP连接建立是需要三次握手假设攻击者短时间伪造不同IP地址的SYN报文服务端每接收到一个SYN报文就进入SYN_RCVD状态但服务端发送出去的ACK SYN报文无法得到未知IP主机的ACK应答久而久之就会占满服务端的半连接队列使得服务端不能为正常用户服务。在TCP三次握手的时候Linux内核会维护两个队列分别是半连接队列也称SYN队列全连接队列也称accept队列我们先来看下Linux内核的SYN队列半连接队列与Accpet队列全连接队列是如何工作的不管是半连接队列还是全连接队列都有最大长度限制超过限制时默认情况都会丢弃报文。SYN攻击方式最直接的表现就会把TCP半连接队列打满这样当TCP半连接队列满了后续再在收到SYN报文就会丢弃导致客户端无法和服务端建立连接。避免SYN攻击方式可以有以下四种方法调大netdev_max_backlog增大TCP半连接队列开启tcp_syncookies减少SYNACK重传次数1.调大netdev_max_backlog当网卡接收数据包的速度大于内核处理的速度时会有一个队列保存这些数据包。控制该队列的最大值如下参数默认值是1000我们要适当调大该参数的值比如设置为100002.增大TCP半连接队列增大 TCP 半连接队列要同时增大下面这三个参数增大net.ipv4.tcp_max_syn_backlog增大listen()函数中的backlog增大net.core.somaxconn3.开启net.ipv4.tcp_syncookies开启syncookies功能就可以在不使用SYN半连接队列的情况下成功建立连接相当于绕过了SYN半连接来建立连接。具体过程当 「SYN队列」满之后后续服务端收到SYN包不会丢弃而是根据算法计算出一个cookie值将cookie值放到第二次握手报文的「序列号」里然后服务端回第二次握手给客户端服务端接收到客户端的应答报文时服务端会检查这个ACK包的合法性。如果合法将该连接对象放入到「Accept队列」。最后应用程序通过调用accept()接口从「Accept队列」取出的连接。net.ipv4.tcp_syncookies参数主要有以下三个值0值表示关闭该功能1值表示仅当SYN半连接队列放不下时再启用它2值表示无条件开启功能那么在应对SYN攻击时只需要设置为1即可。$ echo1/proc/sys/net/ipv4/tcp_syncookies4.减少SYNACK重传次数当服务端受到SYN攻击时就会有大量处于SYN_REVC状态的TCP连接处于这个状态的TCP会重传SYNACK当重传超过次数达到上限后就会断开连接。那么针对SYN攻击的场景我们可以减少SYN-ACK的重传次数以加快处于SYN_REVC状态的TCP连接断开。SYN-ACK报文的最大重传次数由tcp_synack_retries内核参数决定默认值是5次比如将tcp_synack_retries减少到2次$ echo2/proc/sys/net/ipv4/tcp_synack_retries1.4.TCP 连接断开TCP 四次挥手过程是怎样的发出FIN表示此后自身不会继续发送数据。FIN占用一个数据序号。第一次挥手丢失了会发生什么如果第一次挥手丢失了那么客户端迟迟收不到被动方的ACK的话也就会触发超时重传机制重传FIN报文重发次数由tcp_orphan_retries参数控制。第二次挥手丢失即ACK丢失同样会引发FIN的重传。close/shutdown区别close执行时会发出FIN原则上套接字还可持续接收来自对端数据但close关闭下应用无法继续从套接字接收数据。所以FIN_WAIT2状态不可以持续太久而tcp_fin_timeout控制了这个状态下连接的持续时长默认值是60秒。如果主动关闭方使用shutdown函数关闭连接指定了只关闭发送方向而接收方向并没有关闭那么意味着主动关闭方还是可以接收数据的。此时如果主动关闭方一直没收到第三次挥手那么主动关闭方的连接将会一直处于FIN_WAIT2状态tcp_fin_timeout无法控制shutdown关闭的连接。如下图第三次挥手丢失了会发生什么服务端就会重发FIN报文重发次数仍然由tcp_orphan_retries参数控制这与主动关闭侧重发FIN报文的重传次数控制方式是一样的。第四次挥手丢失了会发生什么在Linux系统TIME_WAIT状态会持续2MSL后才会进入关闭状态。然后服务端被动关闭方没有收到ACK报文前还是处于LAST_ACK状态。如果第四次挥手的ACK报文没有到达服务端服务端就会重发FIN报文重发次数仍然由前面介绍过的tcp_orphan_retries参数控制。客户端在收到第三次挥手后就会进入TIME_WAIT状态开启时长为2MSL的定时器如果途中再次收到第三次挥手FIN报文后就会重置定时器当等待2MSL时长后客户端就会断开连接。为什么 TIME_WAIT 等待的时间是 2MSL比较合理的解释是 网络中可能存在来自发送方的数据包当这些发送方的数据包被接收方处理后又会向对方发送响应所以一来一回需要等待 2 倍的时间。为什么需要 TIME_WAIT 状态防止历史连接中的数据被后面相同四元组的连接错误的接收2MSL时长这个时间足以让两个方向上的数据包都被丢弃使得原来连接的数据包在网络中都自然消失再出现的数据包一定都是新建立连接所产生的。保证「被动关闭连接」的一方能被正确的关闭如果服务端没有收到ACK那么就会触发TCP重传机制服务端会重新发送一个FIN这样一去一来刚好两个 MSL 的时间。TIME_WAIT 过多有什么危害第一是占用系统资源比如文件描述符、内存资源、CPU资源、线程资源等第二是占用端口资源端口资源也是有限的一般可以开启的端口为3276861000也可以通过net.ipv4.ip_local_port_range参数指定范围。如何优化 TIME_WAIT打开net.ipv4.tcp_tw_reuse和net.ipv4.tcp_timestamps选项如下的 Linux 内核参数开启后则可以复用处于 TIME_WAIT 的 socket 为新的连接所用。有一点需要注意的是tcp_tw_reuse功能只能用客户端连接发起方因为开启了该功能在调用connect()函数时内核会随机找一个time_wait状态超过1秒的连接给新的连接复用。net.ipv4.tcp_tw_reuse1使用这个选项还有一个前提需要打开对 TCP 时间戳的支持即net.ipv4.tcp_timestamps1默认即为1这个时间戳的字段是在TCP头部的「选项」里它由一共8个字节表示时间戳其中第一个4字节字段用来保存发送该数据包的时间第二个4字节字段用来保存最近一次接收对方发送到达数据的时间。由于引入了时间戳我们在前面提到的2MSL问题就不复存在了因为历史数据包会因为时间戳过期被自然丢弃。net.ipv4.tcp_max_tw_buckets这个值默认为18000当系统中处于TIME_WAIT的连接一旦超过这个值时系统就会将后面的TIME_WAIT连接状态重置这个方法比较暴力。程序中使用SO_LINGER应用强制使用RST关闭。我们可以通过设置socket选项来设置调用close关闭连接行为。structlingerso_linger;so_linger.l_onoff1;so_linger.l_linger0;如果l_onoff为非 0 且l_linger值为 0那么调用close后会立该发送一个RST标志给对端直接关闭。不过这是一个非常危险的行为不值得提倡。服务器出现大量 TIME_WAIT 状态的原因有哪些HTTP没有使用长连接关闭HTTP长连接机制后每次请求都要经历这样的过程建立TCP- 请求资源 - 响应资源 -释放连接。当服务端出现大量的TIME_WAIT状态连接的时候可以排查下是否客户端和服务端都开启了HTTP Keep-Alive因为任意一方没有开启HTTP Keep-Alive都会导致服务端在处理完一个HTTP请求后就主动关闭连接此时服务端上就会出现大量的TIME_WAIT状态的连接。HTTP长连接超时HTTP 长连接的特点是只要任意一端没有明确提出断开连接则保持 TCP 连接状态。web 服务软件一般都会提供一个参数用来指定 HTTP 长连接的超时时间比如 nginx 提供的 keepalive_timeout 参数。假设设置了HTTP长连接的超时时间是60秒nginx就会启动一个「定时器」如果客户端在完后一个HTTP请求后在60秒内都没有再发起新的请求定时器的时间一到nginx就会触发回调函数来关闭该连接那么此时服务端上就会出现TIME_WAIT状态的连接。2.Udp2.1.概述3.Udp和Tcp差异连接TCP是面向连接的传输层协议传输数据前先要建立连接。一个套接字和另一个套接字进行双向数据收发。UDP是不需要连接即刻传输数据。一个套接字可以向多个套接字发送数据也能接收来自多个套接字发出的数据。可靠性TCP是可靠交付数据的数据可以无差错、不丢失、不重复、按序到达。UDP是不可靠的传输协议不保证可靠交付数据数据发送之后丢失了就丢失了。拥塞控制、流量控制Tcp支持拥塞控制流量控制。Udp不支持。首部开销TCP首部在没有使用「选项」字段时是 20 个字节如果使用了「选项」字段则会变长的。UDP首部只有 8 个字节并且是固定不变的开销较小。传输方式TCP是字节流式传输需要应用层处理包的边界识别。UDP是一个包一个包的发送是有边界的但可能会丢包和乱序。分片不同TCP的数据大小如果大于MSS大小则会在传输层进行分片目标主机收到后也同样在传输层组装TCP数据包如果中途丢失了一个分片只需要传输丢失的这个分片。UDP的数据大小如果大于MTU大小则会在IP层进行分片目标主机收到后在IP层组装完数据接着再传给传输层。为什么UDP头部没有「首部长度」字段而TCP头部有「首部长度」字段呢Tcp首部长度可能因为首部选项而变化Udp首部固定。为什么 UDP 头部有「包长度」字段而 TCP 头部则没有「包长度」字段呢先说说 TCP 是如何计算负载数据长度其中IP总长度 和IP首部长度在IP首部格式是已知的。TCP首部长度则是在TCP首部格式已知的所以就可以求得TCP数据的长度。UDP头部的包长度可以起到头部尺寸为4字节倍数效果。