TCP协议——三次握手和四次挥手

文章目录

    • 1. 示意图
    • 2. 三次握手
    • 3. 四次挥手
    • 4. 三次和四次问题
      • 4.1 为什么三次握手
      • 4.2 为什么四次挥手
    • 5. 状态变化实验
      • 5.1 三次握手实验
      • 5.2 四次挥手实验

1. 示意图

image-20240319202159125

Tips:

不管是握手还是挥手,发送的都是完整的TCP报头,这不过这些标记位被设置了

2. 三次握手

TCP可靠性保证里面有一个在建立连接之前需要进行三次握手

打个比方:

小潘(客户端):小王,我喜欢你,我们在一起吧!(发起请求,第一次握手)

小王(服务端):好呀好呀(做出应答),我也喜欢你,那我们在一起吧!(第二次握手)

小潘(客户端):吼吼吼,我们现在是情侣啦~(做出应答,第三次握手)

  1. 客户端向服务端发送SYN请求建立连接,发送完毕之后状态变为SYN_SENT同步发送
  2. 服务端收到请求之后,状态变为SYN_RECV同步收到,并做出ACK确认应答,并捎带SYN请求建立连接
  3. 客户端收到应答之后,发送ACK,状态变为ESTABLISHED,表示客户端连接建立完毕;服务端收到ACK之后,状态变为ESTABLISHED,服务端连接建立完毕

三次握手时的三个函数:

  • 客户端向服务端发起连接调用connect函数,建立的本质是connect要求客户端构建SYN请求报文发送给服务端

    connect函数是负责发起三次握手,握手的过程由双方操作系统自己完成

    发送SYN之后就进入阻塞状态,三次握手完毕才会返回

  • accept本身并不参与三次握手,只会把建立好的连接拿过来,如果底层没有建立好的连接,会一直阻塞住

三次握手成功之后,调用writeread这些系统调用进行通信

本质也不是发送接收数据,将数据写到TCP发送缓冲区(将数据从TCP接收缓冲区读上来)

3. 四次挥手

正常数据通信完毕之后,四次挥手断开连接

打个比方,小潘和小王打视频,嘴巴讲话输出数据流,眼睛看、耳朵听着接收信息

… … 巴拉巴拉巴拉巴拉讲了很久之后 … …

四次挥手断开连接:

  • 小潘:我说完啦(嘴巴不输出了(写端关闭),眼睛可以看、耳朵还在听着(读端未关闭),第一次挥手,发送FIN包)

  • 小王:好的,但我还没讲完,你听我再说会吧!(确认应答,第二次挥手)

    // ... ...
    //小王继续巴拉巴拉说
    // ... ...
    
  • 小王:现在我也说完啦!(嘴巴不输出了(关闭写端),眼睛可以看、耳朵可以听(读端未关闭),第三次挥手。发送FIN包)

  • 小潘:点点头,知道双方确认结束视频,准备挂断视频(发送的不是数据流,而是控制信息,第四次挥手)

  • 客户端向服务端发送FIN,表明没有要发送的数据,要断开连接,进入`
  • 因为要保证可靠性,服务端向客户端发送ACK确认应答表明收到
  • 当服务端也没有数据发送之后,向客户端发送FIN
  • 客户端收到之后,发送ACK确认应答表明收到

4. 三次和四次问题

事实上TCP建立连接的时候也是四次握手,只不过将第二次报文被捎带应答了:

image-20240320140936623

对于四次挥手,也可也合并成三次挥手,客户端说我要断开连接啦FIN(第一次挥手),服务端说好的,我也要和你断开连接ACK+FIN(第二次挥手),最后客户端说好的,那我们断开连接吧ACK(第三次挥手)

从最朴素的角度看,三次握手和四次挥手本质是一来一回的一种可靠性,既双方至少可靠的给对方发送了一次消息。

那为什么各种教材或者书籍都写的是三次握手和四次挥手呢?

  • 客户端向服务端发送SYN建立连接请求,服务端一定会给客户端发送ACK应答,然后服务端也要和客户端建立连接,因为不存在协商上时间差的问题,所以ACK+SYN压缩在一起是必然的。

    因为服务端就是为客户端做服务的,就是等着客户端来连接,所以当客户端发起连接请求的时候,服务端必须无偿同意!这个可不敢比作是男女朋友关系

  • 至于四次挥手,这里面ACKFIN要分开,这是因为有协商的成分在,当客户端要与服务端断开连接说“服务端,我没什么和你要和你说的了,我要断开连接”,服务端收到之后说“可我还有话要给你说啊”,然后服务端将消息发送完毕之后发送FIN,客户端答复ACK,此时才真正断开连接

    所以在一方想断开连接,另一方并不想断开连接,所以想让第二次和第三次挥手压缩成一个,是具有巧合性的!

4.1 为什么三次握手

对于三次握手它能够保证无论是客户端还是服务端在通信之前,双方至少做过一次可靠收发,这叫验证全双工通路是否通畅!

这里至少一次收发,表示的是可靠的收发,如果是2次握手:

image-20240320143552300

这里虽然双方都有一次收和发,但是对于服务端来讲,只表明自己有接收能力,并不知道是否具有发送能力,因为发出去的报文,没有应答!

假设进行一次握手:

客服端发送一个SYN请求,服务端就建立一个连接;如果客户端恶意向服务端发送大量SYN请求,服务端就要建立大量的连接

可是服务端维护这些连接是有成本的,服务端需要将这些连接管理起来,这样就十分容易将服务端连接资源打满

image-20240320144626539

假设进行两次握手:

当客户端发送SYN请求,服务端收到之后向客户端发送ACK确认,在发送之后,服务端先将连接建立好,当客户端收到ACK之后再建立连接。可是如果客户端之间丢弃这个ACK报文,那这其实是是和一次握手的问题一样。

就算这个客户端不是恶意行为,当客户端出现异常时,并没有建立连接,可是服务端还是要将连接维护一段时间。如果有一千万的客户端连接,其中10%的客户端异常了,这就表明服务端需要长时间维护这10%无用的连接。

所以让服务器作异常兜底,是行不通的!

三次握手:

三次握手,第一次握手和第二次握手都是有对于的应答,所以并不担心是否丢失,就算丢失了三次握手未成功,连接还未建立

最担心的就是第三次的应答的丢失,但是对于第三次握手,是客户端发出的,发出之后客户端以为连接建立完毕,当服务端收到之后再建立连接,如果没收到则不建立连接,认为三次握手没有成功。这样就将建立连接失败的成本嫁接到了客户端!

这就能在一定程度上保证服务端的稳定性,既奇数次握手,确保一般情况下握手失败连接成本在客户端!

为什么不是5、7、9次呢?

因为三次是验证全双工的最小次数!

4.2 为什么四次挥手

断开连接的本质是双方没有数据给对方发送,所以必须可靠告诉对方。

四次挥手即双方都能得知对方不想发送消息的意愿,需要协商断开连接

客户端没有消息给服务端发送,可是服务端还是有消息给客服端发送,客户端收到ACK之后,还是能收到服务端的消息的(关闭写端不关闭读端

客户端:发送FIN之后,状态变为FIN_WAIT_1

服务端:收到之后状态变为CLOSE_WAIT,发送ACK

客户端:收到之后状态变为FIN_WAIT_2

FIN_WAIT_2表示不会再给对方发送数据,最后发送的ACK并不是数据,而是管理报文

服务端:发送FIN之后,状态变为LAST_ACK

客户端:收到之后状态变为TIME_WAIT,发送ACK

服务端:状态变为CLOSE

5. 状态变化实验

Tips:

以下实验是基于此篇文章写的tcp套接字代码:Linux网络编程——tcp套接字

研究的是三次握手和四次挥手,之间的IO过程省略

采用2台服务器进行测试

#pragma once

#include"Log.hpp"

#include<iostream>
#include<cstring>

#include<sys/wait.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#include"threadPool.hpp"
#include"Task.hpp"

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 1;  //不要设置太大
Log log;


enum{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LITSEN_ERR
};
class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *t)
    :t_sockfd_(fd), t_clientip_(ip), t_clientport_(port), t_tsvr_(t)
    {}
public:
    int t_sockfd_;
    std::string t_clientip_;
    uint16_t t_clientport_;
    TcpServer *t_tsvr_; //需要this指针
};

class TcpServer
{

public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip)
    :listensockfd_(defaultfd)
    ,port_(port)
    ,ip_(ip)
    {}

    //初始化服务器
    void Init()
    {
        //创建套接字
        listensockfd_ = socket(AF_INET, SOCK_STREAM, 0); //sock_stream提供字节流服务--tcp
        if(listensockfd_ < 0)
        {
            log(Fatal, "create socket, errno: %d, errstring: %s",errno, strerror(errno));
            exit(SOCKET_ERR);
        }

        log(Info, "create socket success, sockfd: %d",listensockfd_);

        // int opt = 1;
        // setsockopt(listensockfd_, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));  //防止偶发性服务器无法进行立即重启

        //本地套接字信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        //填充网络信息
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));

        //bind
        int bd = bind(listensockfd_, (struct sockaddr*)&local, sizeof(local));
        if(bd < 0)
        {
            log(Fatal, "bind error, errno: %d, errstring: %s",errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info, "bind success");


        //tcp面向连接, 通信之前要建立连接
        //监听
        if(listen(listensockfd_, backlog) < 0)
        {
            log(Fatal, "listen error, errno: %d, errstring: %s",errno, strerror(errno));
            exit(LITSEN_ERR);
        }
        log(Info, "listen success");

    }
    void Start()
    {
        log(Info, "server is running...");
        while(true)
        {
            sleep(1);
            //获取新链接
            // struct sockaddr_in client;
            // socklen_t len = sizeof(client);
            // int sockfd = accept(listensockfd_, (struct sockaddr*)&client, &len);
            // if(sockfd < 0)
            // {
            //     log(Warning, "accpet error, errno: %d, errstring: %s",errno, strerror(errno));
            //     continue;
            // }
            // uint16_t clientport = ntohs(client.sin_port);
            // char clientip[32];
            // inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            // log(Info, "get a new link..., sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip, clientport);
            //根据新链接进行通信
		   //... ...

            //sleep(1); 
        }
    }
    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        char buffer[4096];
        while(true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_str = "tcpserver echo# ";
                echo_str += buffer;

                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if(n == 0)
            {
                log(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                log(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
            memset(buffer, 0, sizeof(buffer));
        }
    }

    ~TcpServer(){}
private:
    int listensockfd_;
    uint16_t port_;
    std::string ip_;
};

5.1 三次握手实验

运行服务端:

image-20240320165043797

服务端状态:

image-20240320165106917

本主机客户端和其他客户端连接:

image-20240320165209839

查看连接情况:

image-20240320165801455

这里发现连接建立成功和上层是否accpet无关,是由双方操作系统自主完成

listen第二个参数

listen第二个参数设置为1:

image-20240320171623985

这里发现客户端以为连接成功,可是服务端出现了一个SYN_RECV,并没有三次握手成功。

这是因为listen第二个参数表明连接的最大个数+1

三次握手成功之后,服务端会在底层建立好连接,不过这个连接可以不拿上去(accpet),对于这些建立好的连接没有被拿上去,所以操作系统需要将这里连接维护起来——先描述再组织,采取队列的形式管理这些建立好的连接,叫做全连接队列

这队列的最大长度,就是由listen第二个参数决定的

我们这里设置的是backlog = 1,所以连接队列最长为backlog + 1,没有accept拿走连接,所以连接2个客户端之后就满了(生产消费者模型)

SYN_RECV状态变为ESTABLISHED状态,必须要收到ACK,可是这里的ACK已经发送了(因为客户端的状态已经变为ESTABLISHED状态),但由于listen第二个参数的设置,服务端连接队列满了,所以将收到的ACK直接丢弃了

服务端并不会长时间维持SYN_RECV状态,这叫半连接队列,半连接的节点会隔一段时间被释放掉

这个半连接队列也有长度,由内核自己定

真正意义SYN洪水:

要进入全连接队列,首先要先进入半连接队列,虽然说握手失败之后半连接会过一段时间被释放,但是也耐不住恶意请求一直发SYN将半连接占满,这就导致正常的连接请求进不来,这才是真正意义上的 SYN洪水

为什么listen第二个参数不能太长,为什么不能没有?

  • 如果全连接队列太长,这就会导致有些连接来不及被上层处理,但还是要被系统长时间维护

来不及处理说明服务器已经很忙了,之后还有新连接到来,然后系统还要分资源出来维护这个队列,所以不能设置太长,这样就能再匀出资源给上层使用

  • 如果直接将这个全连接队列删掉,全力支持上层处理,当它空闲的时候,这一部分就又浪费了。
    就好比商场的餐饮店,不仅店子里有吃饭完的位置,门口也有椅子,想吃饭的人里面有位置就进去吃,没位置就可以坐在椅子上等一会,当有客人离席的时候,可以立马补上。如果门口没有椅子,里面满了,客人只能站着等,这样用户体验不好,走了,这样就会损失一批客户。

以上都是为了服务器资源充分利用

5.2 四次挥手实验

设置服务端获取连接5秒之后断开:

        while(true)
        {
            sleep(1);
            //获取新链接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensockfd_, (struct sockaddr*)&client, &len);
            if(sockfd < 0)
            {
                log(Warning, "accpet error, errno: %d, errstring: %s",errno, strerror(errno));
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            log(Info, "get a new link..., sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip, clientport);
            sleep(5);
            //关闭
            close(sockfd);
            log(Info, "close sockfd..., sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip, clientport);
            //....
        }

image-20240321102930664

现象:主动要求断开连接的一方,在四次挥手完毕之后,会进入TIME_WAIT状态,等待若干时长之后,自动释放

如果在TIME_WAIT状态下关闭服务端,然后再重新启动,发现服务起不来:

image-20240321103737591

报错信息:绑定失败

这是因为TIME_WAIT状态表示连接并没有彻底断开,ipport是正在被使用的,所以服务器挂掉之后无法立即再启动

比如说在节假日高峰期,出行人数非常非常非常多,售票软件没抗住,服务器挂掉了,此时服务器上是存在这大量的TIME_WAIT状态的,此时服务器就无法立即重启,这就出事故了。

如果因为TIME_WAIT问题导致服务器无法立即重启,可以设置setsockopt,允许地址复用:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

sockfd:文件名描述符

level:设置层级,一般是SOL_SOCKET套接字层

optname:设置选项

optval:指向一个缓冲区

optlen:缓冲区大小

    //初始化服务器
    void Init()
    {
        //创建套接字
        listensockfd_ = socket(AF_INET, SOCK_STREAM, 0); //sock_stream提供字节流服务--tcp
        if(listensockfd_ < 0)
        {
            log(Fatal, "create socket, errno: %d, errstring: %s",errno, strerror(errno));
            exit(SOCKET_ERR);
        }

        log(Info, "create socket success, sockfd: %d",listensockfd_);

        int opt = 1;
        setsockopt(listensockfd_, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));  //防止偶发性服务器无法进行立即重启

        //本地套接字信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        //填充网络信息
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));

        //bind
        int bd = bind(listensockfd_, (struct sockaddr*)&local, sizeof(local));
        if(bd < 0)
        {
            log(Fatal, "bind error, errno: %d, errstring: %s",errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info, "bind success");


        //tcp面向连接, 通信之前要建立连接
        //监听
        if(listen(listensockfd_, backlog) < 0)
        {
            log(Fatal, "listen error, errno: %d, errstring: %s",errno, strerror(errno));
            exit(LITSEN_ERR);
        }
        log(Info, "listen success");

    }

为什么客户端退出之后再连接可以直接连上?

因为客户端的端口是系统随机指定的,每次都会换端口

而服务器的上的某个服务,端口号是固定的

一个报文从客户端发到服务端,这从客户端发出去到服务端收到之前,这个报文都是在网络当中,但是在网络中是有存活时间的,存活最长时间称为MSL

TIME_WAIT的持续时间是2MSL

  • 在断开连接的时候,可能历史上还有残留的数据,所以要等这些历史的数据在网络通信当中消散,这一来一回的最大时间就是2MSL

    在准备断开连接的这个时间点,并不是要让对方收到这个数据(因为tcp有超时重传、按序到达机制,改补发的早就补发了),而是让对方丢弃这些数据。如果历史残留数据没有消散,在某些情况下,例如又重新连接,采用了同样的ip和端口(极端情况),这样就会影响下一次通信!

  • 四次挥手的时候,也是要保证挥手成功,前两次挥手双方的连接都还未彻底释放,如果失败还有机会补发;但如果最后一次ACK挥手报文丢失,而主动断开连接的一方又立即释放了连接,那对方就会一直处于LAST_ACK状态,这时候对方就算重新补发FIN,但是人家已经退了,所以在TIME_WAIT等待期间,如果ACK丢失,还能收到对方补发的FIN,这就能确保四次挥手正常退出

数据在网络当中是毫米级别,但为什么TIME_WAIT一般是30s~60s呢?

这里分为最大传送时长最大存在时长

传输是毫秒级别,但是存在时长(例如在网络中阻塞了)是由系统决定的

cat /proc/sys/net/ipv4/tcp_fin_timeout
# 可以自己改

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

各类主流电商API商品采集接口的权限控制和功能权限控制

主流电商平台的API接口类型 参数说明 通用参数说明 url说明 /平台/API类型/ 平台&#xff1a;淘宝&#xff0c;京东等&#xff0c; API类型:[item_search,item_get,item_search_shop等]version:API版本key:调用key,测试key:test_api_keysecret:调用secret,测试secret:(不用填写…

正信晟锦:多年不联系的好友借钱怎么办

多年不见的老友突然出现&#xff0c;带着迫切的求助信息——借钱。面对这样的请求&#xff0c;我们该如何应对? 当一个多年未联络的朋友突然出现请求借款时&#xff0c;这确实是一个棘手的问题。一方面&#xff0c;我们可能对旧日友情存有怀念与不舍;另一方面&#xff0c;时间…

[项目前置]如何用webbench进行压力测试

测试软件 采用webbench进行服务器性能测试。 Webbench是知名的网站压力测试工具&#xff0c;它是由Lionbridge公司开发。 webbench的标准测试可以向我们展示服务器的两项内容&#xff1a; 每秒钟相应请求数 和 每秒钟传输数据量 webbench测试原理是&#xff0c;创建指定数…

Apache DolphinScheduler 社区开启讲师招募,赶快加入吧!

随着Apache DolphinScheduler在全球范围内的快速发展&#xff0c;我们的用户群体和社区活动也在不断扩大。 为了进一步丰富我们的社区内容&#xff0c;分享更多有价值的知识和经验&#xff0c;我们诚挚地邀请您加入我们&#xff0c;成为Apache DolphinScheduler社区的分享嘉宾。…

美团2023年财报:全年营收2767亿元,即时配送订单219亿笔

3月22日&#xff0c;美团(股票代码:3690.HK)发布2023年第四季度及全年业绩。公司各项业务继续取得稳健增长&#xff0c;全年营收2767亿元(人民币&#xff0c;下同)&#xff0c;同比增长26%&#xff0c;经营利润134亿元。 本年度&#xff0c;美团继续围绕“零售科技”战略&…

springboot网站开发解决图片存储问题,前端无法访问解决办法

最近使用springboot开发网站的时候&#xff0c;遇到了一个图片存储问题&#xff0c;我之前的方法是&#xff0c;把证书图片存在项目的static目录下面&#xff0c;这样的话&#xff0c;打包发布到远程服务器后&#xff0c;虽然可以正常展示已经打包封装好的内容&#xff0c;但是…

Deep Graph Representation Learning and Optimization for Influence Maximization

Abstract 影响力最大化&#xff08;IM&#xff09;被表述为从社交网络中选择一组初始用户&#xff0c;以最大化受影响用户的预期数量。研究人员在设计各种传统方法方面取得了巨大进展&#xff0c;其理论设计和性能增益已接近极限。在过去的几年里&#xff0c;基于学习的IM方法的…

微PE启用账号和修改密码

启动PE后&#xff0c;进入桌面打开运行dism程序 选择带有系统的盘符&#xff08;默认选的是PE盘&#xff09;&#xff0c;然后打开会话 选择左侧工具箱&#xff0c;然后右侧找到账户管理 然后就可以对已有账号进行管理和清空密码了

6、kubenetes 卷

1、什么是卷 在某些场景下&#xff0c;我们可能希望新的容器可以在之前容器结束的位 置继续运⾏&#xff0c;⽐如在物理机上重启进程。可能不需要&#xff08;或者不想要&#xff09; 整个⽂件系统被持久化&#xff0c;但又希望能保存实际数据的⽬录。 Kubernetes通过定义存储…

蓝桥杯(3.21 刷真题)

P8682 [蓝桥杯 2019 省 B] 等差数列 import java.util.Arrays; import java.util.Scanner; public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();int[] res new int[n1];for(int i1;i<n;i)res[i] sc.ne…

vue2 自定义 v-model (model选项的使用)

效果预览 model 选项的语法 每个组件上只能有一个 v-model。v-model 默认会占用名为 value 的 prop 和名为 input 的事件&#xff0c;即 model 选项的默认值为 model: {prop: "value",event: "input",},通过修改 model 选项&#xff0c;即可自定义v-model …

基于VJ算法(Viola-Jones algorithm)的人面定位算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

边缘自动隐藏窗体,透明度切换,同步父窗体标签切换winform

一、实现功能 默认的标签栏(superTabControl) 可以设置隐藏,即可实现全屏最大化。通过列表切换打开的标签页。用于定制B/S模式系统显示更个性,自定义样式,简介 安全 兼容性好。 二、主要代码 private void Time_Tick(object sender, EventArgs e) {获取主屏

停止docker 容器并删除对应镜像

docker 容器相关命令 docker ps 查看当前系统正在运行的容器情况&#xff0c;返回信息分别为&#xff1a; 容器ID&#xff1a;CONTAINER ID 镜像名IMAGE NAMES 运行命令COMMAND 创建时间CREATED 状态STATUS 映射端口 PORTS docker ps |grep XXX 可以…

Rust基本类型

数值类型 整数类型 无符号整数只能取正数和0&#xff0c;有符号整数可以取正数负数和0。isize和usize类型取决于程序运行的计算机CPU类型&#xff0c;若CPU是32位的&#xff0c;则这两个类型是32位的&#xff0c;若CPU是64位的&#xff0c;则它们是64位的。rust整型 默认使用…

vue/uniapp项目把public下的静态资源打包进dist目录

vue-cli包装的uniapp项目&#xff0c;需要把public下的静态资源打包进dist目录下&#xff0c; 如下图所示 解决方法&#xff1a; 安装依赖&#xff1a;yarn add copy-webpack-plugin -D;根路径新建vue.config.js文件 vue.config.js配置如下&#xff1a; const CopyPlugin …

手机如何设置静态IP地址显示

随着移动互联网的普及&#xff0c;手机已经成为我们日常生活中不可或缺的一部分。在连接无线网络时&#xff0c;我们有时需要设置手机的IP地址为静态&#xff0c;以满足特定的网络需求或解决某些网络问题。本文将指导您如何在手机上设置静态IP地址显示&#xff0c;让您更好地管…

C++语言学习(二)—— C++语言的基本知识

目录 一、面向对象的三个核心概念 二、C语言中的I/O口 三、C语言中的数据类型​​​​​​​ 3.1 逻辑类型 3.2 引用类型 3.2.1 引用作为函数参数 3.2.2 引用作为函数返回值 3.2.3 引用作为类成员 3.3 类类型 四、 C语言中的内联函数 五、 函数重载 六、 带默认形参…

【C++】狗屁不通文章生成器2.0

【C】狗屁不通文章生成器2.0 1 前言2 改进2.1 字词的前后关系2.2 文章生成系统 3 实现(部分)3.1 class wordpair3.1.1 转化为 json3.1.2 添加后缀词3.1.3 选择后缀词 3.2 class createArticle3.2.1文本分割3.2.2生成文章 4演示4.1 wordpair(3x2), 启动词(春天)4.2 wordpair(2x1…

2024.3.24 机器学习周报

目录 引言 Abstract 一、文献阅读 1、题目 2、引言 3、创新点 4、模型架构 4.1 Encoder and Decoder Stacks 4.2 Attention—注意力机制 5、实验 6、结果 二、Self-attention 1、什么是self-attention 2、例子&#xff08;x2→z2&#xff09; 3、矩阵批量处理 …