【Linux】TCP协议【下一】{三次握手/四次挥手的深度解读==状态变化}

文章目录

本篇知识需要有TCP协议【中】的知识!详情点击👇

TCP协议【中】

1.测试一:服务器start函数不定义任何行为(不调用accept)的三次握手状态变化

连接建立成功和accept无关 三次握手由双方OS完成

在服务器不调用accept的情况下 客户端发起连接 双方建立好链接了!

测试:一个客户端跑两个客户端程序访问服务器

两个可以三个呢?

客户端三个程序都认为建立好连接了!

但是服务器认为第三个连接没建立好(为什么?看下面!)!且连接建立的成本在客户端。

接下来,过一段时间再查看状态,发现处于SYN_RECV状态的连接没了,但是客户端仍认为处于连接中。

服务端不会长时间维护SYN_RECV;服务端由于是全连接队列满而丢弃ack包进入established状态而是处于SYN_RECV状态。由于服务端不会长时间维护SYN_RECV,所以过一段时间就会释放半连接(即一个syn包不会长时间处于半连接队列,隔一段时间还未进入全连接队列的syn包就会被移出半连接队列);

像上面,服务端处于SYN_RECV或服务端直接释放了半连接而客户端仍处于established状态这种场景 就是 CS双方认为连接不一致

场景:服务端处于SYN_RECV一段时间后会释放半连接,但客户端认为处于链接,此时客户端向S发讯息。

此时信息不是真正的发到网络了而是存入发送缓冲区,此时的行为是:客户端重新发送syn包,接着情况就和上面一样,服务端仍会处于SYN_RECV;

还有一种场景 服务端已经关闭 客户端仍认为连接成功 但是发消息connect函数已经无法调用成功了

客户端连接结构体对象还没释放

再谈syn洪水

客户端发大量syn,S由于全连接满而把syn放在半连接队列不拿出来,由于半连接队列有长度和时间限制所以这个维度上不会浪费太多资源,但是,一些无意义的连接占用半连接资源,真正想访问服务器的链接进来却无法进入半连接队列或在半连接队列待着不被取走。--- 抢课/抢票进不去 // 转圈转半天404了 // 转圈转半天进去了 ==》服务器有没有处理你的syn请求

backlog不能太大,

没必要太大!不利场景:太大,大量请求,服务器上层处理不过来,全连接队列还满,且一直有连接过来,这些在全连接队列的连接既占用资源还不创造价值,不如不让他们进来。

backlog不能没有

如果没有,当上层处理完正在处理的连接,却发现底层没有连接可使用 --- 上层处理连接的资源没有充分得到利用。如果我们有这样一个队列,他存了一些待accept的连接,那么当上层处理完当前连接就有连接可以继续处理 --- 提高层处理连接的资源的利用率!

backlog的大小的影响因素

backlog的确定因素及其大小通常与不同的上下文和应用场景相关。以下是从不同角度对backlog的详细解释和归纳:

定义和背景:

backlog在计算机和网络领域中,特别是在Linux操作系统和TCP/IP协议栈中,通常指的是一个队列,用于存放等待处理的连接请求或任务。

backlog在PHP-FPM、Nginx和Redis等应用中也有类似的配置,用于处理高并发场景下的连接请求。

确定因素:

应用场景:backlog的大小取决于应用程序的需求,如处理高并发连接的能力、响应速度等。

系统资源:系统的处理能力、内存和CPU资源等也会影响backlog的大小设置。

网络条件:网络带宽、延迟和丢包率等因素也会影响backlog的设置,因为较大的backlog可能会在网络拥堵时导致资源浪费。

协议栈实现:不同操作系统和协议栈实现可能对backlog的处理方式有所不同,这也会影响backlog的确定。

一般大小:

Linux操作系统:在Linux操作系统中,backlog通常分为SYN队列(半连接队列)和ACCEPT队列(已完成连接队列)。SYN队列的长度由系统级别设置(如/proc/sys/net/ipv4/tcp_max_syn_backlog),而ACCEPT队列的长度可以由应用级别设置(如Nginx和PHP-FPM中的backlog配置)。

默认值:许多应用和系统默认将backlog设置为511(如Nginx和PHP-FPM),但这并不是一个固定的标准值,可以根据实际情况进行调整。

调整策略:在高并发场景下,可能需要增加backlog的大小以提高系统的处理能力。然而,过大的backlog可能会导致系统资源浪费,因此需要权衡利弊进行调整。

总结:

backlog的大小应该根据具体的应用场景、系统资源和网络条件进行确定和调整。

在Linux操作系统中,backlog可以分为SYN队列和ACCEPT队列,它们的长度可以分别进行配置。

默认值如511只是一个起点,需要根据实际情况进行调整和优化。

需要注意的是,backlog的设置并不是孤立的,还需要与其他系统参数(如文件描述符限制、网络超时设置等)进行协调和优化。

int listen(int sockfd, int backlog);的backlog参数

服务端处于listen收到客户端的连接请求时,如果连接建立成功,服务端就会创建连接结构体对象,并且可以选择不把建立好的连接返回应用层即作为accept的返回值,这也是为什么上面测试结果中服务器没有调用accept底层却可以建立连接的的解释,客户端可以发起多个连接,服务端本地就会存在多个建立好但未送到accept的连接,怎么办?先描述再组织!用队列管理底层已经建立好的连接!让上层调用accept取走连接!底层创建{成功建立好连接的}连接对象放入到队列中,上层取 ---- PCModel!backlog参数:backlog+1表示【底层已经建立好的连接队列】的最大长度!【底层已经建立好的连接队列】==》全连接队列!

全连接队列

在网络编程中,特别是在处理TCP连接时,经常会遇到两种类型的连接队列:全连接队列(Completed Connection Queue,通常被称为"accept队列")和半连接队列(Incomplete Connection Queue,通常被称为"SYN队列")。这两个队列在TCP三次握手过程中起着关键作用。

半连接队列(SYN队列)

当客户端向服务器发起TCP连接请求时,它会发送一个SYN包。服务器收到SYN包后,会将其放入半连接队列中,并回复一个SYN+ACK包给客户端。此时,服务器和客户端都还没有完成完整的TCP三次握手。

全连接队列(Accept队列)

当客户端收到服务器的SYN+ACK包后,会回复一个ACK包给服务器。服务器收到这个ACK包后,会将这个连接从半连接队列中移除,并放入全连接队列中。此时,服务器已经完成了TCP三次握手,并等待应用层的accept()系统调用来接受这个连接。

backlog参数

在listen()函数中,backlog参数通常用来指定全连接队列的最大长度。但是,在一些系统中,backlog参数实际上同时影响了半连接队列和全连接队列的大小。具体行为可能因操作系统而异。

队列溢出

半连接队列溢出:如果半连接队列满了,服务器会丢弃新的SYN包,并且客户端可能会收到一个RST包,表示连接被重置。

全连接队列溢出:如果全连接队列满了,服务器不会立即回复ACK包给客户端。这会导致客户端在重传SYN+ACK包时超时,并重试连接。如果重试次数达到上限,客户端会放弃连接,并可能返回一个连接超时错误。

如何调整队列大小

半连接队列大小:在一些系统中,可以通过修改内核参数(如tcp_max_syn_backlog)来调整半连接队列的大小。

全连接队列大小:全连接队列的大小通常受listen()函数中的backlog参数影响,但也可能受其他内核参数(如somaxconn)的约束。在某些系统中,全连接队列的大小也可能受到系统级别的限制。

注意事项

合理地设置队列大小对于服务器的性能和稳定性至关重要。过小的队列可能会导致连接被拒绝或超时,而过大的队列可能会浪费系统资源。

在高并发场景下,可能需要考虑使用其他技术(如连接池、异步IO等)来优化性能。

监控队列长度和连接状态对于及时发现和解决问题非常重要。可以使用工具(如netstat、ss等)来查看队列长度和连接状态。

解决上面的问题:服务器认为第三个连接没建立好

服务器listen的backlog参数设置为1,意味着全连接队列长度最大为2,客户端发起两个链接,CS双方都认为连接建立好了,但是第三次,C认为好了但是S认为没好,原因在于,客户端传syn包,假设服务端半连接队列未满,那么Csyn包进入半连接队列然后服务端状态变为SYN_RECV,然后S发syn+ack给C,C收到后发ack给S后状态变为established但是由于全连接队列满,S收到了ack也不会把syn包入全连接队列(满了怎么入)而是丢弃ack(其实也不是丢弃ack而是不做出对应的响应即把syn入全连接队列)(收到ack表示C成功手收到了"我"上一条发的消息),即上述场景是由于S全连接队列满把第三次ack丢弃,因为没有收到ack所以不会进入established而是处于SYN_RECV。

当全连接队列已满,客户端再发起连接会发生什么?

当全连接队列已满时,客户端再发起连接,会发生以下情况:

客户端连接请求的处理:

客户端在发起连接时,会首先发送一个SYN报文给服务端。此时,如果全连接队列已满,服务端会收到这个SYN报文,但无法立即将新的连接放入全连接队列中。

服务端的行为:

服务端收到SYN报文后,会将其放入半连接队列(syn queue)中,并发送ACK+SYN报文给客户端作为响应。

当客户端收到这个ACK+SYN报文后,会发送一个ACK报文给服务端,表示已经收到了服务端的响应。

然而,如果此时全连接队列仍然已满,服务端将不再认为客户端的ACK有效,而是会重新发送ACK+SYN报文给客户端。这个过程可能会重复多次,每次重试的间隔时间会在前一次的基础上翻倍(例如,1秒、2秒、4秒、8秒等)。

重试次数与配置:

服务端重新发送ACK+SYN报文的次数与net.ipv4.tcp_synack_retries配置有关,该值默认是5。这意味着服务端最多会重试5次。

连接失败与清理:

如果在多次重试后,全连接队列仍然是满的,服务端将认为再向客户端发送ACK+SYN报文已经失去了意义,因此会将这个半连接从半连接队列中删除。此时,客户端的连接请求将失败。

配置选项的影响:

如果tcp_abort_on_overflow配置为1,当全连接队列满时,服务端会发送一个reset包给客户端,直接废掉这个握手过程和连接。这有助于客户端更快地意识到连接失败。

通常情况下,为了应对突发流量,建议将tcp_abort_on_overflow设置为0,这样服务端会尝试通过重试来缓冲连接请求,而不是直接拒绝。

解决策略:

当遇到全连接队列满的问题时,一种常见的解决策略是增加全连接队列的长度。这可以通过调整应用代码中的backlog值或系统级的somaxconn值来实现。

综上所述,当全连接队列已满时,客户端的连接请求可能会经历多次重试,并最终失败。为了避免这种情况,可以考虑优化服务端的配置或调整应用代码来增大全连接队列的长度。

2.测试二:服务器start函数不定义任何行为(不调用close)的四次挥手状态变化

  1. 客户端退出 向服务端发送fin 服务端向C发ack后进入CLOSE_WAIT;由于没有调用close所以不会从CLOSE_WAIT到LAST_ACK;如果服务端没有数据要发送了就会调用close,此时一旦调用close就会成为lastack状态,客户端接收到fin会变为timewait状态,然后客户端会发ack给S,一旦发出ack,客户端会认为四次挥手完成,等待若干时长自动释放。
  2. 若服务器主动退出,则:一段时间内服务器会处于timewait状态,如果在服务器处于timewait~close期间再次以上次的端口号启动服务器就会出现bind错误的情况:因为连接没有彻底断开,原来的ip和port仍处于被使用状态,而一个端口号只能bind一个进程,所以出现错误。

双十一购物节 天狗平台同时有大量客户端处于连接访问中 由于客户端太多 使得服务器垮掉

此时服务器主动退出 此时服务器就处于timewait状态(30~60s不等,由底层和应用层共同控制)购物节交易量很大,天狗想赚钱。就得立即启动服务器,但是他无法启动,因为原来的端口仍处于使用状态,他不想等30 ~ 60秒,就得换个端口,但是像这种官方知名网站IP和端口号一般都是公开的也不好更改怎么办?setsockopt!

setsockopt使得即使服务器处于timewait状态也要让服务器以原来的端口号启动(进程处于timewait状态不影响我重新启动提供服务)(当然,如果端口号不能用是因为在被其他进程使用此时就不能再让一个进程用这个端口号)。

客户端主动断开 需不需要setsockopt来使得能够复用端口号

不用!客户端的端口号由OS自动分配 不一定非要用上一次得到端口号 而服务器就不一样了 服务器一般绑定一个知名端口 供用户访问

详细了解setcsockopt

setsockopt 函数是 Linux 中用于设置套接字选项的系统调用。这个函数允许你修改套接字的行为或特性。下面我将详细解释 setsockopt 函数的参数、返回值、工作原理、函数功能及其作用。

函数原型

c

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

参数

sockfd:这是一个文件描述符,通常是由 socket 函数返回的套接字描述符。

level:指定要设置选项的协议级别。常见的值有 SOL_SOCKET(基本套接字选项)、IPPROTO_TCP(TCP 选项)、IPPROTO_IP(IP 选项)等。

optname:指定要设置的选项的名称。这个值依赖于 level 参数。例如,在 SOL_SOCKET 级别下,SO_REUSEADDR 和 SO_KEEPALIVE 是有效的选项名。

optval:指向一个包含选项值的缓冲区的指针。这个缓冲区的类型和大小取决于 optname。

optlen:指定 optval 缓冲区的大小(以字节为单位)。

返回值

如果函数成功,则返回 0。

如果函数失败,则返回 -1,并设置全局变量 errno 以指示错误原因。

工作原理

setsockopt 函数通过修改内核中与套接字相关的数据结构来设置选项。当你调用这个函数时,内核会检查你提供的参数,并根据这些参数更新套接字的状态或行为。

函数功能及作用

setsockopt 函数的主要功能是设置套接字的选项。这些选项可以影响套接字的行为、性能、安全性等方面。以下是一些常见的套接字选项及其作用:

SO_REUSEADDR:允许套接字在关闭后立即重新使用其地址。这对于服务器程序来说非常有用,因为它们通常需要绑定到一个固定的端口上。

SO_KEEPALIVE:启用 TCP 的保活机制。当连接空闲一段时间后,TCP 会自动发送保活探测报文,以检查连接是否仍然可用。

SO_RCVBUF 和 SO_SNDBUF:分别设置套接字的接收和发送缓冲区大小。这可以影响套接字的吞吐量和延迟。

IP_TOS(或 IPV6_TCLASS):设置 IP 头部的类型服务(Type-of-Service)字段或流量类别(Traffic Class)字段。这可以用于 QoS(服务质量)配置。

TCP_NODELAY:禁用 Nagle 算法。Nagle 算法是一种用于减少小数据包数量的技术,但在某些场景下(如实时通信)可能会导致延迟。禁用 Nagle 算法可以减少延迟。

通过设置这些选项,你可以根据需要定制套接字的行为,以满足你的应用程序的特定需求。

TCP协议中,主动断开连接的一方会有timewait状态到close这个状态变化,由timewait状态到close的必要性在哪里?能不能让它发送了ack后就直接close不经过timewait状态

在TCP协议中,主动断开连接的一方在发送FIN报文并收到对方ACK报文后,会进入TIME_WAIT状态而不是直接关闭(CLOSE状态),这个状态的存在有其必要性,主要体现在以下几个方面:

  1. 保证挥手成功 即 被动关闭方收到最后一个ack又继续发信息或者最后一个ack丢失,被动关闭方重复发送fin,保证主动关闭方能够收到让被动关闭方成功收到ack进入closed状态(如果由于最后一个ack丢失被动关闭方无法退出一直发fin,过一段时间他也要退出的!他不可能一直等待。)(ack丢了,服务端重发的fin也丢了怎么办?同上,过一段时间他也要退出的!他不可能一直等待)---- 提高四次挥手的容错性
  2. 防止最后一个ack或历史网络报文与新连接的syn报文混淆 --通信双方历史数据消散【题外话:序号是随机的:防止黑客得出序号规律发恶意报文/ 历史数据影响新数据】

保证对端能收到ACK报文:

TIME_WAIT状态的存在是为了确保在主动关闭方发送的最后一个ACK报文丢失的情况下,被动关闭方能够重传FIN报文。这样,主动关闭方可以再次进入TIME_WAIT状态,并重传ACK报文,从而确保连接的正确关闭。

防止已失效的报文段被后续连接接收:

客户端发送最后一个ACK后,再经过一个2MSL(报文最大生存时间)(一个报文在网络中一来一回)时间,就可以使本连接持续时间内所产生的所有报文段都从网络中消失。从而确保在关闭连接后,客户端发送的最后一个ACK报文段不会与新连接的SYN报文段混淆。这是TCP协议设计的一个精妙之处,通过保证旧的连接状态在网络中"消失"的时间足够长,来避免与新连接发生冲突。

确保被动关闭方关闭连接:

在TIME_WAIT状态期间,主动关闭方可以等待被动关闭方可能发送的FIN报文段。如果被动关闭方在收到主动关闭方的ACK后没有立即关闭连接,而是在稍后的某个时间点发送FIN报文段,那么主动关闭方可以正确地处理这个FIN报文段,确保连接的完全关闭。

至于是否能让主动关闭方在发送了ACK后直接关闭(不经过TIME_WAIT状态),答案是不可以。这是因为TCP协议的设计考虑到了网络的不稳定性和报文丢失的可能性,而TIME_WAIT状态的存在正是为了确保在这些情况下,连接仍然能够正确地关闭。如果取消了TIME_WAIT状态,可能会导致连接无法正常关闭,甚至引发新的连接与旧的连接发生冲突的问题。

综上所述,TIME_WAIT状态在TCP协议中是一个重要的状态,它的存在确保了连接的正确关闭,并避免了与新连接发生冲突的可能性。

MSL

MSL(Maximum Segment Lifetime)在网络通信中,特别是在TCP协议中,指的是报文段最大生存时间。以下是关于MSL的详细描述:

定义:

MSL是TCP协议中定义的一个计时器值,用于确定一个TCP报文段在网络中可以存在或传输的最长时间。

作用:

如果在MSL时间内,TCP报文段没有被接收方确认(即没有收到ACK报文),发送方会认为该报文段已经丢失,并可能会重传该报文段。

在TCP的四次挥手过程中,MSL对于确保连接的正确关闭和避免与新连接混淆具有重要意义。

应用场景:

当主动关闭方(通常是客户端)发送完FIN报文段并进入TIME_WAIT状态时,它会等待2MSL(两倍的最大报文段生存时间)的时间才进入CLOSED状态。这是为了确保被动关闭方(通常是服务器)发送的ACK报文段在网络中已经消失,避免与新连接中可能出现的具有相同序列号的数据包发生混淆。

数值:

MSL的具体值取决于网络环境和操作系统的实现。在大多数现代操作系统中,MSL通常被设置为大约30秒到2分钟的时间,但这个值可能会因网络条件和操作系统配置的不同而有所变化。

总结:

MSL是TCP协议中用于确保报文段传输可靠性的一个关键参数。通过设定报文段在网络中的最大生存时间,TCP协议能够在网络不稳定或报文丢失的情况下,通过重传机制来保证数据传输的完整性和可靠性。同时,在连接关闭的过程中,MSL也起到了避免与新连接混淆的重要作用。

最大传送时间

在网络中发数据是很快的 最大传送时间一般是ms级别的

msl是指一个报文的存活时间 即如果一个报文阻塞在网络中 过了msl他也要死亡

msl一般多久?

MSL(Maximum Segment Lifetime)通常指的是报文最大生存时间,是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。以下是关于MSL时长的常见设置和规定:

标准规定:根据RFC 793(或RFC 0793的旧版本),MSL被规定为2分钟。

实际应用:

在实际应用中,MSL的时长可能会有所不同。常见的时长设置包括30秒、1分钟或2分钟。

Linux系统中,net.ipv4.tcp_fin_timeout的数值记录了2MSL状态需要等待的超时时间(以秒为单位)。

可修改性:在绝大多数的情况下,MSL的值是可以根据需要进行修改的。

综上所述,MSL的时长并非固定不变,而是可以根据实际网络环境和需求进行调整。常见的MSL时长设置包括30秒、1分钟和2分钟。如需更详细的信息或特定网络环境下的MSL设置,建议咨询网络管理员或参考相关网络设备的文档。

相关推荐
库库的里昂13 分钟前
Linux系统Docker部署开源在线协作笔记Trilium Notes与远程访问详细教程
linux·运维·docker·开源
在下不上天24 分钟前
flume-将日志采集到hdfs
大数据·linux·运维·hadoop·hdfs·flume
Mango0000001 小时前
香港站群服务器有助于提升网站在搜索引擎中的排名
运维·服务器·搜索引擎
humors2211 小时前
阿里云ECS服务器监控报警配置
运维·服务器·安全·阿里云·云计算
mit6.8241 小时前
[Redis#3] 通用命令 | 数据类型 | 内部编码 | 单线程 | 快的原因
linux·redis·分布式
hgdlip1 小时前
使用代理ip和本地网络的区别是什么
网络·网络协议·tcp/ip
^Lim1 小时前
esp32 JTAG 串口 bootload升级
java·linux·网络
小林熬夜学编程2 小时前
【Linux系统编程】第五十弹---构建高效单例模式线程池、详解线程安全与可重入性、解析死锁与避免策略,以及STL与智能指针的线程安全性探究
linux·运维·服务器·c语言·c++·安全·单例模式
玖玖玖 柒染2 小时前
windows下sqlplus连接到linux oracle不成功
linux·windows·oracle
LuckyTHP2 小时前
CentOS 9 无法启动急救方法
linux·运维·centos