Linux socket编程(5):三次握手和四次挥手分析和SIGPIPE信号的处理

本文主要是介绍Linux socket编程(5):三次握手和四次挥手分析和SIGPIPE信号的处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在我之前写的Wireshark抓包:理解TCP三次握手和四次挥手过程中,通过抓包分析了TCP传输的三次握手和四次挥手的过程。在这一节中,将分析在Linux中的三次握手和四次挥手的状态和过程,另外还有一个在我们编程过程中值得注意的SIGPIPE信号的处理。

文章目录

  • 1 TCP连接的11种状态
  • 2 实验:查看TCP状态变化
  • 3 read/recv返回0的作用
  • 4 SIGPIPE信号

1 TCP连接的11种状态

在TCP建立连接的过程中将经历一系列状态,包括:LISTEN(监听)、SYN-SENT(同步已发送)、SYN-RECEIVED(同步已接收)、ESTABLISHED(已建立)、FIN-WAIT-1(等待第一个关闭)、FIN-WAIT-2(等待第二个关闭)、CLOSE-WAIT(等待关闭)、CLOSING(关闭中)、LAST-ACK(最后的确认)、TIME-WAIT(等待时间)、以及虚构的状态CLOSED(关闭)。

  • CLOSED是虚构的,因为它表示当没有传输控制块(TCB)时的状态(没有连接时的状态)

在TCP标准中:

  • 三次握手:首先发送SYN标志的客户端被称为主动打开者,而另一侧的服务器称为被动打开者
  • 四次挥手:首先发送FIN标志的客户端或服务器被称为主动关闭者,而另一端则被称为被动关闭者

如下图所示:客户端通过发送SYN标志成为SYN_SENT状态的过程(主动打开),同时服务器成为LISTEN状态的过程(被动打开)。

在这里插入图片描述

现在来看一下我们熟悉的三次握手和四次挥手的握手流程:

在这里插入图片描述

  1. LISTEN(监听)
    • 表示服务器在接收到来自客户端的SYN标志后可以创建新连接的状态。
    • 在Linux中,通过bind()listen()系统调用,服务器进入LISTEN状态。
  2. SYN_SENT(SYN已发送)
    • 表示关闭状态的客户端发送SYN标志并转换的状态。
    • 在Linux中,客户端可以通过connect()系统调用进入SYN_SENT状态。
    • 根据/proc/sys/net/ipv4/tcp_syn_retries值以最大RTO(重传超时)间隔发送SYN标志。
  3. SYN_RECEIVED(SYN已接收)
    • 表示处于LISTEN状态的服务器在接收到客户端的SYN标志后,响应SYN+ACK标志并转换的状态。
    • 在Linux中,通过accept()系统调用,服务器进入SYN_RECEIVED状态。
    • 根据/proc/sys/net/ipv4/tcp_synack_retries值以最大RTO(重传超时)间隔发送SYN标志。
  4. ESTABLISHED(已建立)
    • 表示在3次握手后建立连接的状态,服务器和客户端可以交换数据。
    • 在Linux中,通过send()recv()系统调用可以进行数据交换。
    • 可以通过在套接字中设置SO_KEEPALIVE选项来定期检查TCP连接的有效性。
  5. FIN_WAIT_1(等待第一次关闭)
    • 表示在已建立的状态中,主动关闭者结束后转换的状态。主动关闭者在调用close()或其进程终止后,由于套接字关闭,主动关闭者发送FIN标志并进入FIN_WAIT_1状态。
  6. FIN_WAIT_2(等待第二次关闭)
    • 表示FIN_WAIT_1状态的主动关闭者接收到被动关闭者的ACK,或者直到由Linux内核设置的FIN_WAIT_2的超时时间过去为止。
  7. TIME_WAIT(等待时间)
    • 表示FIN_WAIT_2状态的主动关闭者接收到被动关闭者的FIN标志后的状态。TIME_WAIT状态应持续2MSL(2 * 最大分段寿命),即直到网络上的所有相关数据包(分段)完全被清除为止,以确保对后续新连接的影响最小化。
  8. CLOSING(关闭中)
    • 表示发生同时关闭,FIN_WAIT_1状态的主动关闭者接收到FIN标志时的状态。
  9. CLOSE_WAIT(等待关闭)
    • 表示被动关闭者从主动关闭者接收到FIN标志并转换的状态。在Linux环境中,CLOSE_WAIT状态没有超时,只有在被动关闭者的套接字关闭时才会结束。
  10. LAST_ACK(最后确认)
    • 表示CLOSE_WAIT状态的被动关闭者发送FIN标志给主动关闭者后,在收到相应的ACK之前保持的状态。

理论介绍完了,还是得实际写代码看看Linux中这些状态的切换,来看看实际执行过程中会遇到什么问题。

2 实验:查看TCP状态变化

  • 这里实验使用这篇利用fork实现服务端与多个客户端建立连接最后贴出来的代码

1、运行服务端程序

在这里插入图片描述

可以看到此时服务端处于LISTEN状态。

2、运行客户端程序

在这里插入图片描述

现在客户端和服务端都处于ESTABLISHED建立连接状态。

  • SYN_SENTSYN_RCVD状态切换地过快,这里没体现出来

3、关闭客户端程序

在这里插入图片描述

可以看到客户端进入了TIME_WAIT状态。在等待2MSL时间后,客户端的网络状态将关闭(CLOSED)。

3 read/recv返回0的作用

注意:在一端关闭后,另一端需要关闭自己的程序,否则主动关闭的那一端将无法进入TIME_WAIT状态,而是保持在FIN_WAIT2状态。

那如何判断对端关闭了呢? 当一端关闭后,另一端的readrecv函数将无限非阻塞返回0。

现在修改一下服务端的读取代码:
在这里插入图片描述

这里把break注释掉,也就是在客户端关闭时,服务端不退出fork出来的子进程。现在重复一下前面的实验过程:运行服务端和客户端,然后客户端断开连接。如下图所示,客户端果然处于FIN_WAIT2状态。

在这里插入图片描述

所以无论是客户端还是服务端都需要实时的readrecv来判断对端是否断开,进行资源的回收处理,否则对端的状态将无法处于TIME_WAIT

4 SIGPIPE信号

我们知道,当一方关闭连接时,它会发送一个FIN标志给对方。这是TCP的一种半关闭状态,表示发送方(主动关闭的一方)不再发送数据,但仍然可以接收数据。所以当服务端收到客户端发送的FIN后,它可以继续向客户端发送数据,但这些数据会被送入TCP的发送缓冲区,并不会立即发送。

如果客户端收到服务端的FIN并关闭了连接,而服务端仍然试图向客户端发送数据,这时如果客户端已经关闭了接收端,写入操作就会导致Linux内核发送一个SIGPIPE信号被发送给服务端进程。

  • SIGPIPE:用于通知进程它试图向一个已经关闭的管道(或socket)写数据。

默认情况下,如果进程忽略或者不捕获SIGPIPE信号,进程会被终止。因此,为了避免进程因为SIGPIPE信号而终止,可以在程序中捕获SIGPIPE信号,或者在写入之前使用一些手段来检查连接状态。

现在再来修改一下服务端程序:

在这里插入图片描述

这里我们也把break注释掉,这样理论上这个recv函数会无限非阻塞地返回0,也就是服务端用于处理客户端的子进程会一直输出peer closed,同时客户端还会处于FIN_WAIT2状态。就是前面read/recv返回0的作用中的实验结果。

但现在,在服务端的recv返回0时,表示服务端已经收到了FIN信号,此时服务端使用send往客户端发送一个消息。现在会产生一个SIGPIPE信号,将服务端用于处理客户端的子进程杀掉。如下图所示,可以看到进程确实被杀掉了。

在这里插入图片描述

为了验证一下确实产生了SIGPIPE信号,我们自己来注册这个信号。程序做出如下修改:

在这里插入图片描述

结果如下:

在这里插入图片描述

不注册的话,内核默认会将程序杀掉,注册了这个信号的话,内核就不会杀掉进程,就由我们自己处理了。在这里由于服务端的socket_read中没有break掉,内核也没有杀掉这个子进程,所以这里recv将无限非阻塞返回0,加上在这个分支中一直调用send发送数据,所以服务端在这里无限输出peer closed,并无限收到SIGPIPE信号。


所以为了防止服务端程序在客户端被关闭后,由于程序之间没有及时的同步,导致服务端继续往客户端写数据,最后异常地被SIGPIPE信号关闭,我们可以忽略掉SIGPIPE信号以防止服务端被误关闭。

signal(SIGPIPE, SIG_IGN);

这篇关于Linux socket编程(5):三次握手和四次挥手分析和SIGPIPE信号的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/408546

相关文章

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

Linux磁盘分区、格式化和挂载方式

《Linux磁盘分区、格式化和挂载方式》本文详细介绍了Linux系统中磁盘分区、格式化和挂载的基本操作步骤和命令,包括MBR和GPT分区表的区别、fdisk和gdisk命令的使用、常见的文件系统格式以... 目录一、磁盘分区表分类二、fdisk命令创建分区1、交互式的命令2、分区主分区3、创建扩展分区,然后

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

Linux使用nohup命令在后台运行脚本

《Linux使用nohup命令在后台运行脚本》在Linux或类Unix系统中,后台运行脚本是一项非常实用的技能,尤其适用于需要长时间运行的任务或服务,本文我们来看看如何使用nohup命令在后台... 目录nohup 命令简介基本用法输出重定向& 符号的作用后台进程的特点注意事项实际应用场景长时间运行的任务服

什么是cron? Linux系统下Cron定时任务使用指南

《什么是cron?Linux系统下Cron定时任务使用指南》在日常的Linux系统管理和维护中,定时执行任务是非常常见的需求,你可能需要每天执行备份任务、清理系统日志或运行特定的脚本,而不想每天... 在管理 linux 服务器的过程中,总有一些任务需要我们定期或重复执行。就比如备份任务,通常会选在服务器资

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专