linuxTcp状态转换

news2024/11/17 4:49:59

1.TCP状态转换 

在TCP进行三次握手,或者四次挥手的过程中,通信的服务器和客户端内部会发送状态上的变化,发生的状态变化在程序中是看不到的,这个状态的变化也不需要程序猿去维护,但是在某些情况下进行程序的调试会去查看相关的状态信息,先来看三次握手过程中的状态转换。

1.1三次握手

在第一次握手之前,服务器端必须先启动,并且已经开始了监听
  - 服务器端先调用了 listen() 函数, 开始监听
  - 服务器启动监听前后的状态变化: 没有状态 ---> LISTEN

当服务器监听启动之后,由客户端发起的三次握手过程中状态转换如下:

第一次握手:

  • 客户端:调用了connect() 函数,状态变化:没有状态 -> SYN_SENT
  • 服务器:收到连接请求SYN,状态变化:LISTEN -> SYN_RCVD

第二次握手:

  • 服务器:给客户端回复ACK,并且请求和客户端建立连接,状态无变化,依然是 SYN_RCVD
  • 客户端:接收数据,收到了ACK,状态变化:SYN_SENT -> ESTABLISHED

第三次握手:

  • 客户端:给服务器回复ACK,同意建立连接,状态没有变化,还是 ESTABLISHED
  • 服务器:收到了ACK,状态变化:SYN_RCVD -> ESTABLISHED

三次握手完成之后,客户端和服务器都变成了同一种状态,这种状态叫:ESTABLISHED,表示双向连接已经建立, 可以通信了。在通过过程中,正常的通信状态就是 ESTABLISHED。

1.2四次挥手

关于四次挥手对于客户端和服务器哪段先断开连接没有要求,根据实际情况处理即可。下面根据上图中的实例描述一下四次挥手过程中TCP的状态转换(上图中主动断开连接的一方是客户端):

第一次挥手:

  • 客户端:调用close() 函数,将tcp协议中的FIN设置为1,请求和服务器断开连接,状态变化:ESTABLISHED -> FIN_WAIT_1
  • 服务器:收到断开连接请求,状态变化: ESTABLISHED -> CLOSE_WAIT

第二次挥手:

  • 服务器:回复ACK,同意断开连接的请求,状态没有变化,还是 CLOSE_WAIT
  • 客户端:收到ACK,状态变化:FIN_WAIT_1 -> FIN_WAIT_2

第三次挥手:

  • 服务器端:调用close() 函数,发送FIN给客户端,请求断开连接,状态变化:CLOSE_WAIT -> LAST_ACK
  • 客户端:收到FIN,状态变化:FIN_WAIT_2 -> TIME_WAIT

第四次挥手:

  • 客户端:回复ACK给服务器,状态是没有变化的,状态变化:TIME_WAIT -> 没有状态
  • 服务器端:收到ACK,双向连接断开,状态变化:LAST_ACK -> 无状态(没有了)

1.3状态转换

在下图中同样是描述TCP通信过程中的客户端和服务器端的状态转,看起来比较乱,其实只需要看两条主线:红色实线和绿色虚线。关于黑色的实线对应的是一些特殊情况下的状态切换,在此不做任何分析。

因为三次握手是由客户端发起的,据此分析红色的实线表示的客户端的状态,绿色虚线表示的是服务器端的状态。

客户端:

  • 第一次握手:发送SYN,没有状态 -> SYN_SENT
  • 第二次握手:收到回复的ACK,SYN_SENT -> ESTABLISHED主动断开连接,第一次挥手发送FIN,状态ESTABLISHED -> FIN_WAIT_1
  • 第二次挥手,收到ACK,状态FIN_WAIT_1 -> FIN_WAIT_2
  • 第三次挥手,收到FIN,状态FIN_WAIT_2 -> TIME_WAIT
  • 第四次挥手,回复ACK,等待2倍报文时长之后,状态TIME_WAIT -> 没有状态

服务器端:

  • 启动监听,没有状态 -> LISTEN
  • 第一次握手,收到SYN,状态LISTEN -> SYN_RCVD
  • 第三次握手,收到ACK,状态SYN_RCVD -> ESTABLISHED收到断开连接请求,第一次挥手状态 ESTABLISHED -> CLOSE_WAIT
  • 第三次挥手,发送FIN请求和客户端断开连接,状态CLOSE_WAIT -> LAST_ACK
  • 第四次挥手,收到ACK,状态LAST_ACK -> 无状态(没有了)

在TCP通信的时候,当主动断开连接的一方接收到被动断开连接的一方发送的FIN和最终的ACK后(第三次挥手完成),连接的主动关闭方必须处于TIME_WAIT状态并持续2MSL(Maximum Segment Lifetime)时间,这样就能够让TCP连接的主动关闭方在它发送的ACK丢失的情况下重新发送最终的ACK。

一倍报文寿命(MSL)大概时长为30s,因此两倍报文寿命一般在1分钟作用。

主动关闭方重新发送的最终ACK,是因为被动关闭方重传了它的FIN。事实上,被动关闭方总是重传FIN直到它收到一个最终的ACK。

1.4相关命令

$ netstat 参数
$ netstat -apn	| grep 关键字
  • 参数:
    • -a (all)显示所有选项
    • -p 显示建立相关链接的程序名
    • -n 拒绝显示别名,能显示数字的全部转化成数字。
    • -l 仅列出有在 Listen (监听) 的服务状态
    • -t (tcp)仅显示tcp相关选项
    • -u (udp)仅显示udp相关选项

2.半关闭

TCP连接只有一方发送了FIN,另一方没有发出FIN包,仍然可以在一个方向上正常发送数据,这中状态可以称之为半关闭或者半连接。当四次挥手完成两次的时候,就相当于实现了半关闭,在程序中只需要在某一端直接调用 close() 函数即可。套接字通信默认是双工的,也就是双向通信,如果进行了半关闭就变成了单工,数据只能单向流动了。比如下面的这个例子:

服务器端:

  • 调用了close() 函数,因此不能发数据,只能接收数据
  • 关闭了服务器端的写操作,现在只能进行读操作 –> 变成了读端

客户端:

  • 没有调用close(),客户端和服务器的连接还保持着
  • 客户端可以给服务器发送数据,也可以接收服务器发送的数据 (但是,服务器已经丧失了发送数据的能力),因此客户端也只能发送数据,接收不到数据 –> 变成了写端

按照上述流程做了半关闭之后,从双工变成了单工,数据单向流动的方向: 客户端 —–> 服务器端。

// 专门处理半关闭的函数
#include <sys/socket.h>
// 可以有选择的关闭读/写, close()函数只能关闭写操作
int shutdown(int sockfd, int how);
  • 参数:
    • sockfd: 要操作的文件描述符
    • how:
      • SHUT_RD: 关闭文件描述符对应的读操作
      • SHUT_WR: 关闭文件描述符对应的写操作
      • SHUT_RDWR: 关闭文件描述符对应的读写操作
  • 返回值:函数调用成功返回0,失败返回-1

3.端口复用

在网络通信中,一个端口只能被一个进程使用,不能多个进程共用同一个端口。我们在进行套接字通信的时候,如果按顺序执行如下操作:先启动服务器程序,再启动客户端程序,然后关闭服务器进程,再退出客户端进程,最后再启动服务器进程,就会出如下的错误提示信息:bind error: Address already in use

# 第二次启动服务器进程
$ ./server 
bind error: Address already in use

$ netstat -apn|grep 9999
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.1:9999          127.0.0.1:50178         TIME_WAIT   -   

通过netstat查看TCP状态,发现上一个服务器进程其实还没有真正退出。因为服务器进程是主动断开连接的进程, 最后状态变成了 TIME_WAIT状态,这个进程会等待2msl(大约1分钟)才会退出,如果该进程不退出,其绑定的端口就不会释放,再次启动新的进程还是使用这个未释放的端口,端口被重复使用,就是提示bind error: Address already in use这个错误信息。

如果想要解决上述问题,就必须要设置端口复用,使用的函数原型如下:

// 这个函数是一个多功能函数, 可以设置套接字选项
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  • 参数:
    • sockfd:用于监听的文件描述符
    • level:设置端口复用需要使用 SOL_SOCKET 宏
    • optname:要设置什么属性(下边的两个宏都可以设置端口复用)
      • SO_REUSEADDR
      • SO_REUSEPORT
    • optval:设置是去除端口复用属性还是设置端口复用属性,实际应该使用 int 型变量
      • 0:不设置
      • 1:设置
    • optlen:optval指针指向的内存大小 sizeof(int)

这个函数应该添加到服务器端代码中,具体应该放到什么位置呢?答:在绑定之前设置端口复用

  1. 创建监听的套接字
  2. 设置端口复用
  3. 绑定
  4. 设置监听
  5. 等待并接受客户端连接
  6. 通信
  7. 断开连接

参考代码如下:

#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>

// server
int main(int argc, const char* argv[])
{
    // 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket error");
        exit(1);
    }

    // 绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(9999);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 本地多有的IP
    // 127.0.0.1
    // inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
    
    // 设置端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 绑定端口
    int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }

    // 监听
    ret = listen(lfd, 64);
    if(ret == -1)
    {
        perror("listen error");
        exit(1);
    }

    fd_set reads, tmp;
    FD_ZERO(&reads);
    FD_SET(lfd, &reads);

    int maxfd = lfd;

    while(1)
    {
        tmp = reads;
        int ret = select(maxfd+1, &tmp, NULL, NULL, NULL);
        if(ret == -1)
        {
            perror("select");
            exit(0);
        }

        if(FD_ISSET(lfd, &tmp))
        {
            int cfd = accept(lfd, NULL, NULL);
            FD_SET(cfd, &reads);
            maxfd = cfd > maxfd ? cfd : maxfd;
        }
        for(int i=lfd+1; i<=maxfd; ++i)
        {
            if(FD_ISSET(i, &tmp))
            {
                char buf[1024];
                int len = read(i, buf, sizeof(buf));
                if(len > 0)
                {
                    printf("client say: %s\n", buf);
                    write(i, buf, len);
                }
                else if(len == 0)
                {
                    printf("客户端断开了连接\n");
                    FD_CLR(i, &reads);
                    close(i);
                }
                else
                {
                    perror("read");
                    exit(0);
                }
            }
        }
    }

    return 0;
}

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

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

相关文章

YB203H系列是一组CMOS技术实现的三端低功耗高电压稳压器

概述: YB203H系列是一组CMOS技术实现的三端低功耗高电压稳压器。输出电流为200mA且允许的输入电压可高达80V。具有几个固定的输出电压&#xff0c;范围从2.1V到12.0V.CMOS技术可确保其具有低压降和低静态电流的特性。最高耐压100W.尽管主要为固定电压调节器而设计&#xff0c;…

Spring过滤器和拦截器的区别

&#x1f4d1;前言 本文主要Spring过滤器和拦截器的区别的问题&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&#x…

【Linux】Linux进程间通信(三)

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;Linux &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【Linux】…

SMART PLC星三角延时启动功能块(梯形图FC)

这里我们介绍SMART PLC星三角延时启动功能块,SMART PLC的周期定时器功能块请参考下面文章链接: 周期定时器FB_Cycle_time(SCL+梯形图代码)-CSDN博客文章浏览阅读80次。博途PLC定时器指令使用详细介绍请参考下面文章链接:博途PLC IEC定时器编程应用(SCL语言)_scl定时器-CS…

机器学习第6天:线性回归模型正则化

文章目录 机器学习专栏 正则化介绍 岭回归 岭回归成本函数 核心代码 示例 Lasso回归 Lasso回归损失函数 核心代码 弹性网络 弹性网络成本函数 核心代码 结语 机器学习专栏 机器学习_Nowl的博客-CSDN博客 正则化介绍 作用&#xff1a;正则化是为了防止模型过拟合…

ESP32-0.96寸OLED通过低功耗蓝牙BLE通信显示出ESP32-BME280读取到的温湿度值

ESP32-0.96寸OLED通过低功耗蓝牙BLE通信显示出ESP32-BME280读取到的温湿度值 简介ESP32-BME280作为BLE Server手机作为BLE Client与ESP32-BME280 BLE Server通信ESP32-0.96寸OLED作为BLE Client与ESP32-BME280 BLE Server通信总结 简介 两个ESP32开发板&#xff0c;一个ESP32挂…

【Hello Go】Go语言复合类型

复合类型 分类指针基本操作new函数指针作为函数的参数 数组概述操作数据数组初始化数组比较在函数之间传递数组 slice概述切片的创建和初始化切片操作切片和底层数组关系内建函数appendcopy 切片作为函数传参 map概述创建和初始化常用操作赋值遍历 删除map作函数参数 结构体结构…

C语言青蛙爬井(ZZULIOJ1072:青蛙爬井)

题目描述 有一口深度为high米的水井&#xff0c;井底有一只青蛙&#xff0c;它每天白天能够沿井壁向上爬up米&#xff0c;夜里则顺井壁向下滑down米&#xff0c;若青蛙从某个早晨开始向外爬&#xff0c;对于任意指定的high、up和down值&#xff08;均为自然数&#xff09;&…

《rPPG》——(1)PyTorch——Windows环境配置

《rPPG》——&#xff08;1&#xff09;PyTorch——Windows环境配置 如何查看电脑是否已安装Python环境以及Python版本 anaconda对应python3.8的版本号是多少? 截止到我的知识截止日期&#xff08;2022年1月&#xff09;&#xff0c;Anaconda支持Python 3.8的版本号是Anacond…

【数字人】7、GeneFace++ | 使用声音和面部运动系数的关系作为 condition 来指导 NeRF 重建说话头

文章目录 一、背景二、相关工作2.1 唇形同步的 audio-to-motion2.2 真实人像渲染 三、方法3.1 对 GeneFace 的继承3.2 GeneFace 的结构3.2.1 Pitch-Aware Audio-to-Motion Transform3.2.2 Landmark Locally Linear Embedding3.2.3 Instant Motion-to-Video Rendering 四、效果 …

C语言百钱买百鸡(ZZULIOJ1074:百钱买百鸡)

题目描述 百钱买百鸡问题&#xff1a;公鸡五文钱一只&#xff0c;母鸡三文钱一只&#xff0c;小鸡三只一文钱&#xff0c;用100文钱买100只鸡&#xff0c;公鸡、母鸡、小鸡各买多少只&#xff1f; 本程序要求解的问题是&#xff1a;给定一个正整数n&#xff0c;用n文钱买n只鸡&…

CentOS Linux release 7.9.2009 (Core)中安装配置Tomcat

一、安装JDK 部分内容可以参考我这篇文章&#xff1a;Windows11与CentOS7下配置与检测JDK与Maven环境变量 中的 2.2 安装jdk-8u371-linux-x64.tar.gz和配置环境变量/etc/profile //1、安装redhat-lsb yum install -y redhat-lsb//2、查看系统版本信息 lsb_release -a //3、查…

【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏5(附项目源码,完结)

文章目录 最终效果前言简单绘制一下环境显示当前波数生成不同的敌人控制游戏运行速度游戏结束最终效果扩展源码完结最终效果 前言 本期是本项目的最后一篇,主要内容是配置环境、生成不同敌人、结束重开。 简单绘制一下环境 环境可以按自己喜好,去找一些瓦片,想怎么配置怎…

ExoPlayer架构详解与源码分析(9)——TsExtractor

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

智能驾驶汽车虚拟仿真视频数据理解(一)

赛题官网 datawhale 赛题介绍 跑通demo paddle 跑通demo torch 提交的障碍物取最主要的那个&#xff1f;不考虑多物体提交。障碍物&#xff0c;尽可能选择状态发生变化的物体。如果没有明显变化的&#xff0c;则考虑周边的物体。车的状态最后趋于减速、停止&#xff0c;时序…

python→函数曲线

CSDN中公式一栏&#xff0c;亦可以插入Latex函数。 以函数 为例 也可以用Latex写如下代码&#xff1a; \documentclass{article} \usepackage{amsmath} \begin{document} \[ y \frac{n}{n30} \] \end{document} 如下&#xff1a; 那么&#xff0c;该函数图像如何呢&#xf…

vue+element实现多级表头加树结构

标题两种展示方式 方式一 完整代码: <template><div class"box"><el-tableref"areaPointTable":data"tableData"border:span-method"objectSpanMethod":header-cell-style"tableHeaderMerge"><el-ta…

数据库编程sqlite3库安装及使用

数据库编程 数据库的概念 数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。 数据库是存放数据的仓库。它的存储空间很大&#xff0c;可以存放百万条、千万条、上亿条数据。但是数据库并不是…

一起学docker系列之五docker的常用命令--操作容器的命令

目录 前言1 启动容器2 查看容器3 退出容器4 启动已经停止的容器5 重启容器6 停止容器7 删除已经停止的容器8 启动容器说明和举例9 查看容器日志10 查看容器内运行的进程11 查看容器内部细节12 进入正在运行的容器并进行交互13 导入和导出容器结语 前言 当涉及到容器化技术&…

Java学习之路 —— 网络通信

文章目录 1. InetAddress2. UDP3. TCP4. 总结 1. InetAddress InetAddress的常用方法如下&#xff1a; public class InetAddressDemo {public static void main(String[] args) throws Exception{// 1. 获取本机IP地址对象InetAddress ip1 InetAddress.getLocalHost();Sys…