TCP状态转换详解

news2024/11/23 11:20:01

1.什么是TCP的状态转换

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层协议。在 TCP 连接的生命周期中,连接的状态会随着不同阶段的通信而发生变化,这些变化被称为状态转换。

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

2.三次握手的状态转换

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

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

第一次握手
  • 客户端:

    • 操作: 调用 connect() 函数发送 SYN 包(同步包)。
    • 状态变化: CLOSED -> SYN_SENT
  • 服务器:

    • 操作: 监听状态下收到客户端的 SYN 包。
    • 状态变化: LISTEN -> SYN_RCVD
第二次握手
  • 服务器:

    • 操作: 向客户端发送 SYN-ACK 包(同步确认包),确认收到客户端的 SYN 包,并请求建立连接。
    • 状态变化: SYN_RCVD(保持不变)
  • 客户端:

    • 操作: 收到服务器的 SYN-ACK 包,确认连接请求。
    • 状态变化: SYN_SENT -> ESTABLISHED
第三次握手
  • 客户端:

    • 操作: 向服务器发送 ACK 包(确认包),确认服务器的 SYN-ACK 包。
    • 状态变化: ESTABLISHED(保持不变)
  • 服务器:

    • 操作: 收到客户端的 ACK 包,确认建立连接。
    • 状态变化: SYN_RCVD -> ESTABLISHED

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

根据上面图片和描述,可以更直观地看到 TCP 三次握手过程中客户端和服务器的状态转换。通过三次握手,客户端和服务器从初始状态逐步进入 ESTABLISHED 状态,完成连接建立。

  • 客户端:

    • CLOSED -> SYN_SENT -> ESTABLISHED
  • 服务器:

    • LISTEN -> SYN_RCVD -> ESTABLISHED

3.四次挥手的状态转换

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

第一次挥手
  • 客户端:

    • 操作: 调用 close() 函数,将 TCP 协议中的 FIN 设置为 1,请求与服务器断开连接。
    • 状态变化: ESTABLISHED -> FIN_WAIT_1
  • 服务器:

    • 操作: 收到客户端的 FIN 包,表示客户端请求断开连接。
    • 状态变化: 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 -> CLOSED
  • 服务器:

    • 操作: 收到客户端的 ACK 包,确认客户端已同意断开连接。
    • 状态变化: LAST_ACK -> CLOSED

根据上面图片和描述,可以更直观地看到 TCP 四次挥手过程中客户端和服务器的状态转换。通过四次挥手,客户端和服务器从 ESTABLISHED 状态逐步进入 CLOSED 状态,完成连接断开。

  • 客户端:

    • ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
  • 服务器:

    • ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED

4.TCP状态转换图

在下图中同样是描述TCP通信过程中的客户端和服务器端的状态转,图片中主要关注两条主线:红色实线(客户端状态)和绿色虚线(服务器端状态)。黑色实线表示的是一些特殊情况下的状态切换,在此不做分析。因为三次握手是由客户端发起的,据此分析红色的实线表示的客户端的状态,绿色虚线表示的是服务器端的状态。

客户端状态转换

  1. 第一次握手:

    • 操作: 发送 SYN 包。
    • 状态变化: CLOSED -> SYN_SENT
  2. 第二次握手:

    • 操作: 收到服务器回复的 SYN-ACK 包。
    • 状态变化: SYN_SENT -> ESTABLISHED
  3. 主动断开连接,第一次挥手:

    • 操作: 发送 FIN 包,请求断开连接。
    • 状态变化: ESTABLISHED -> FIN_WAIT_1
  4. 第二次挥手:

    • 操作: 收到服务器的 ACK 包,确认服务器同意断开连接。
    • 状态变化: FIN_WAIT_1 -> FIN_WAIT_2
  5. 第三次挥手:

    • 操作: 收到服务器的 FIN 包,确认断开连接请求。
    • 状态变化: FIN_WAIT_2 -> TIME_WAIT
  6. 第四次挥手:

    • 操作: 回复 ACK 包给服务器,确认断开连接。等待 2 倍报文时长后,连接关闭。
    • 状态变化: TIME_WAIT -> CLOSED

服务器端状态转换

  1. 启动监听:

    • 操作: 服务器启动监听,等待客户端连接。
    • 状态变化: CLOSED -> LISTEN
  2. 第一次握手:

    • 操作: 收到客户端的 SYN 包。
    • 状态变化: LISTEN -> SYN_RCVD
  3. 第三次握手:

    • 操作: 收到客户端的 ACK 包,确认连接建立。
    • 状态变化: SYN_RCVD -> ESTABLISHED
  4. 收到断开连接请求,第一次挥手:

    • 操作: 收到客户端的 FIN 包,请求断开连接。
    • 状态变化: ESTABLISHED -> CLOSE_WAIT
  5. 第三次挥手:

    • 操作: 发送 FIN 包,请求与客户端断开连接。
    • 状态变化: CLOSE_WAIT -> LAST_ACK
  6. 第四次挥手:

    • 操作: 收到客户端的 ACK 包,确认断开连接。
    • 状态变化: LAST_ACK -> CLOSED

TIME_WAIT 状态的作用

在 TCP 通信中,主动断开连接的一方在收到被动断开连接一方发送的 FIN 包和最终的 ACK 包(即第三次挥手完成)后,必须处于 TIME_WAIT 状态并持续 2 倍 MSL(Maximum Segment Lifetime)时间。这么做的目的是:

  1. 确保 ACK 包能被正确接收:

    • 如果最终的 ACK 包丢失,服务器会重传 FIN 包,客户端在 TIME_WAIT 状态期间可以重新发送 ACK 包。
  2. 防止旧连接影响新连接:

    • TIME_WAIT 状态确保旧连接在消失前,有足够的时间让延迟的报文在网络中完全消失,避免旧连接的数据影响新连接。

5.Linux查看网络状态相关命令

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

6.半关闭

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

服务器端:

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

没有调用close(),客户端和服务器的连接还保持着

客户端可以给服务器发送数据,也可以接收服务器发送的数据 (但是,服务器已经丧失了发送数据的能力),因此客户端也只能发送数据,接收不到数据 –> 变成了写端

半关闭的工作机制

TCP 提供了一种半关闭机制,通过调用 shutdown() 函数来实现。shutdown() 函数允许应用程序分别关闭一个 TCP 连接的读或写操作,而不是完全关闭连接。

#include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd:套接字描述符。
how:
    SHUT_RD:关闭读取功能。
    SHUT_WR:关闭写入功能。
    SHUT_RDWR:同时关闭读取和写入功能。

半关闭的示例

以下是一个示例,展示了客户端如何实现半关闭,通知服务器它已经完成了数据发送,但仍然可以接收来自服务器的数据。

//服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置端口复用
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 监听套接字
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 接受连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
        perror("accept");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 接收来自客户端的数据
    read(new_socket, buffer, BUFFER_SIZE);
    printf("Server received: %s\n", buffer);

    // 发送数据给客户端
    const char *response = "Hello from server";
    send(new_socket, response, strlen(response), 0);

    // 继续接收来自客户端的数据
    memset(buffer, 0, BUFFER_SIZE);
    read(new_socket, buffer, BUFFER_SIZE);
    printf("Server received: %s\n", buffer);

    close(new_socket);
    close(server_fd);
    return 0;
}
//客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};

    // 创建套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将地址转换为二进制形式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    // 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    // 发送数据给服务器
    const char *message = "Hello from client";
    send(sock, message, strlen(message), 0);

    // 半关闭,关闭写入功能,但仍保持读取功能
    shutdown(sock, SHUT_WR);

    // 接收来自服务器的响应
    read(sock, buffer, BUFFER_SIZE);
    printf("Client received: %s\n", buffer);

    // 尝试再发送数据(会失败,因为写入功能已关闭)
    const char *another_message = "This will not be sent";
    ssize_t bytes_sent = send(sock, another_message, strlen(another_message), 0);
    if (bytes_sent == -1) {
        perror("send after shutdown");
    }

    close(sock);
    return 0;
}

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

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

相关文章

数据库最佳实践:优化爬虫管理的数据存储方案

摘要&#xff1a; 面对日益增长的数据抓取需求&#xff0c;如何高效管理和存储爬虫获取的海量信息成为一大挑战。本文将深入探讨数据库最佳实践&#xff0c;揭示如何通过优化策略提升爬虫数据存储效率&#xff0c;助您跨越数据管理的障碍&#xff0c;实现数据价值最大化。 一、…

虚拟试衣人像合成新SOTA!IMAGDressing-v1:ControlNet和IP-Adapter的最佳拍档

文章链接&#xff1a;https://arxiv.org/pdf/2407.12705 github链接&#xff1a;https://imagdressing.github.io/ Demo试用&#xff1a;https://sf.dictdoc.site/ 亮点直击 为商家引入了一项新的虚拟试衣&#xff08;VD&#xff09;任务&#xff0c;并设计了一个综合亲和力测量…

关闭 Linux 服务器上的 IPv6

虽然 IPv6 已经逐渐普及&#xff0c;但在某些 Linux 服务器上的业务系统仍然可能遇到一些奇怪的问题。特别是在集群场景中&#xff0c;因为集群各个节点之间需要互相通信&#xff0c;如果 IPv6 没有正确配置网络&#xff0c;可能导致一些未知问题&#xff0c;解决起来相当麻烦。…

acwing796-子矩阵的和-前缀和

s矩阵是全局变量&#xff0c;维度n*m,从1~n和 1~m存储元素【0】【0】~【0】【m】和【0】【0】~【n】【0】分别存储的都是0.s矩阵刚开始是存储输入的元素&#xff0c;后面用于存储前缀和。 s矩阵的意思是s【i】【j】表示从【0】【0】到【i】【j】为对角线的矩阵里面所有元素的和…

Pytorch的编译新特性TorchDynamo的工作原理和使用示例

在深度学习中&#xff0c;优化模型性能至关重要&#xff0c;特别是对于需要快速执行和实时推断的应用。而PyTorch在平衡动态图执行与高性能方面常常面临挑战。传统的PyTorch优化技术在处理动态计算图时效果有限&#xff0c;导致训练时间延长和模型性能不佳。TorchDynamo是一种为…

AI批量剪辑,批量发布大模型矩阵系统搭建开发

目录 前言 一、AI矩阵系统功能 二、AI批量剪辑可以解决什么问题&#xff1f; 总结&#xff1a; 前言 基于ai生成或剪辑视频的原理&#xff0c;利用ai将原视频进行混剪&#xff0c;生成新的视频素材。ai会将剪辑好的视频加上标题&#xff0c;批量发布到各个自媒体账号上。这…

[CP_AUTOSAR]_通信服务_CanTp模块(二)

目录 3、功能规范3.1、提供给上层的服务3.1.1、Initialization and shutdown3.1.2、Transmit request3.1.3、Transmit cancellation 3.2、提供给下层的服务3.2.1、Transmit confirmation3.2.2、Reception indication 3.3、内部行为3.3.1、N-SDU接收 在前面 《[CP_AUTOSAR]_通信…

一款异次元小清新风格的响应式wordpress个人博客主题

一款异次元小清新风格的响应式个人博客主题。这是一款专注于用户阅读体验的响应式 WordPress 主题&#xff0c;整体布局简洁大方&#xff0c;针对资源加载进行了优化。 Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净&#xff0c;简单且响应迅速的博客主题&#x…

go-微服务的设计概括

一、微服务到底是什么&#xff1f; 初学者很容易把微服务和分布式混为一谈&#xff0c;但其实二者之间存在非常大的差异&#xff0c;我个人认为主要有以下几点&#xff1a; 分布式主要是一种技术手段&#xff0c;用来保证多个相同的进程能够共同工作而不出错。采用各种复杂的…

修复公路 (最小生成树)

//新生训练 Input 4 4 1 2 6 1 3 4 1 4 5 4 2 3 Output 5 #include <iostream> #include <algorithm> #include <bits/stdc.h> using namespace std; typedef long long ll;struct road {int u,v;ll w;bool operator<(const road a)const{return w<a.w…

每日练习*

目录 一、选择题二、知识点1.中间件特点的描述1.1中间件的定义和作用1.2中间件的主要特点1.3中间件的应用场景1.4中间件的发展趋势 二、重写与重载总结![](https://i-blog.csdnimg.cn/direct/aa4190dfbd0e463294e41059016b8895.png) 一、选择题 题目选自牛客网 1.执行下列代码…

自动化测试 - selenium 环境搭建

在进行自动化测试时&#xff0c;Selenium 是一个非常强大的工具&#xff0c;在使用前需要做一些环境准备。 1. 配置 Chromedriver 访问 Chrome 浏览器的官方网站&#xff08;https://www.google.cn/chrome/&#xff09;&#xff0c;下载并安装 Chrome 浏览器。 接下来&#x…

Postman 集合变量的实用指南

在运用 Postman 进行 API 测试时&#xff0c;变量扮演着动态数据存储器的角色。它们作为键值对存在&#xff0c;其中“键”是变量的标识&#xff0c;而“值”则是存储在变量中的数据。这种机制不仅可以在多个 API 调用中重用数据&#xff0c;还有助于降低数据冗余&#xff0c;优…

【已解决】Linux(Centos7)中yum过程域名无法解析问题

问题原因 Linux中yum过程域名无法解析问题&#xff0c;但是ping 域名时联通的&#xff08;即DNS没问题&#xff09;&#xff0c;所以初步判断是镜像源的问题。 解决方法&#xff08;Centos7&#xff09; 1、备份/etc/yum.repos.d/CentOS-Base.repo 2、下载CentOS-Base.repo…

WebGL-编译报错,如何定位sendfile报错位置

1&#xff09;WebGL-编译报错&#xff0c;如何定位sendfile报错位置 2&#xff09;设置DepthBufferBits和设置DepthStencilFormat的区别 3&#xff09;Unity打包exe后&#xff0c;游戏内拉不起Steam的内购 4&#xff09;使用了Play Asset Delivery提交版本被Google报错 这是第3…

Nginx的HA高可用的搭建

1. 什么是高可用 高可用&#xff08;High Availability, HA&#xff09;是一种系统设计策略&#xff0c;旨在确保服务或应用在面对硬件故障、软件缺陷或任何其他异常情况时&#xff0c;仍能持续稳定地运行。它通过实现冗余性、故障转移、负载均衡、数据一致性、监控自动化、预防…

MySQL下载安装(保姆式教程)以及解决一些可能的问题

目录 MySQL的下载和安装 显示路径已经存在问题解决 注意&#xff1a; 端口被占用问题解决 注意&#xff1a; 服务名占用问题解决 注意&#xff1a; 应用配置问题解决 注意&#xff1a; MySQL环境配置 检查MySQL是否成功安装 如何将MySQL文件删除干净 MySQL的下载和安装 首先直接…

Python(字符串)

方法名描述说明 str.lower() 将str字符串全部转化为小写字母&#xff0c;结果为一个新的字符串str.upper()将str字符串全部转化为大写字母&#xff0c;结果为一个新的字符串str.split(sepNone)将str按照指定的分隔符sep分隔&#xff0c;结果为列表类型str.count(sub)结果为…

Golang | Leetcode Golang题解之第239题滑动窗口最大值

题目&#xff1a; 题解&#xff1a; func maxSlidingWindow(nums []int, k int) []int {n : len(nums)prefixMax : make([]int, n)suffixMax : make([]int, n)for i, v : range nums {if i%k 0 {prefixMax[i] v} else {prefixMax[i] max(prefixMax[i-1], v)}}for i : n - 1…

使用C#实现无人超市管理系统——数据结构课设(代码+PPT+说明书)

说明&#xff1a;这是自己做的课程设计作业&#xff0c;得分情况98/100 如果想要获取私信我 本项目采用线性表中的链表来进行本次系统程序的设计。链表分为两条线&#xff0c;分别是存储用户信息和商品信息&#xff0c;并且都设为公共属性&#xff0c;方便对用户信息和商品信息…