优雅/粗暴地关闭TCP连接--close-shutdown的选择

2023-11-13

     一个 TCP 连接需要经过三次握手进入数据传输阶段,最后来到连接关闭阶段。在最后的连接关闭阶段,我们需要重点关注的是“半连接”状态。

     因为 TCP 是双向的,这里说的方向,指的是数据流的写入 - 读出的方向。

比如客户端到服务器端的方向,指的是客户端通过套接字接口,向服务器端发送 TCP 报文;而服务器端到客户端方向则是另一个传输方向。在绝大数情况下,TCP 连接都是先关闭一个方向,此时另外一个方向还是可以正常进行数据传输。

举个例子,客户端主动发起连接的中断,将自己到服务器端的数据流方向关闭,此时,客户端不再往服务器端写入数据,服务器端读完客户端数据后就不会再有新的报文到达。但这并不意味着,TCP 连接已经完全关闭,很有可能的是,服务器端正在对客户端的最后报文进行处理,比如去访问数据库,存入一些数据;或者是计算出某个客户端需要的值,当完成这些操作之后,服务器端把结果通过套接字写给客户端,我们说这个套接字的状态此时是“半关闭”的。最后,服务器端才有条不紊地关闭剩下的半个连接,结束这一段 TCP 连接的使命。

当然,我这里描述的是服务器端“优雅”地关闭了连接。如果服务器端处理不好,就会导致最后的关闭过程是“粗暴”的,达不到我们上面描述的“优雅”关闭的目标,形成的后果,很可能是服务器端处理完的信息没办法正常传送给客户端,破坏了用户侧的使用场景。接下来我们就来看看关闭连接时,都有哪些方式呢?

close 函数

首先,我们来看最常见的 close 函数:


int close(int sockfd)

这个函数很简单,对已连接的套接字执行 close 操作就可以,若成功则为 0,若出错则为 -1。

这个函数会对套接字引用计数减一,一旦发现套接字引用计数到 0,就会对套接字进行彻底释放,并且会关闭 TCP 两个方向的数据流。

套接字引用计数是什么意思呢?因为套接字可以被多个进程共享,你可以理解为我们给每个套接字都设置了一个积分,如果我们通过 fork 的方式产生子进程,套接字就会积分 +1, 如果我们调用一次 close 函数,套接字积分就会 -1。这就是套接字引用计数的含义。

close 函数具体是如何关闭两个方向的数据流呢?

在输出方向,系统内核会将该套接字设置为不可读,任何读操作都会返回异常。

在输入方向,系统内核尝试将发送缓冲区的数据发送给对端,并最后向对端发送一个 FIN 报文,接下来如果再对该套接字进行写操作会返回异常。

如果对端没有检测到套接字已关闭,还继续发送报文,就会收到一个 RST 报文,告诉对端:“Hi, 我已经关闭了,别再给我发数据了。”

   在套接字上调用close()会将双向通信通道的两端都关闭,有时候,只关闭连接的一端也是有用处的,这样数据只能在一个方向上通过套接字传输。系统调用shutdown()提供了这种功能。

shutdown 函数

shutdown 函数的原型是这样的:


int shutdown(int sockfd, int howto)

对已连接的套接字执行 shutdown 操作,若成功则为 0,若出错则为 -1。

howto 是这个函数的设置选项,它的设置有三个主要选项:

1、SHUT_RD(0):关闭连接的“读”这个方向,对该套接字进行读操作直接返回 EOF。从数据角度来看,套接字上接收缓冲区已有的数据将被丢弃,如果再有新的数据流到达,会对数据进行 ACK,然后悄悄地丢弃。也就是说,对端还是会接收到 ACK,在这种情况下根本不知道数据已经被丢弃了。

2、SHUT_WR(1):关闭连接的“写”这个方向,这就是常被称为”半关闭“的连接。此时,不管套接字引用计数的值是多少,都会直接关闭连接的写方向。套接字上发送缓冲区已有的数据将被立即发送出去,并发送一个 FIN 报文给对端。应用程序如果对该套接字进行写操作会报错。

3、SHUT_RDWR(2):相当于 SHUT_RD 和 SHUT_WR 操作各一次,关闭套接字的读和写两个方向。

讲到这里,不知道你是不是有和我当初一样的困惑,使用 SHUT_RDWR 来调用 shutdown 不是和 close 基本一样吗,都是关闭连接的读和写两个方向。

其实,这两个还是有差别的。

第一个差别:close 会关闭连接,并释放所有连接对应的资源,而 shutdown 并不会释放掉套接字和所有的资源。

第二个差别:close 存在引用计数的概念,并不一定导致该套接字不可用;shutdown 则不管引用计数,直接使得该套接字不可用,如果有别的进程企图使用该套接字,将会受到影响。

第三个差别:close 的引用计数导致不一定会发出 FIN 结束报文,而 shutdown 则总是会发出 FIN 结束报文,这在我们打算关闭连接通知对端的时候,是非常重要的。

无论该套接字上是否还关联其它的文件描述符,shutdown()都会关闭套接字通道。(换句话说,shutdown()是根据打开的文件描述符(open file description)来执行操作,而同文件描述符无关)例如,假设sockfd指向一个已经连接的流失套接字,如果执行下列调用,那么连接依然会保持打开状态,我们依然可以通过文件描述符fd2在该链接上做I/O操作。

fd2 = dup(sockfd);

close(sockfd);

但是,如果执行如下的调用,那么该链接的双向通道都会关闭,通过fd2也无法在执行I/O操作了。

fd = dup(sockfd);

shutdown(sockfd,SHUT_RDWR);

 

举例体会 close 和 shutdown 的差别

下面,我们通过构建一组客户端和服务器程序,来进行 close 和 shutdown 的实验。

客户端程序,从标准输入不断接收用户输入,把输入的字符串通过套接字发送给服务器端,同时,将服务器端的应答显示到标准输出上。

如果用户输入了“close”,则会调用 close 函数关闭连接,休眠一段时间,等待服务器端处理后退出;如果用户输入了“shutdown”,调用 shutdown 函数关闭连接的写方向,注意我们不会直接退出,而是会继续等待服务器端的应答,直到服务器端完成自己的操作,在另一个方向上完成关闭。

在这里,我们会第一次接触到 select 多路复用,这里不展开讲,你只需要记住,使用 select 使得我们可以同时完成对连接套接字和标准输入两个 I/O 对象的处理。


# include "lib/common.h"
# define    MAXLINE     4096

int main(int argc, char **argv) {
    if (argc != 2) {
        error(1, 0, "usage: graceclient <IPaddress>");
    }
    
    int socket_fd;
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, argv[1], &server_addr.sin_addr);

    socklen_t server_len = sizeof(server_addr);
    int connect_rt = connect(socket_fd, (struct sockaddr *) &server_addr, server_len);
    if (connect_rt < 0) {
        error(1, errno, "connect failed ");
    }

    char send_line[MAXLINE], recv_line[MAXLINE + 1];
    int n;

    fd_set readmask;
    fd_set allreads;

    FD_ZERO(&allreads);
    FD_SET(0, &allreads);
    FD_SET(socket_fd, &allreads);
    for (;;) {
        readmask = allreads;
        int rc = select(socket_fd + 1, &readmask, NULL, NULL, NULL);
        if (rc <= 0)
            error(1, errno, "select failed");
        if (FD_ISSET(socket_fd, &readmask)) {
            n = read(socket_fd, recv_line, MAXLINE);
            if (n < 0) {
                error(1, errno, "read error");
            } else if (n == 0) {
                error(1, 0, "server terminated \n");
            }
            recv_line[n] = 0;
            fputs(recv_line, stdout);
            fputs("\n", stdout);
        }
        if (FD_ISSET(0, &readmask)) {
            if (fgets(send_line, MAXLINE, stdin) != NULL) {
                if (strncmp(send_line, "shutdown", 8) == 0) {
                    FD_CLR(0, &allreads);
                    if (shutdown(socket_fd, 1)) {
                        error(1, errno, "shutdown failed");
                    }
                } else if (strncmp(send_line, "close", 5) == 0) {
                    FD_CLR(0, &allreads);
                    if (close(socket_fd)) {
                        error(1, errno, "close failed");
                    }
                    sleep(6);
                    exit(0);
                } else {
                    int i = strlen(send_line);
                    if (send_line[i - 1] == '\n') {
                        send_line[i - 1] = 0;
                    }

                    printf("now sending %s\n", send_line);
                    size_t rt = write(socket_fd, send_line, strlen(send_line));
                    if (rt < 0) {
                        error(1, errno, "write failed ");
                    }
                    printf("send bytes: %zu \n", rt);
                }

            }
        }

    }

}

我对这个程序的细节展开解释一下:

第一部分是套接字的创建和 select 初始工作:

9-10 行创建了一个 TCP 套接字;

12-16 行设置了连接的目标服务器 IPv4 地址,绑定到了指定的 IP 和端口;

18-22 行使用创建的套接字,向目标 IPv4 地址发起连接请求;

30-32 行为使用 select 做准备,初始化描述字集合,这部分我会在后面详细解释,这里就不再深入。

第二部分是程序的主体部分,从 33-80 行, 使用 select 多路复用观测在连接套接字和标准输入上的 I/O 事件,其中:

38-48 行:当连接套接字上有数据可读,将数据读入到程序缓冲区中。

40-41 行,如果有异常则报错退出;

42-43 行如果读到服务器端发送的 EOF 则正常退出。

49-77 行:当标准输入上有数据可读,读入后进行判断。如果输入的是“shutdown”,则关闭标准输入的 I/O 事件感知,并调用 shutdown 函数关闭写方向;如果输入的是”close“,则调用 close 函数关闭连接;

64-74 行处理正常的输入,将回车符截掉,调用 write 函数,通过套接字将数据发送给服务器端。

服务器端程序稍微简单一点,连接建立之后,打印出接收的字节,并重新格式化后,发送给客户端。

服务器端程序有一点需要注意,那就是对 SIGPIPE 这个信号的处理。后面我会结合程序的结果展开说明。


#include "lib/common.h"

static int count;

static void sig_int(int signo) {
    printf("\nreceived %d datagrams\n", count);
    exit(0);
}

int main(int argc, char **argv) {
    int listenfd;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERV_PORT);

    int rt1 = bind(listenfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
    if (rt1 < 0) {
        error(1, errno, "bind failed ");
    }

    int rt2 = listen(listenfd, LISTENQ);
    if (rt2 < 0) {
        error(1, errno, "listen failed ");
    }

    signal(SIGINT, sig_int);
    signal(SIGPIPE, SIG_IGN);

    int connfd;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    if ((connfd = accept(listenfd, (struct sockaddr *) &client_addr, &client_len)) < 0) {
        error(1, errno, "bind failed ");
    }

    char message[MAXLINE];
    count = 0;

    for (;;) {
        int n = read(connfd, message, MAXLINE);
        if (n < 0) {
            error(1, errno, "error read");
        } else if (n == 0) {
            error(1, 0, "client closed \n");
        }
        message[n] = 0;
        printf("received %d bytes: %s\n", n, message);
        count++;

        char send_line[MAXLINE];
        sprintf(send_line, "Hi, %s", message);

        sleep(5);

        int write_nc = send(connfd, send_line, strlen(send_line), 0);
        printf("send bytes: %zu \n", write_nc);
        if (write_nc < 0) {
            error(1, errno, "error write");
        }
    }
}

服务器端程序的细节也展开解释一下:

第一部分是套接字和连接创建过程:

11-12 行创建了一个 TCP 套接字;

14-18 行设置了本地服务器 IPv4 地址,绑定到了 ANY 地址和指定的端口;

20-40 行使用创建的套接字,以此执行 bind、listen 和 accept 操作,完成连接建立。第二部分是程序的主体,通过 read 函数获取客户端传送来的数据流,并回送给客户端:

51-52 行显示收到的字符串,在 56 行对原字符串进行重新格式化,之后调用 send 函数将数据发送给客户端。注意,在发送之前,让服务器端程序休眠了 5 秒,以模拟服务器端处理的时间。我们启动服务器,再启动客户端,依次在标准输入上输入 data1、data2 和 close,观察一段时间后我们看到:


$./graceclient 127.0.0.1
data1
now sending data1
send bytes:5
data2
now sending data2
send bytes:5
close

$./graceserver
received 5 bytes: data1
send bytes: 9
received 5 bytes: data2
send bytes: 9
client closed

客户端依次发送了 data1 和 data2,服务器端也正常接收到 data1 和 data2。在客户端 close 掉整个连接之后,服务器端接收到 SIGPIPE 信号,直接退出。客户端并没有收到服务器端的应答数据。

我在下面放了一张图,这张图详细解释了客户端和服务器端交互的时序图。因为客户端调用 close 函数关闭了整个连接,当服务器端发送的“Hi, data1”分组到底时,客户端给回送一个 RST 分组;服务器端再次尝试发送“Hi, data2”第二个应答分组时,系统内核通知 SIGPIPE 信号。这是因为,在 RST 的套接字进行写操作,会直接触发 SIGPIPE 信号。

这回知道你的程序莫名其妙终止的原因了吧。

我们可以像这样注册一个信号处理函数,对 SIGPIPE 信号进行处理,避免程序莫名退出:


static void sig_pipe(int signo) {
    printf("\nreceived %d datagrams\n", count);
    exit(0);
}
signal(SIGINT, sig_pipe);

接下来,再次启动服务器,再启动客户端,依次在标准输入上输入 data1、data2 和 shutdown 函数,观察一段时间后我们看到:


$./graceclient 127.0.0.1
data1
now sending data1
send bytes:5
data2
now sending data2
send bytes:5
shutdown
Hi, data1
Hi,data2
server terminated

$./graceserver
received 5 bytes: data1
send bytes: 9
received 5 bytes: data2
send bytes: 9
client closed

和前面的结果不同,服务器端输出了 data1、data2;客户端也输出了“Hi,data1”和“Hi,data2”,客户端和服务器端各自完成了自己的工作后,正常退出。

我们再看下客户端和服务器端交互的时序图。因为客户端调用 shutdown 函数只是关闭连接的一个方向,服务器端到客户端的这个方向还可以继续进行数据的发送和接收,所以“Hi,data1”和“Hi,data2”都可以正常传送;当服务器端读到 EOF 时,立即向客户端发送了 FIN 报文,客户端在 read 函数中感知了 EOF,也进行了正常退出。

总结

在这一讲中,我们讲述了 close 函数关闭连接的方法,使用 close 函数关闭连接有两个需要明确的地方。close 函数只是把套接字引用计数减 1,未必会立即关闭连接;close 函数如果在套接字引用计数达到 0 时,立即终止读和写两个方向的数据传送。基于这两个确定,在期望关闭连接其中一个方向时,应该使用 shutdown 函数。

思考题

和往常一样,给你留两道思考题。

第一道题,你可以看到在今天的服务器端程序中,直接调用exit(0)完成了 FIN 报文的发送,这是为什么呢?为什么不调用 close 函数或 shutdown 函数呢?

第二道题关于关于信号量处理,今天的程序中,使用的是SIG_IGN默认处理,你知道默认处理和自定义函数处理的区别吗?不妨查查资料,了解一下。欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起来交流。

 

问题一,为什么调用exit以后不需要调用close,shutdown?因为在调用exit之后进程会退出,而进程相关的所有的资源,文件,内存,信号等内核分配的资源都会被释放,在linux中,一切皆文件,本身socket就是一种文件类型,内核会为每一个打开的文件创建file结构并维护指向改结构的引用计数,每一个进程结构中都会维护本进程打开的文件数组,数组下标就是fd,内容就指向上面的file结构,close本身就可以用来操作所有的文件,做的事就是,删除本进程打开的文件数组中指定的fd项,并把指向的file结构中的引用计数减一,等引用计数为0的时候,就会调用内部包含的文件操作close,针对于socket,它内部的实现应该就是调用shutdown,只是参数是关闭读写端,从而比较粗暴的关闭连接。
第二个问题,信号的处理有三种,默认处理,忽略处理,自定义处理。默认处理就是采用系统自定义的操作,大部分信号的默认处理都是杀死进程,忽略处理就是当做什么都没有发生。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

优雅/粗暴地关闭TCP连接--close-shutdown的选择 的相关文章

  • Nginx常见模块

    nginx常见模块 4 Nginx常见模块 4 1 四层访问控制 访问控制基于模块ngx http access module实现 可以通过匹配客户端源IP地址进行限制 环境配置 注意 如果能在防火墙设备控制 最好就不要在nginx上配置
  • 深度学习项目,使用python进行表情识别,pytorch应用

    文章目录 前言 一 深度学习是什么 二 数据的预处理 1 数据分类 2 代码 三 构建模型与训练 1 模型与代码 2 使用方法 四 实时识别 总结 前言 这个项目是以前课设用到的功能之一 参考了其他人的人的博客 自己做了一下整理 需要用到的
  • Intra ERC Scheme

    Iterative Method First initial the corrupted MB with neighboring MB information then use iterative techniques to conceal
  • 机器学习中梯度下降法和牛顿法的比较

    在机器学习的优化问题中 梯度下降法和牛顿法是常用的两种凸函数求极值的方法 他们都是为了求得目标函数的近似解 在逻辑斯蒂回归模型的参数求解中 一般用改良的梯度下降法 也可以用牛顿法 由于两种方法有些相似 我特地拿来简单地对比一下 下面的内容需
  • Linux日志分析工具之AWStats

    Linux日志分析工具之AWStats 博客主页 微笑的段嘉许博客主页 欢迎关注 点赞 收藏 留言 本文由微笑的段嘉许原创 CSDN首发时间 2023年2月3日 坚持和努力一定能换来诗与远方 作者水平很有限 如果发现错误 一定要及时告知作者
  • python单元测试之pytest

    前提 需要安装pytest和pytest html 安装pytest 在控制台输入 命令 pip install pytest 进行下载安装 安装pytest html 在控制台输入 命令 pip install pytest html 进
  • syntax error near unexpected token `newline'脚本无法执行

    问题描述 执行run sh脚本是报错 报错信息如下 data app information provider No such file or directory command not found run sh line 4 syntax
  • python 多并发竞争微信token刷新问题的解决方案

    看日志 正常时候的日志 2017 09 24 07 35 30 723 views py line 34 INFO GetToken from Redis 2017 09 24 07 35 31 342 views py line 24 I
  • Windows Visual Studio Fortran MS-MPI配置

    0 Visual Studio及 Intel Fortran版本信息 Windows版本为Windows11 visual studio 版本采用的是Visual Studio 2019 版本 16 11 Intel Fortran 采用的
  • Unity2018.3 笔记 ——prefab解除绑定

    Unity2018 3可以对Prefab 进行嵌套操作 并且许多操作都要在Prefab Editor下进行 但是在Prefab的操作会影响到所有预制体 如果只是想对某一个Prefab删除特定的对象 就要解除对其原有Prefab的内容进行解绑
  • 电脑或网页打开很慢是什么原因?

    网页打开很慢是什么原因 一 电脑的配置太低会 电源里调为高性能 电脑配置太低是导致打开网页速度过慢的一个根本的原因 首先我们可以查看自己电脑配置到底如何 这样就可以查看到电脑的CPU和内存两者所占用率是怎样的 如果两者占有的比例都是很高的
  • java——多线程

    文章目录 Java 的并发基础知识 1 创建线程 2 同步方法和同步代码块 3 线程安全的容器 4 volatile 关键字 5 Lock 和 Condition 接口 Java 多线程编程的基本框架 1 创建和启动线程 2 线程的状态转换
  • 深入了解struts中的struts-config.xml

    弄清楚struts config xml中各项元素的作用 对于我们构建web项目有莫大的好处
  • 拉取Docker镜像时提示 no space left on device 问题解决

    问题原因 出现此问题一般是 docker 根目录空间不足导致 解决方法 修改其 Docker Root Dir 的值 指向一个更大空间的目录 方法一步骤 使用软链接 我们知道在操作系统当中 默认情况下 Docker 容器的存放位置在 var
  • Maven打包详解(“There are test failures”)

    Maven打包详解 There are test failures 以下操作都是在IDEA软件内进行的 第一步 idea界面中选择右边的Maven projects 界面 第二步 选择目标项目文件下的Lifecycle目录下的clean 双
  • 请用前端写一段计算请假时间的代码

    当然可以 首先 我们需要先确定请假时间所需要的信息 比如请假的开始时间和结束时间 然后我们可以使用 JavaScript 的 Date 对象来获取这些信息 并使用 Date getTime 方法来获取它们的毫秒表示形式 接下来就可以计算出两
  • 购物H5商城架构运维之路

    一 引言 公司属于旅游行业 需要将旅游 酒店 购物 聚合到线上商城 通过对会员数据进行聚合 形成大会员系统 从而提供统一的对客窗口 二 业务场景 围绕更加有效地获取用户 提升用户的LTV Life Time Value 生命周期总价值 的整
  • 软件设计师笔记之操作系统基础知识

    操作系统基础知识 考点梳理 操作系统作为计算机科学最为基本的理论基础和分支领域之一是软考中必须重点掌握的知识 1 操作系统的内核 2 操作系统的五大管理功能 进程管理 存储管理 设备管理 文件管理 作业管理 3 网络操作系统和嵌入式操作系统
  • 2020-11-07 layui图片层实现查看大图效果

    之前主要使用element ui和 ant design 框架来实现前端页面 后来在公司需要使用layui开发 一开始接触这个框架感觉美观程度不如之前用的element ui和antd 觉得layui不好用 比较抵触这个框架 后来用多了发现

随机推荐

  • 部署代码生成模型CodeGeeX2

    模型下载 大家需要从huggingface下载CodeGeex2的模型文件 可以直接下载到本地 也可以git clone 这里给huggingface的地址 大家自行下载 THUDM codegeex2 6b Hugging Face 模型
  • pytorch加载保存查看checkpoint文件

    參考以下 https blog csdn net joyce peng article details 104133594 ps 有大佬有CSDN转发博客的实操经验的麻烦分享下
  • GPU服务器上的tensorrt环境搭建

    GPU服务器上的tensorrt环境搭建 文章目录 GPU服务器上的tensorrt环境搭建 1 拉取docker服务器上的支持cuda cudnn的镜像文件 2 设置环境变量并下载相关依赖 2 1安装cuda和cudnn 2 2 pyto
  • python中 什么是描述符?

    文章目录 什么是描述符 动态查找 管理属性 set name 魔术方法 描述符定义 数据验证模块 自定义验证 Goods类的验证 总结 参考文档 什么是描述符 什么是描述符 描述符 是python中一个高级的特性 简单来说 是用来控制 对象
  • 最大化印刷MES管理系统价值,提升印刷车间效率与质量

    印刷MES管理系统 既是对ERP计划及车间现场管理的实时反馈及响应 更是制造执行过程中人 机 料 法 环等资源相互作用 相互配置的最优方案监控 可以让企业获得更多 更有利 更实用的生产制造数据 在这个过程当中 提升效率和质量 降低风险就是印
  • 30天自制操作系统(第02天)–汇编语言学习和MakeFile入门

    第二天 我们将之前的程序进一步翻译成汇编语言 进一步了解启动程序到底做了些啥 还记得第一天中有一部分程序主体么 以及最开头的代码 我们把它们翻译成汇编看看 hello os TAB 4 ORG 0x7c00 指名程序的装载地址 以下一段是标
  • 关于使用快慢指针进行单向链表环存在性判定的若干问题的进一步思考

    文章目录 面试官问这个问题的目的是什么 快慢指针的工作原理 快慢指针究竟几步可以定位环 快指针的速度只能是双倍速么 其他判定单向链表环路的方法 小结 费曼算法 1 写下这个问题 2 真正地努力思考 3 写下解决办法 物理学家Murray G
  • 苹果手机怎样批量删除照片

    https jingyan baidu com article 851fbc37953b183e1f15ab02 html 喜欢使用苹果手机拍照的朋友 会经常删照片 那怎样操作更快呢 今天就为大家分享苹果手机怎样批量删除照片 工具 原料 苹
  • git命令详解

    一 简介 git作为应用广泛的一种分布式版本控制系统 其与svn比较最大的差别就是一个是分布式 一个是集中式 git在每个开发者的本地有一个完整的版本库 当在本地处理工作时 无需联网便可修改提交 当需要与其它开发者交互时 只需要提交自己的修
  • 陀螺研究院×BSN丨解析区块链视角下的消费者权益保护访谈全文发布

    3月是我国消费者权益保护月 在近日播出的3 15晚会中 央视曝光了科勒卫浴 宝马 Max Mara多家知名商店安装人脸识别摄像头 手机清理软件泄露老人隐私 瘦肉精羊等多个极其恶劣的消费者权益侵害行为 可以看出 随着数据时代的渐行渐近 消费者
  • switch怎么切换服务器账号,任天堂eshop如何换区 switch账号如何切换其他服地区

    任天堂eshop里面的游戏因为注册地区不同 游戏的售价也相应不同 因为汇率的影响 游戏的售价产生了高低差价 这种差价可以让玩家买到较低价的游戏 操作起来方法就是在switch里进行账号切换 怎样切换任天堂账号地区呢 一起来看下吧 1 首先需
  • /usr/lib64/sa/sa1脚本解释

    usr lib64 sa sa1脚本解释 前言 脚本原文 脚本解释 附 前言 这个脚本是 Linux 系统上的 sysstat 工具的一部分 在 etc cron d sysstat这个定时任务下执行 用来收集系统性能数据 需要配合 etc
  • 休眠后网络无法自动连接——网卡属性没有电源管理选项

    问题描述 1 每次休眠过后网卡都是无法连接网络的状态 需要手动禁用 gt 开启网卡后才会恢复正常 2 同时网卡属性里没有电源管理选项 环境 Win10 网卡设备 realtek pcie gbe family controller 解决办法
  • 遗传算法超详细图解

    遗传算法 Genetic Algorithm 顾名思义 是一种基于自然选择原理和自然遗传机制的启发式搜索算法 该算法通过模拟自然界中生物遗传进化的自然机制 选择 交叉和变异操作 将好的遗传基因 最优目标 不断遗传给子代 使得后代产生最优解的
  • webpack对js文件和eslint做缓存处理

    一 什么是webpack的cache Webpack的缓存通常是指模块缓存和构建缓存 1 模块缓存 通过缓存模块的内容 可以避免重复读取和解析同一个模块的开销 Webpack默认是开启模块缓存的 即第一次编译时会将已经加载的模块信息缓存到内
  • MAX30102血氧模块检测心率和血氧

    1 完成 CubeMX初始化配置 1 1 利用CubeMX完成HAL库工程模板和初始化 通过选择芯片型号创建CubeMX工程 在弹出的对话框中输入开发板上的芯片型号 STM32F103RB 在右侧筛选栏中选择Tx型 即开发板上芯片所用的LQ
  • 7个高清图片素材网,免费/可商用

    1 菜鸟图库 https www sucai999 com pic html v NTYwNDUx 菜鸟图库是一个综合性素材网站 这里面有很多设计 图片 视频 音频等素材 图片素材全部都是高清无水印 基本都能免费下载 还有部分素材是可以商用
  • c#输出当前日期和当前时间_如何在C#中的当前日期时间添加小时数?

    我们在C 中使用DateTime类的AddHours 方法 Syntax 句法 DateTime DateTime AddHours double 以下C 代码在当前日期时间添加小时数 using System namespace Cons
  • 父类和子类

    尽管很多知名译本都把C 面向对象里有继承关系的类称作基类和派生类 但人们很多口语化的表达里还是叫他们父类和子类 毕竟 你继承了我嘛 非亲非故的 谁让你继承 恰逢今天父亲节 我们就来聊聊C 里对父亲和儿子这一关系的设计 读程序 品人生 什么东
  • 优雅/粗暴地关闭TCP连接--close-shutdown的选择

    一个 TCP 连接需要经过三次握手进入数据传输阶段 最后来到连接关闭阶段 在最后的连接关闭阶段 我们需要重点关注的是 半连接 状态 因为 TCP 是双向的 这里说的方向 指的是数据流的写入 读出的方向 比如客户端到服务器端的方向 指的是客户