[Linux] TCP协议介绍(3): TCP协议的“四次挥手“过程、状态分析...

news2024/11/24 19:05:52

TCP协议是面向连接的

上一篇文章简单分析了TCP通信非常重要的建立连接的"三次握手"的过程

本篇文章来分析TCP通信中同样非常重要的断开连接的"四次挥手"的过程

TCP的"四次挥手"

TCP协议建立连接 需要"三次握手". "三次挥手"完成之后, 连接双方就可以正常通信了

而在通信的最后, TCP协议断开连接 需要"四次挥手":

|huger

从图中看, TCP的"四次挥手", 是由 两端分别发送FIN报文, 对端再应答ACK报文 形成的

整个过程是这样的:

  1. 先发送FIN的一端(主动端A) 在接收到对端(被动端B)的ACK之后, 会进入FIN_WAIT_2状态, 而不是直接CLOSED

  2. 并且, B端接收到FIN之后, 也没有直接应答FIN关闭连接. 而是, 进入了CLOSE_WAIT状态

  3. 然后, B端才发送了FIN报文, 并进入LAST_ACK状态, 直到收到A端的ACK应答报文

  4. A端收到B端发送的FIN报文, 并发送ACK应答报文之后, 并没有直接进入CLOSED状态, 而是先进入了TIME_WAIT状态

  5. "四次挥手"的主动发起者, 可以是客户端, 也可以是服务端

    所以, "四次挥手"用主动端和被动端来区分状态

可以看到, TCP"四次挥手"的过程中, 双方会进入许多的状态:

  1. 先发送FIN的一端, 会依次进入FIN_WAIT_1 FIN_WAIT_2 TIME_WAIT, 最后才CLOSED
  2. 后发送的一端, 则会在接收到FIN之后, 依次进入CLOSE_WAIT LAST_WAIT, 然后才COLSED

哪一方发送了FIN报文, 就表示这一方想要断开连接了, 此端应用层不会再向对端发送数据了


那么了解了"四次挥手"的整个过程, 一定会有一个疑问: A端发送FIN报文之后, 为什么B端没有直接应答FIN, 而是进入了CLOSE_WAIT状态?

答案是, 为了维护数据传输的可靠性

A端向B端发送FIN报文, 表示A端不准备通信了, 实际也就表示A端应用层不会再向B端发送数据了

但是, A端没有数据发送了, B端却不一定. 毕竟, TCP协议是存在发送缓冲区的, 也就是说 B端可能还有数据没有向A端发送呢, 如果直接和A端一起断开连接, 那么还没有发送的数据怎么办?

所以, 虽然A端发送了FIN报文, 想要断开连接, 但是B端可能并不想现在据断开连接, 所以就可能不会直接应答FIN

即, 当被动端不想直接断开连接时, 就只应答ACK报文, 并进入CLOSE_WAIT状态

直到, 被动端也没有要发送的数据了, 被动端才会发送FIN报文, 并进入LAST_ACK状态

如果, 主动端发送FIN报文时, 被动端也想要断开连接了

那么, B端就可能会应答ACK+FIN的报文

不过, 这并不是一般情况, 我们还是要讨论一般情况滴

主动端接收到被动端的FIN报文之后, 向被动端应答最后一个ACK报文, 并进入TIME_WAIT状态 持续一段时间后, 正式关闭连接

被动端在收到主动端的ACK应答报文之后, 正式关闭连接

"四次挥手"状态分析

了解了"四次挥手"的大致过程, "四次挥手"过程中 挥手双方 会进入这么多的状态

那么, 双方为什么要进入这么多状态? 这些状态都有什么存在意义?

下面, 就来解释一下:

针对主动端:

  1. 主动端(我)发送FIN报文之后, 会进入 FIN_WAIT_1 状态

    FIN_WAIT_1 状态 的作用

    1. 表示我已经主动发送FIN报文, 告诉对端 自己想要断开连接
    2. 防止因网络延迟或其他原因, 我没有收到对端的ACK应答报文. 出现此情况, 还需要超时重传FIN报文
    3. 进入FIN_WAIT_1状态开始, 我就关闭了TCP发送缓冲区, 应用层不会再向对端发送数据, 同时让对端也了解到这一点

    此状态持续时间, 取决于什么时候收到对端的ACK应答报文

  2. 主动端接收到对端的ACK报文之后, 会进入 FIN_WAIT_2 状态

    FIN_WAIT_2 状态 的作用

    1. 表示我已经收到了对端的ACK应答报文
    2. 并了解到 对端还不想关闭连接, 所以 我需要保持TCP接收缓冲区不关闭, 保持此端接收数据的功能

    此状态持续时间, 取决于对端什么时候想要关闭连接, 即 什么时候收到对端发送的FIN报文

  3. 主动端接收到对端的FIN报文, 并作出应答之后, 会进入 TIME_WAIT 状态

    TIME_WAIT 状态 的作用

    1. 表示我已经收到了对端发送的FIN报文, 了解到对端数据也发完了, 对端也想要关闭连接了

    2. 此端也已经发送了ACK应答报文, 告诉对端收到了FIN报文

    3. 但是, 此状态并不会直接结束, 而是会持续一段时间

      原因一: 对端发送的数据可能还在传输中, 所以并不能马上关闭连接

      原因而: 对端可能没有收到我发送的ACK应答, 对端可能还会发送FIN报文, 我还得再次ACK应答, 保证对端收到了ACK之后 正确关闭连接

    此状态持续时间, 不能过长 不能过短, TCP协议推荐值为2*MSL (后面分析解释)

那么

针对被动端:

  1. 被动端收到对端的FIN报文, 并作出应答之后, 会进入 CLOSE_WAIT 状态

    CLOSE_WAIT 状态 的作用

    1. 表示被动端已经收到了对端的FIN报文, 知道了对端要关闭连接 并且已经关闭了发送缓冲区 不再给被动端发送数据了

    2. 不过, 被动端暂时还不想关闭连接, TCP发送缓冲区内还有数据没有发送完, 所以需要维持CLOSE_WAIT状态

    3. 并且, 需要在 对端没有收到ACK应答, 再次发送FIN报文时, 重新应答ACK报文

    4. 还有, 被动端收到了FIN报文, 也会向应用层关闭发送缓冲区

      提醒应用层, 将write()send()缓冲区里已经存在的数据发走之后, 就不要在发送数据了, 发送缓冲区要关闭了

      不然, 还一直有数据要发送, 还要一直消耗双方资源

    此状态持续时间, 取决于什么时候TCP发送缓冲区的数据发完, 并且与close()调用有关 (后面分析解释)

  2. 被动端数据发送完, 调用close() 发送FIN报文之后, 会进入 LAST_ACK 状态

    LAST_ACK 状态 的作用

    1. 表示被动端已经发送了FIN报文, 也要关闭连接了

    2. 等待对端的ACK应答报文, 即 需要知道 对端已经收到了被动端的FIN报文

      如果长时间没有收到对端的ACK应答报文, 被动端需要重新发送FIN报文

      所以, 需要维护LAST_ACK状态

    直到收到对端的ACK应答, 才会关闭连接

在上面这5个状态中, 有2个状态很重要: 被动端的COLSE_WAIT主动端的TIME_WAIT

并且, 这两种状态也是"四次挥手"过程中, 最容易观察到的

观察、分析CLOSE_WAITTIME_WAIT

我们可以实现一个最简单的TCP服务器, 并使用telnet建立连接查看端口的状态

tcpServer.cpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SOCKET_ERR  1
#define BIND_ERR    2
#define LISTEN_ERR  3
#define USE_ERR     4
#define CONNECT_ERR 5
#define FORK_ERR    6
#define WAIT_ERR    7

#define BUFFER_SIZE 1024

class tcpServer {
public:
    tcpServer(uint16_t port, const std::string& ip = "")
        : _port(port)
        , _ip(ip)
        , _listenSock(-1) {}

    void init() {
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);

        if (_listenSock < 0) {
            // 套接字文件描述符创建失败
            printf("socket() faild:: %s : %d\n", strerror(errno), _listenSock);
            exit(SOCKET_ERR); // 创建套接字失败 以 SOCKET_ERR 退出
        }
        printf("socket create success: %d\n", _listenSock);

        // 套接字创建成功, 就需要将向 sockaddr_in 里填充网络信息
        struct sockaddr_in local;
        std::memset(&local, 0, sizeof(local));

        // 填充网络信息
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        _ip.empty() ? (local.sin_addr.s_addr = htonl(INADDR_ANY)) : (inet_aton(_ip.c_str(), &local.sin_addr));

        // 绑定网络信息到主机
        if (bind(_listenSock, (const struct sockaddr*)&local, sizeof(local)) == -1) {
            printf("bind() faild:: %s : %d\n", strerror(errno), _listenSock);
            exit(BIND_ERR);
        }
        printf("socket bind success : %d\n", _listenSock);

        if (listen(_listenSock, 2) == -1) {
            printf("listen() faild:: %s : %d\n", strerror(errno), _listenSock);
            exit(LISTEN_ERR);
        }
        printf("listen success : %d\n", _listenSock);
        // 开始监听之后, 别的主机就可以发送连接请求了.
    }

    // 服务器初始化完成之后, 就可以启动了
    void loop() {
        while (true) {
            sleep(1);
            struct sockaddr_in peer;          // 输出型参数 接受所连接主机客户端网络信息
            socklen_t peerLen = sizeof(peer); // 输入输出型参数
            
            int serviceSock = accept(_listenSock, (struct sockaddr*)&peer, &peerLen);
            if (serviceSock == -1) {
                printf("accept() faild:: %s : %d\n", strerror(errno), serviceSock);
                continue;
            }
            sleep(120);
	  		close(serviceSock);
        }
    }

private:
    uint16_t _port; // 端口号
    std::string _ip;
    int _listenSock; // 服务器套接字文件描述符
};

void Usage(std::string proc) {
    std::cerr << "Usage:: \n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:: \n\t" << proc << " 8080 127.0.0.1" << std::endl;
}

int main(int argc, char* argv[]) {
    if (argc != 3 && argc != 2) {
        Usage(argv[0]);
        exit(USE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3) {
        ip = argv[2];
    }

    tcpServer svr(port, ip);

    svr.init();
    svr.loop();

    return 0;
}

这是一个非常简单的tcpServer, 编译、运行起来后 就可以对其发起连接了

这个服务器需要注意的是:

  1. listen(_listenSock, 2), listen()第二个参数设置为2

  2. accept()接口没有被调用, close()同样没有被调用

    如要测试, 请手动注释

CLOSE_WAIT

CLOSE_WAIT是被动端会进入的状态

将程序编译、运行起来, 使用telnet进行连接, 使用netstat命令查看状态:

  1. 服务器运行起来, 暂时没有建立连接时

    使用netstat -nlpt查看, 系统中处于LISTEN状态的TCP服务

    |huger

  2. 当使用telnet向服务器发起连接之后

    使用netstat -npt查看TCP服务的连接状态

    |wide

    可以看到, 系统维护有 从客户端到服务端的连接从服务端到客户端的连接, 并且状态都处于ESTABLISHED

    这可以说明什么?

    要知道, 上面的服务代码是没有调用accept()

    可能很多人认为accept()接口是用来同意连接请求的, 但实际并不是的, 因为即使没有调用accept(), "三次握手"依然是正常完成了, 双端正常进入了ESTABLISHED状态

    所以, 这个现象说明 "三次握手"完成与否, 是与应用层是否调用accept()无关的

    并且说明了, accept()接口的功能 只是将内核中已经与客户端建立好的TCP连接数据、信息, "拿"到进程中使用

  3. 如果使用telnet向服务器发起更多连接(一共4次)

    再次使用netstat -npt查看TCP服务的连接状态

    |wide

    可以看到, telnet4次尝试向服务端建立连接, 系统成功建立了3条TCP连接

    有1条没有成功建立, 而是 客户端进入了SYN_SENT状态, 也就表示服务端好像没有应答

    出现这种现象的原因是: listen()的第二个参数设置为了2, 并且没有调用accept()将建立好的连接拿走

    如果listen()的第二个参数设置为1, 那么 就只能成功连接2条

    如果listen()的第二个参数设置为0, 那么 就只能成功连接1条

    前提是不调用accept()

    很容易测试, 可以试一下

    listen()第二个参数的具体作用, 后面再介绍

  4. 重新使用telnet向服务器发起连接, 并且记录从建立连接, 到断开连接, 服务端连接状态的变化

    |wide

    可以看到:

    1. 客户端发起连接之后, 服务端和客户端相互维护连接, 均进入ESTABLISHED状态

    2. 客户端主动关闭连接之后, 服务端进入CLOSE_WAIT状态

    3. 之后, 如果服务端没有停止运行, 服务端会一直处于CLOSE_WAIT状态

    4. 服务端停止运行了, 相应的连接状态才关闭

    5. 客户端(主动端)主动关闭连接之后, 可以看到 先进入了FIN_WAIT_2状态

      并且, 在之后的观察中 可以发现, 主动端完整的关闭了连接

      但是, TCP"四次挥手"规定, 只要主动端没有收到被动端发送的FIN报文, 就会一直处于FIN_WAIT_2状态吗?

      理论上是这样的, 但是 Linux在实现上并没有这样实现:

      执行man tcp的命令, 并搜索tcp_fin_timeout:

      |wide

      tcp_fin_timeout(整数; 默认值: 60; 自 Linux2.2 起)

      它指定了在强制关闭套接字之前, 等待最终FIN数据包的秒数. 这违反了TCP规范, 但却是防止服务攻击所必需的. 在 Linux2.2 中, 默认值为 180

      也就是, 说Linux针对 主动端 等待 被动端的FIN报文 设定了一个时间限制. 只要 主动端等待的时间 超出了这个时间限制, 主动端就会强制关闭连接

      这也就是为什么, 上面 客户端进入FIN_WAIT_2一段时间之后, 突然不见了

    可以发现, 客户端(主动端)关闭连接, 服务端(被动端)好像会一直处于CLOSE_WAIT状态

    服务端处于CLOSE_WAIT状态, 但是客户端已经关闭了连接, 就会导致服务端一直无效占用系统资源

    至于服务端为什么会一直处于CLOSE_WAIT状态, 实际是因为服务端没有调用close()关闭socket

  5. 如果我们将服务端代码中被注释掉的部分解开, 然后再编译运行, 并且使用telnet连接

    可以先预测一下结果:

    代码在accept()之后sleep(120), 然后再调用close()

    也就是说accept()将连接拿到进程中的120s之后, 服务端会close()socket

    那么, 如果由客户端在这120s内 主动断开连接, 那么此次服务端不会一直处于CLOSE_WAIT状态

    实际的结果:

    |wide

    实际的结果, 也正如预测的那样, 服务端基本是在连接建立成功120s之后, 调用了close()关闭了连接, 从而解决了一直处于CLOSE_WAIT的情况

    整个过程中, 服务端是没有停止运行的, 只是在最后调用了close()关闭了socket

    客户端的FIN_WAIT_2状态也确实只维持了60s

从观察、分析的结果来看, 如果被动端 不调用close() 关闭此次连接创建的socket, 那么被动端就会一直处于CLOSE_WAIT状态, 即使 已经不会再有任何通信

这就会导致, 被动端的系统资源得不到释放, 一直被无效占用, 所以, 无论是客户端还是服务端, 双端在通信完成之后, 一定要调用close()关闭socket释放资源

TIME_WAIT

TIME_WAIT是主动端会进入的状态

要观察TIME_WAIT状态, 需要让客户端收到服务端发送的FIN报文, 所以 我们需要将服务端代码中sleep(120), 调整到 sleep(50) 或 更低, 主要是为了保证客户端在FIN_WAIT_2状态维持时间内 收到 服务端的FIN报文

我设置为sleep(40)

依旧将程序编译、运行起来, 使用telnet进行连接, 使用netstat命令查看状态:

telnet发起连接之后, 主动关闭连接, 并查看客户端状态变化:

|lwide

可以看到, 在连接成功建立(24:01) 的40s左右之后(24:40), 被动端(服务端)调用close() 发送FIN报文

主动端(客户端)收到FIN报文并应答, 进入TIME_WAIT状态(24:40), 被动端收到应答关闭连接

主动端进入TIME_WAIT60s后(25:41), 关闭连接. 因为25:34时 主动端依旧处于TIME_WAIT, 25:41就关闭了连接

从观察的结果来看TIME_WAIT大致维持了60s


那么

1. 为什么主动端要维护一个TIME_WAIT状态?

2. 为什么TIME_WAIT状态维持的时间是多少? 为什么?

首先关于第一个问题:

TIME_WAIT是"四次挥手"的主动发起方需要维持的一个状态

我们知道, 主动端想要关闭连接, 被动端可能还存在数据未发送完毕 并不想要关闭连接

主动端是如何进入TIME_WAIT状态的呢? 是被动端将数据发送完毕之后, 向主动端发送FIN报文, 主动端收到此报文并应答之后, 进入TIME_WAIT状态

也就是说, 主动端向被动端 发送ACK应答报文之后, 才进入了TIME_WAIT

被动端是需要收到主动端的ACK应答报文才能正常关闭连接的, 所以主动端是需要保证 被动端确实收到了ACK应答报文的

如果, 被动端没有收到主动端的ACK报文, 那么被动端是会重传FIN报文的

因为, 被动端可能重传FIN报文, 所以 主动端需要维持一段时间的TIME_WAIT状态, 保证可能重传的FIN报文不被漏掉

这是TIME_WAIT状态存在的第一个作用


其次, 如果在主动端应答了ACK报文之后, 不维护TIME_WAIT状态 直接关闭连接, 被动端也收到了ACK报文正常的关闭了连接. 但是, 实际上网络中还有报文在有效传输

如果, 此时 恰好 双端使用了相同的四元组(源IP/目的IP:源Port/目的Port)建立了新的连接

那么 新的连接有没有可能会收到 旧的有效报文呢? 旧报文会不会影响此次的连接呢?

答案当然是有可能的. 虽然因为序号和确认序号等标识 被影响的概率很低

所以, 需要维护一段时间的TIME_WAIT状态, 保证旧报文传输完毕或失效, 这是第二个作用

关于第二个问题:

在简单分析"四次挥手"双端状态时, 提到过 TIME_WAIT的持续时间不能太长, 不能太短, TCP协议标准 推荐值为2*MSL

MSL(Maximum Segment Lifetime), 表示 TCP报文在网络中的最大生存时间. 不同系统 可能设置不同的MSL, 如果一个TCP报文在网络中传输的时间超过了系统的MSL, 那么此报文到达目的地时会被丢弃

TCP协议标准 推荐TIME_WAIT的持续时长为2倍MSL, 为什么呢?

因为 如果TIME_WAIT持续2*MSL的时长, 那么基本可以保证此次连接 双方发送的报文 都已经传输完毕或已经失效

如果, 主动端在TIME_WAIT期间 再次收到了被动端的FIN报文, 主动端会重新发送ACK报文并进入新的TIME_WAIT

那么, 主动端发送ACK应答报文之后, 此报文会有两种结果:

  1. 丢失, 即被动端一直没收到ACK报文

    此时, 被动端会一直重传FIN, 直到达到重传的上限

    否则, 双端的状态一般是不会变化的

    你可能会想, 如果被动端一直在重传FIN, 但是每一个都没有被主动端收到

    然后重传时间超出了2*MSL, 主动端都关闭连接了, 被动端还在重传FIN

    如果出现了这种情况, 那么在被动端达到重传上限之前, 网络中会一直存在有效的FIN报文

    这怎么解决?

    只能说出现这种情况的概率, 非常非常低. 不过 在此种情况中, 由于被动端在达到重传上线之前一直没有关闭连接, 也就没有释放资源, 系统一直占用着四元组资源

    所以, 也不会出现 双端使用相同四元组建立新连接的情况


    当然, 还有最极限的一种情况: 被动端刚好达到重传上限, 重传了最后一个FIN报文, 刚好关闭连接释放资源, 双端就使用了相同的四元组建立了新的连接

    此时, 网络中还传输有有效的FIN报文, 新连接就又可能被影响

    但是, 好像还是无法解决, 只能说出现这种情况的概率 更更更更低了


    这都是非常非常非常极端的情况, 基本不需要考虑

  2. 被动端收到了ACK报文

    那么, 以此报文可以被接收为前提, 最长的传输时长就是不超过MSL

    并且, 被动端有可能在收到ACK的前一瞬 刚好重传了一份FIN报文, 那么 这一份FIN在网络中传输失效需要的时间就是MSL

    ACK最长的传输时间+FIN传输失效需要的时间<=2*MSL

    所以, TCP协议标准 推荐TIME_WAIT持续时长为2*MSL


那么, Linux系统中的MSL实现的是多少呢?

按照TCP标准的建议, TIME_WAIT持续2*MSL. 而 我们实测TIME_WAIT会持续约60s

那么, Linux的MSL就应该是30

但实际不是的. 查看Linux源码可以看到, MSL=60:

|wide

同样 可以看到TIME_WAIT的持续时间, 也可以看作默认60s:

|wide

Linux系统实现的TIME_WAIT的持续时间, 并不只是简单的60s, 但是一般可以看作是60s

并且, 可以在源码中看到有关FIN_WAIT_2状态的定义TCP_FIN_TIMEOUT, 实际就是TCP_TIMEWAIT_LEN

所以, 在Linux系统中查看TCP_FIN_TIMEOUT就是查看TCP_TIMEWAIT_LEN, 也可以看作是查看Linux系统的MSL:

cat /proc/sys/net/ipv4/tcp_fin_timeout

|wide


查看源码我们发现, Linux针对TIME_WAIT状态持续时间的实现, 并没有按照TCP协议标准的建议走

而是将TCP_TIMEWAIT_LEN设置为了与TCP_PAWS_MSL相同的值60

因为Linux针对TIME_WAIT有其他方面的优化


可能造成的问题 和 解决

TIME_WAIT持续太久, 也会无效占用系统资源, 除了占用系统资源之外, 可能会造成一些其他:

如果是服务端TIME_WAIT持续太久, 会出现这种情况:

由于服务端主动关闭连接, 维持在TIME_WAIT状态, 导致端口资源无法释放, 耽误服务重启

Linux系统TIME_WAIT维持时间在60s左右

服务器在实际运行中, 如果整个服务挂掉了, 服务器建立的每一个连接都会进入TIME_WAIT, 只要有一个连接没有正式关闭, 服务就无法使用相同的端口重启, 难道服务要等待60s再重启吗?

要解决这个问题, 除了直接从系统层面做优化之外. Linux还提供了一个系统调用setsockopt()

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname,
               const void *optval, socklen_t optlen);
/*
 * sockfd, 创建的套接字
 * level, 协议层 可以指定协议, 这里使用 SOL_SOCKET
 * optname, 选项名
 * optval, 要设置的值/内容, 需要根据选项的实际类型进行定义和填充, 是一个输入性参数
 * optlen, optval的长度/大小
 */

这个系统调用可以 对进程中的指定socket的行为 做出一些调整, 即 设置套接字的一些选项, 需要调用在创建套接字之后

有两个选项, 可以使相同的服务直接使用相同的端口/IP创建套接字, 即使之前的连接还未正式关闭

这两个选项看作布尔值, 可以直接以0/1设置

  1. SO_REUSEADDR

    可以在服务进入TIME_WAIT之后, 即使 没有正式关闭连接, 让服务使用相同的PortIP创建socketbind. 不过前提是, 需要是同一个服务

    // 创建套接字之后
    int opt = 1;
    setsockopt(_listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    

    在TIME_WAIT状态 |wide

  2. SO_REUSEPORT

    允许不同服务在任何状态下, 使用相同的PortIP创建socketbind, 之前的服务的连接不用在TIME_WAIT状态

    // 创建套接字之后
    int opt = 1;
    setsockopt(_listenSock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    

    |wide


至此, TCP协议"四次挥手"的过程以及相关状态分析, 简单介绍完毕

感谢阅读~

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

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

相关文章

英伟达开源最强通用模型Nemotron-4 340B

英伟达的通用大模型 Nemotron&#xff0c;开源了最新的 3400 亿参数版本。 本周五&#xff0c;英伟达宣布推出 Nemotron-4 340B。它包含一系列开放模型&#xff0c;开发人员可以使用这些模型生成合成数据&#xff0c;用于训练大语言模型&#xff08;LLM&#xff09;&#xff0…

【CICID】GitHub-Actions语法

[TOC] 【CICID】GitHub-Actions语法 1 场景 ​ 当我们开发过程中&#xff0c;经常需要提交代码&#xff0c;打包&#xff0c;部署新代码到对应的环境&#xff0c;整个过程都是人工手动操作&#xff0c;占据开发人员大量时间&#xff0c;并且很繁琐容易出错。所以需要借助一些…

奥特曼谈AI的机遇、挑战与人类自我反思:中国将拥有独特的大语言模型

奥特曼在对话中特别提到&#xff0c;中国将在这个领域扮演重要角色&#xff0c;孕育出具有本土特色的大语言模型。这一预见不仅彰显了中国在全球人工智能领域中日益增长的影响力&#xff0c;也预示着未来技术发展的多元化趋势。 ①奥特曼认为AI在提升生产力方面已显现积极作用&…

蔡崇信“预言”:微软与OpenAI未来极有可能会分道扬镳

近日&#xff0c;在美国投行摩根大通于上海举行的第二十届全球中国峰会上&#xff0c;阿里巴巴集团联合创始人、董事局主席蔡崇信与摩根大通北亚区董事长兼大中华区投资银行业务副主席关金星&#xff08;Kam Shing Kwang&#xff09;进行了一场精彩对话。蔡崇信深入分享了他对公…

【LVGL】Guider 界面分析

文章目录 前言架构创建 UI切换界面空间释放分析创建页面空间变化 前言 分析Gui Guider-1.7.2-GA 生成的 LVGL 界面切换&#xff0c;资源管理等处理 架构 所有控件存放于同一个结构体 lv_ui 内&#xff0c;每个页面都至少包含 screen_xxx 和 screen_xxx_del 两个成员 typede…

C语言:文件系统

一、目录和文件 在当前目录下使用touch 创建一个名为 -a的文件: touch -a ; // 错误&#xff0c; touch -- -a//正确 touch ./-a 正确 ls -n可以看到对象的用户id&#xff0c;可以在/etc/passwd中查看&#xff0c;/etc/group可以看到组号 获取文件属性 #include <sys/ty…

苹果加大AI布局,上海新店开业昭示中国市场新动向

随着全球科技巨头纷纷进军人工智能领域&#xff0c;苹果公司亦不甘示弱&#xff0c;近期在上海静安新店的开业以及CEO蒂姆库克的一系列动作&#xff0c;都显示出苹果在AI方面的雄心壮志。这不仅是对未来技术趋势的积极回应&#xff0c;更是对市场竞争态势的精准把握。 库克的访…

Gone框架介绍26 - Gone v1.x 版本 正式发布,更加强大的依赖注入,更加卓越的执行效率

gone是可以高效开发Web服务的Golang依赖注入框架 github地址&#xff1a;https://github.com/gone-io/gone 文档地址&#xff1a;https://goner.fun/zh/ 文章目录 优化和新特性gone 核心功能增强内置Goners覆盖测试 后续计划 优化和新特性 gone 核心功能增强 重构了函数参数依…

Qt项目天气预报(1) - ui界面搭建

ui中部 效果演示 ui效果 显示效果 控件列表 配合右图查看 居中对齐-label 设置label居中对齐(别傻傻的空格对齐了) 间距配置 widget03 外围的widget对象: 包含label 和 widget0301&#xff0c;如下图 widget0301 内围的widget对象&#xff0c;如下图 样式表 widget03 …

Java与数据库连接技术JDBC关键核心之PreparedStatement以及SQL注入演示解决和原理

PreparedStatement SQL注入 执行预编译的SQL对象 这样也能登录成功 模拟 SQL注入是这个原因 现在基本上不存在SQL注入的问题 解决 SQL注入就是传一些语句导致原来的SQL语句改变了 修改代码 通过设置参数的方式 就能防止SQL注入 实际上我们进行了一个转化 将字符和关键字进…

【归并排序】| 详解归并排序核心代码之合并两个有序数组 力扣88

&#x1f397;️ 主页&#xff1a;小夜时雨 &#x1f397;️专栏&#xff1a;动态规划 &#x1f397;️如何活着&#xff0c;是我找寻的方向 目录 1. 题目解析2. 代码 1. 题目解析 题目链接: https://leetcode.cn/problems/merge-sorted-array/description/ 本道题是归并排序的…

Python学习打卡:day07

day7 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day753、列表的常用操作课后练习题54、列表的循环遍历列表的遍历—— while 循环列表的遍历—— for 循环while 循环和 for 循环的对比练习 55、元组…

回归预测 | Matlab实现NGO-HKELM北方苍鹰算法优化混合核极限学习机多变量回归预测

回归预测 | Matlab实现NGO-HKELM北方苍鹰算法优化混合核极限学习机多变量回归预测 目录 回归预测 | Matlab实现NGO-HKELM北方苍鹰算法优化混合核极限学习机多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现NGO-HKELM北方苍鹰算法优化混合核极限…

Kotlin 协程真的轻量吗?

前言 在官方文档的介绍中,提到了: 协程是轻量的 并给出了一个例子: fun main() = runBlocking {repeat(50_000) {// 启动大量的协程launch {delay

模型算法—线性回归

线性回归是统计学中最常见的一种回归分析方法&#xff0c;用于建立自变量&#xff08;解释变量&#xff09;和因变量&#xff08;响应变量&#xff09;之间的线性关系。线性回归模型可以用来预测一个或多个自变量对应的因变量的值。 线性回归的基本形式如下&#xff1a; &…

【麒麟虚拟机】NetworkManager没有运行

麒麟V10 建linux麒麟虚拟机&#xff0c;发现&#xff0c;网络没有配置 提示&#xff0c;NetworkManager没有运行。编辑联接也不能配置 解决方法&#xff0c;在终端输入命令&#xff1a; sudo systemctl start NetworkManager 启动以后&#xff0c;编辑连接选自动以太网&…

从 Linux 逻辑地址 线性地址 物理地址 内存管理设计,感受Linux kernel 设计的精妙

1&#xff0c;机器解析的思路 发现网络上大量的教程&#xff0c;多是以讹传讹地讲解 Linux 内存管理&#xff1b; 都是在讲&#xff1a; 逻辑地址 -> 线性地址 -> 物理地址 如果谙熟 Linux 制定的GPT和编译原理或对二进制分析比较熟练的话&#xff0c;会发现线性地…

[AIGC] Python的Range函数

Python的range()函数是一个内置函数&#xff0c;常常用于编程中生成数列。这个函数可以生成一个整数序列&#xff0c;这个序列通常用在循环中。 文章目录 基本用法详细用法注意事项 基本用法 range()函数的基本形式为 range(stop) —— 这将生成一个从0开始&#xff0c;到stop…

js 用正则表达式 匹配自定义字符之间的字符串数据,如:( )、[ ]、{ }、< >、【】等括号之间的字符串数据

要使用正则表达式匹配尖括号()之间的数据&#xff0c;可以使用以下代码示例&#xff1a; 在JavaScript中&#xff0c;你可以使用正则表达式来匹配括号()之间的数据。以下是一个简单的例子&#xff0c;它展示了如何使用正则表达式来获取两对括号之间的文本。 // 示例字符串 con…

5.3.1_2 二叉树的层次遍历

&#x1f44b; Hi, I’m Beast Cheng&#x1f440; I’m interested in photography, hiking, landscape…&#x1f331; I’m currently learning python, javascript, kotlin…&#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以订…