TCP shutdown 之后~

news2024/9/28 23:30:55

目录

摘要

1 API

2 shutdown(sockfd, SHUT_WR)

3 shutdown(sockfd, SHUT_WR)

4 kernel 是怎么做的?


摘要

        通过 shutdown() 关闭读写操作,会发生什么?具体点呢,考虑两个场景:

        场景一:C 发送数据完毕,想调用 shutdown 关闭写操作,这时候 TCP 数据包抓包是否可以看出 C 执行了这个操作;S 往 C 发数据后,C 是否还回 ACK?
        场景二:C 读取数据完毕,想调用 shutdown 关闭读操作,这时候 TCP 数据包抓包是否可以看出 C 执行了这个操作?S 继续往 C 发数据,整条 TCP 数据流会发生什么情况?

文中引用 Linux 内核源码基于版本 5.4.259,并做了一些删减以提高可读性。

1 API

        先来看下 shutdown 的接口:

NAME
       shutdown - shut down part of a full-duplex connection

SYNOPSIS
       #include <sys/socket.h>

       int shutdown(int sockfd, int how);

DESCRIPTION
       The  shutdown()  call causes all or part of a full-duplex connection on the socket associated with sockfd
       to be shut down.  If how is SHUT_RD, further receptions will be disallowed.  If how is  SHUT_WR,  further
       transmissions will be disallowed.  If how is SHUT_RDWR, further receptions and transmissions will be dis‐
       allowed.

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

        shutdown 接口比较简单,仅需只需要传入文件描述符与执行的动作这两个参数即可。

2 shutdown(sockfd, SHUT_WR)

        先来看场景一:C 发送数据完毕,想调用 shutdown 关闭写操作,这时候 TCP 数据包抓包是否可以看出 C 执行了这个操作;S 往 C 发数据后,C 是否还回 ACK?

        能否看出 C 执行了这个操作呢?那我们需要知道 close 的表现:如果是 close 一个 socket,相当于直接关闭了读写,发 FIN,后续收到包会回 RST。

        对于 shutdown 关闭写的场景,,只是关闭了写,那还是可以读的,所以 C 仍然会继续回 ACK,能否看出 C  执行了这个操作呢?关闭写应当会发送一个 FIN,而后续收到数据又会继续回 ACK, 所以应该是能区分出来才对?最好的方式就是写个代码验证了:

        搞一个 server:

void server_process(int sock)
{
    char buf[10240];

    while (1) {
        ssize_t ret = recv(sock, buf, sizeof(buf), 0);
        if (ret < 0) {
            if (errno != EAGAIN) {
                printf("server recv failed: %s\n", strerror(errno));
                break;
            }
            continue;
        } 

        if (ret == 0) {
            printf("read end!\n");
            break;
        }

        buf[ret] = 0;
        size_t ret_s = send(sock, buf, ret, 0);
        printf("resp:%s %d/%d\n", buf, ret_s, ret);
    }
}

int do_server()
{
    int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) {
        printf("server socket failed: %s\n", strerror(errno));
        return -1;
    }

    uint32_t ip;
    inet_aton(g_server_ip, (struct in_addr *)&ip);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip;
    addr.sin_port = htons(g_server_port);

    if (bind(sock, (struct sockaddr *)&addr, (socklen_t)sizeof(addr)) < 0) {
        printf("server bind failed: %s\n", strerror(errno));
        return -1;
    }

    if (listen(sock, 10) < 0) {
        printf("listen failed: %s\n", strerror(errno));
        return -1;
    }

    while (1) {
        int new_sock = accept(sock, NULL, NULL);
        if (new_sock < 0) {
            continue;
        }

        server_process(new_sock);
        close(new_sock);
    }

    return 0;
}

        有 server 必有 client:

int do_client()
{
    int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) {
        printf("client socket failed: %s\n", strerror(errno));
        return -1;
    }

    uint32_t ip;
    inet_aton(g_server_ip, (struct in_addr *)&ip);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip;
    addr.sin_port = htons(g_server_port);

    if (connect(sock, (struct sockaddr *)&addr, (socklen_t)sizeof(addr)) < 0) {
        printf("client connect failed: %s\n", strerror(errno));
        return -1;
    }

    char buf[10240];
    int i = 0;
    while (1) {
        int n = snprintf(buf, sizeof(buf), "echo %d", i++);
        size_t ret_s = send(sock, buf, n, 0);
        if (ret_s != n) {
            break;
        }

        //if (shutdown(sock, SHUT_RD) < 0) {
        //    printf("shutdown failed: %s\n", strerror(errno));
        //} 

        ssize_t ret = recv(sock, buf, sizeof(buf), 0);
        if (ret == 0) {
            printf("read end!\n");
            break;
        }
        if (ret < 0) {
            if (errno != EAGAIN) {
                printf("client recv failed: %s\n", strerror(errno));
                break;
            }
            continue;
        }

        buf[ret] = 0;
        printf("resp:%s %d/%d\n", buf, ret, n);
        sleep(1);
    }

    return 0;
}

        我们用 client 模拟角色 C,server 模拟角色 S,通过在 client 中添加 shutdown 调用复现场景。修改代码前,默认输出如下:

         我们在 client 发送后,shutdown 关闭写,并通过 sleep 阻塞住循环,观察输出与抓包结果:

    

        可以看出,client shutdown 关闭写发了一个 FIN,随后server 回了 length 6 的数据,并且 client 仍然继续响应了 ACK。所以是可以跟 close 关闭 socket 区分开的。

3 shutdown(sockfd, SHUT_WR)

        同样的,修改代码,client 发送之后关闭读,修改后 client 代码如下:

int do_client()
{
    int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) {
        printf("client socket failed: %s\n", strerror(errno));
        return -1;
    }

    uint32_t ip;
    inet_aton(g_server_ip, (struct in_addr *)&ip);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip;
    addr.sin_port = htons(g_server_port);

    if (connect(sock, (struct sockaddr *)&addr, (socklen_t)sizeof(addr)) < 0) {
        printf("client connect failed: %s\n", strerror(errno));
        return -1;
    }

    char buf[10240];
    int i = 0;
    while (1) {
        int n = snprintf(buf, sizeof(buf), "echo %d", i++);
        size_t ret_s = send(sock, buf, n, 0);
        if (ret_s != n) {
            break;
        }

        if (shutdown(sock, SHUT_RD) < 0) {
            printf("shutdown failed: %s\n", strerror(errno));
        } 

        /*ssize_t ret = recv(sock, buf, sizeof(buf), 0);
        if (ret == 0) {
            printf("read end!\n");
            break;
        }
        if (ret < 0) {
            if (errno != EAGAIN) {
                printf("client recv failed: %s\n", strerror(errno));
                break;
            }
            continue;
        }

        buf[ret] = 0;
        printf("resp:%s %d/%d\n", buf, ret, n);*/
        sleep(100);
    }

    return 0;
}

        直接看输出:

         数据流看不出变化,跟未执行 shutdown 关闭读操作的 TCP 流表现是一样的。

4 kernel 是怎么做的?

        直接看下 shutdown 的源码就知道了,用户层调用 shutdown,首先通过系统调用进来,随后调用到 inet 层的 inet_shutdown 函数:

// net/ipv4/af_inet.c
int inet_shutdown(struct socket *sock, int how)
{
	struct sock *sk = sock->sk;
	int err = 0;

	// 一些状态检查
	switch (sk->sk_state) {
	case TCP_CLOSE:
		err = -ENOTCONN;
		/* Hack to wake up other listeners, who can poll for
		   EPOLLHUP, even on eg. unconnected UDP sockets -- RR */
		/* fall through */
	default:
		sk->sk_shutdown |= how;
		if (sk->sk_prot->shutdown)
			sk->sk_prot->shutdown(sk, how);
		break;

	/* Remaining two branches are temporary solution for missing
	 * close() in multithreaded environment. It is _not_ a good idea,
	 * but we have no choice until close() is repaired at VFS level.
	 */
	case TCP_LISTEN:
		if (!(how & RCV_SHUTDOWN))
			break;
		/* fall through */
	case TCP_SYN_SENT:
		err = sk->sk_prot->disconnect(sk, O_NONBLOCK);
		sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
		break;
	}

	// 设置tcp连接状态, 比如从 estab -> fin_wait1
	sk->sk_state_change(sk);
	release_sock(sk);
	return err;
}

        我们看已连接场景下的流程,也就是 switch 中的 default分支,这里将 how 动作保存在了 shutdown 标记中,然后继续调用到 tcp 协议自己的 shutdown:

// net/ipv4/tcp.c
void tcp_shutdown(struct sock *sk, int how)
{
	if (!(how & SEND_SHUTDOWN))
		return;

	/* If we've already sent a FIN, or it's a closed state, skip this. */
	if ((1 << sk->sk_state) &
	    (TCPF_ESTABLISHED | TCPF_SYN_SENT |
	     TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
		/* Clear out any half completed packets.  FIN if needed. */
		if (tcp_close_state(sk))
			tcp_send_fin(sk);
	}
}

tcp_shutdown 中,首先判断不是关闭写的话,就直接 return 了,所以 shutdown 关闭读,真的就只是记录了一个标记,连 socket 状态也没有发生改变。如果是关闭写,则会走到 tcp_close_state、tcp_send_fin,其实就是将 state 转移到下一个状态,即 FIN_WAIT1:

static const unsigned char new_state[16] = {
  /* current state:        new state:      action:	*/
  [0 /* (Invalid) */]	= TCP_CLOSE,
  [TCP_ESTABLISHED]	= TCP_FIN_WAIT1 | TCP_ACTION_FIN,
  ...
};

static int tcp_close_state(struct sock *sk)
{
	int next = (int)new_state[sk->sk_state];
	int ns = next & TCP_STATE_MASK;

	tcp_set_state(sk, ns);

	return next & TCP_ACTION_FIN;
}

        看到这里,我们也能将原理同测试的现象对应起来了,也就那样~

最后附上完整的测试代码,有 linux 和 windows 的:

https://github.com/Fireplusplus/Linux/tree/master/tcp_shutdown

另外,windows 下关闭读的表现不太一样,C 继续收到数据会回 RST, 并且 C 继续 send 也会失败,真是无语!

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

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

相关文章

VBA技术资料MF184:图片导入Word添加说明文字设置格式

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

C ++初阶:C++入门级知识点

&#x1f37a;0.前言 言C之言&#xff0c;聊C之识&#xff0c;以C会友&#xff0c;共向远方。各位博友的各位你们好啊&#xff0c;这里是持续分享C知识的小赵同学&#xff0c;今天要分享的C知识是C入门知识点&#xff0c;在这一章&#xff0c;小赵将会向大家展开聊聊C入门知识…

基于Mediapipe的手势识别系统 | OpenCV | Mediapipe | C++ | QT | Python | C# | Unity

基于Mediapipe的手势识别系统 OpenCV、Mediapipe C (QT)、Python (PyCharm)、C# (Visual Studio) Unity 3D 登录界面 图片手势识别 视频文件手势识别 摄像头实时手势识别 演示视频 基于Mediapipe的手势识别系统

UDP和TCP协议段格式分析

目录 UDP协议 特点 UDP协议的缓冲区 UDP协议段格式 TCP协议 特点 如何理解TCP是传输控制协议&#xff1f; TCP协议段格式 四位首部长度 16位窗口大小 32位序号 32位确认序号 TCP/IP四层模型&#xff1a; UDP协议 UDP&#xff08;User Datagram Protocol &#xff…

十大护眼落地灯品牌哪款好?十大护眼落地灯品牌

十大护眼落地灯品牌哪款好&#xff1f;根据国际市场的研究数据表明&#xff0c;我国在日常生活中对电子产品的依赖度极高&#xff0c;每天看电子产品的时间超过8小时&#xff0c;出现眼睛酸痛、干涩、视觉疲劳的人群也不再少数&#xff0c;而给眼睛带来伤害的除了电子产品中所含…

界面控件DevExpress ASP.NET Web Forms v24.1最新版本系统环境配置要求

本文档包含有关安装和使用 DevExpress ASP.NET Web Forms控件的系统要求的信息。 点击获取DevExpress v24.1正式版 .NET Framework DevExpress ASP.NET Web Forms控件支持以下.NET框架版本。 如果您需要 DevExpress 产品的早期版本&#xff0c;请直接戳这里联系我>> …

MySQL中的EXPLAIN的详解

一、介绍 官网介绍&#xff1a; https://dev.mysql.com/doc/refman/5.7/en/explain-output.htmlhttps://dev.mysql.com/doc/refman/8.0/en/explain-output.htmlexplain&#xff08;执行计划&#xff09;&#xff0c;使用explain关键字可以模拟优化器执行sql查询语句&#xff…

爆火的本地知识库项目是什么?什么是RAG?本地知识库与大模型的关系

“ 本地知识库就相当于大模型的外部资料库。” 很多人应该都听过本地知识库项目&#xff0c;它是当今人工智能领域爆火的项目之一&#xff0c;那么到底什么是本地知识库&#xff1f;它和大模型有什么关系&#xff1f;怎么构建本地知识库&#xff1f; 01 — 为什么需要本地知…

Docker的介绍、保姆级安装和使用

一、Docker简介 1.1、Docker是什么 Docker是一个用于开发、发布和运行应用程序的开放平台;使您能够将应用程序与基础设施分离,以便您可以快速交付软件。不像虚拟机那样笨重(比如:我需要将一个安装好nginx环境的内容分享给其他人: 方式一【使用虚拟】(应用程序Nginx与基…

系统架构设计师 - 软件工程(2)

软件工程 软件工程&#xff08;13-22分&#xff09;非常重要软件系统建模系统设计界面设计 ★★软件设计结构化设计 ★★面向对象设计 ★★★★★基本过程设计原则设计模式创建型模式&#xff1a;创建对象结构型模式&#xff1a;更大的结构行为型模式&#xff1a;交互及职责分配…

四川财谷通信息技术有限公司抖音小店优势解析

在数字经济蓬勃发展的今天&#xff0c;电商平台如雨后春笋般涌现&#xff0c;其中&#xff0c;四川财谷通信息技术有限公司旗下的抖音小店凭借其独特的优势和强大的实力&#xff0c;在众多竞争者中脱颖而出&#xff0c;成为消费者和商家信赖的优选平台。本文将详细解析四川财谷…

Windows键快捷键大全

Windows键快捷键大全 Windows键结合其他键可以执行多种快捷操作&#xff0c;以下是一些常用的Windows键快捷键&#xff1a; Windows键 D: 显示或隐藏桌面。Windows键 E: 打开文件资源管理器。Windows键 L: 锁定电脑。Windows键 R: 打开运行对话框。Windows键 I: 打开Win…

Java中JDK动态代理

参考&#xff1a;疯狂Java讲义 第18章 文章目录 前言复杂度与耦合的矛盾 使用JDK动态代理总结 前言 复杂度与耦合的矛盾 开发实际应用的软件系统时&#xff0c;通常会存在相同代码段重复出现的情况&#xff0c;在这种情况下&#xff0c;一般都提取为一个方法&#xff0c;在不…

SOP企业内部推行:效率飙升100%,质量保障零瑕疵!

在企业的日常运营中&#xff0c;你是否经常遇到这样的问题&#xff1a;同样一项工作&#xff0c;不同的人做出来效果却大相径庭&#xff1f;或者&#xff0c;明明已经制定了工作流程&#xff0c;但执行起来却总是出现偏差&#xff0c;导致效率低下、质量不稳&#xff1f;这些问…

【STM32单片机_(HAL库)】3-2-2【中断EXTI】【电动车报警器项目】继电器定时开闭

1.硬件 STM32单片机最小系统继电器模块 2.软件 继电器模块alarm驱动文件添加GPIO常用函数main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "alarm.h"int main(void) {HAL_Init(); …

海外服务器和内地服务器有什么区别?

海外服务器和内地服务器在许多方面存在区别&#xff0c;主要包括以下几个方面&#xff1a; 1. 地理位置 海外服务器&#xff1a;位于中国大陆以外的地区&#xff0c;比如美国、欧洲、东南亚等地。常见的海外服务器提供商有Amazon Web Services&#xff08;AWS&#xff09;、Goo…

稚晖君发布5款全能人形机器人,开源创新,全能应用

8月18日&#xff0c;智元机器人举行“智元远征 商用启航” 2024年度新品发布会&#xff0c;智元联合创始人彭志辉主持并发布了“远征”与“灵犀”两大系列共五款商用人形机器人新品——远征A2、远征A2-W、远征A2-Max、灵犀X1及灵犀X1-W&#xff0c;并展示了在机器人动力、感知、…

【LLM之Base Model】Weaver论文阅读笔记

研究背景 当前的大型语言模型&#xff08;LLM&#xff09;如GPT-4等&#xff0c;尽管在普通文本生成中表现出色&#xff0c;但在创造性写作如小说、社交媒体内容等方面&#xff0c;往往不能很好地模仿人类的写作风格。这些模型在训练和对齐阶段&#xff0c;往往使用的是大规模…

Java | Leetcode Java题解之第347题前K个高频元素

题目&#xff1a; 题解&#xff1a; class Solution {public int[] topKFrequent(int[] nums, int k) {Map<Integer, Integer> occurrences new HashMap<Integer, Integer>();for (int num : nums) {occurrences.put(num, occurrences.getOrDefault(num, 0) 1);…

【layui】layer弹出图片层(开启图片旋转 放大 缩小 还原)

详细参照layui官网组件 弹出层组件 &#x1f525;Photots —————————————————————————— 弹出图片层&#xff08;开启图片旋转 放大 缩小 还原&#xff09;是layui2.8.16的新增功能&#xff0c; 新增 photos 层的鼠标滚轮缩放功能 是layui2.8.16的新增…