Linux网络编程——TCP通信的四次挥手

一、前言

上篇文章讲到了TCP通信建立连接的“三次握手”的一些细节,本文再对TCP通信断开连接的“四次挥手”的过程做一些分析了解。

二、TCP断开连接的“四次挥手”

我们知道TCP在建立连接的时需要“三次握手”,三次握手完后就可以进行通信了。而在通信结束的时候,TCP协议还需要进行“四次挥手”才能断开连接。如图

 从图中看, 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状态

哪一方发送了FIN报文, 就表示这一方想要断开连接了, 此端应用层不会再向对端发送数据了,这里所谓的不发数据指的是不发用户数据了,并不代表底层没有管理报文的交互

问题1、 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应答报文之后, 正式关闭连接

“四次挥手”状态分析

下面就来分析一下这四次挥手时候,每一次的挥手双方都进入哪一种状态,这些状态的意义是什么?

主动端分析


主动段发送FIN报文之后,会进入FIN_WAIT_1状态,该状态的作用是

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

主动端收到ACK应答报文之后,会进入 FIN_WAIT_2状态,该状态的作用是

  1. 表示我已经收到了对端的ACK应答报文
  2. 并了解到 对端还不想关闭连接(因为对端还没有发送FIN), 所以 我需要保持TCP接收缓冲区不关闭, 保持此端接收数据的功能
  3. 此状态持续时间, 取决于对端什么时候想要关闭连接, 即 什么时候收到对端发送的FIN报文

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

  1. 表示我已经收到了对端发送的FIN报文, 了解到对端数据也发完了, 对端也想要关闭连接了
  2. 此端也已经发送了ACK应答报文, 告诉对端收到了FIN报文
  3. 但是, 此状态并不会直接结束, 而是会持续一段时间,原因一: 对端发送的数据可能还在传输中, 所以并不能马上关闭连接,还存在着滞留的报文,必须等它们消散。原因二: 对端可能没有收到我发送的ACK应答, 对端可能还会发送FIN报文, 我还得再次ACK应答, 保证对端收到了ACK之后 正确关闭连接
  4. 此状态持续时间, 不能过长 不能过短, TCP协议推荐值为2*MSL (后面分析解释),MSL:一个消息从C到S或者从S到C的最大传输单元称为MSL。

被动端分析 

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

  1. 表示被动端已经收到了对端的FIN报文, 知道了对端要关闭连接 并且已经关闭了发送缓冲区 不再给被动端发送数据了
  2. 不过, 被动端暂时还不想关闭连接, TCP发送缓冲区内还有数据没有发送完, 所以需要维持CLOSE_WAIT 状态
  3. 并且, 需要在 对端没有收到ACK应答, 再次发送FIN报文时, 重新应答ACK报文
  4. 还有, 被动端收到了FIN报文, 也会向应用层关闭发送缓冲区,提醒应用层, 将write()send()缓冲区里已经存在的数据发走之后, 就不要在发送数据了, 发送缓冲区要关闭了,不然, 还一直有数据要发送, 还要一直消耗双方资源

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

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

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

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

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

    所以, 需要维护LAST_ACK状态

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

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

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

三、通过实验观察CLOSE_WAIT 状态TIME_WAIT 状态 

下面我们使用一个最简单的TCP服务器,并通过连接它来观察现象


#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 1024class 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); */sleep(1);}}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;
}
  1. 需要注意的是上面的代码所实现的服务器中accept() 接口并没有被调用, close()接口也没有被调用。 
  2. listen()接口的第二个参数设置为 2

1、CLOSE_WAIT 状态

Ⅰ、先将服务器运行起来,在没有建立连接的前提下使用netstat命令查看系统中处于监听(LISTEN)状态的TCP服务,如下

 Ⅱ 、接着使用telnet命令连接服务器,如下

 可以看到无论是客户端到服务端还是服务端到客户端,系统都有维护连接,状态都处于ESTABLISHED状态。可是我们在代码中并没有调用accept()接口,这是为什么呢?在我们潜意识中我们都认为accept()是来同意连接请求的,但是并不是这样的,因为即使没有调用accept()"三次握手"依然正常完成,双方都进入了ESTABLISHED状态。

这就说明“三次握手”的完成与否,实际上是和accept()无关的,accept()接口的作用只是将内核中已经与客户端建立好连接的TCP连接数据信息,“拿”到进程中去执行。

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

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

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

TCP协议中,三次握手的过程是由操作系统内核层面的TCP/IP协议栈处理的,而不是由应用程序直接控制的。这意味着即使服务器端的应用程序没有调用 accept(),客户端与服务器之间的三次握手仍然可以顺利完成,并且连接状态可以进入ESTABLISHED

accept() 函数的作用主要是用于从处于监听状态(LISTEN)的套接字上接收到来的连接请求。当有新的连接建立时(即三次握手中最后一个ACK已经收到并且连接已经建立),accept()会从已完成连接的队列中取出一个连接,并为这个连接创建一个新的文件描述符,使得服务器可以通过这个新的文件描述符与客户端进行数据交互。这样做的好处之一是可以让服务器管理多个并发的客户端连接。

 Ⅲ 、接下来尝试使用多个客户端来连接此服务器

 可以看到建立的四条连接有三条都是建立成功的,只有一条的服务端的状态为 SYN_RECYSYN_RECY 并不是标准的TCP状态之一,而是一个特定于某些操作系统(尤其是Linux)中的状态标识,用来描述类似的场景。在Linux内核中,当一个半开(half-open)连接存在时,即服务器已收到SYN包并发送了SYN-ACK但尚未收到最终的ACK确认,该连接可能会被标记为SYN_RECY。实际上,它指的是处于三次握手第二步和第三步之间的一个状态,类似于标准TCP状态转换图中的SYN_RCVD

这表示该条连接没有建立成功,这是因为listen()接口的第二个参数设置为2,并且没有调用accept()接口将建立好的连接拿走。

函数的第二个参数指定了处于待处理状态的最大连接数,即那些已经完成了三次握手但还没有被应用程序通过调用accept处理的连接数量。换句话说,它定义了等待队列的最大长度,该队列保存了那些等待被服务器接受(通过 accept方法)的客户端连接。

  • 当一个客户端尝试连接到服务器时,如果此时服务器的待处理连接数小于设置的值,则该连接会被添加到等待队列中。
  • 如果当连接请求到来时,等待队列已满(即当前未处理的连接数已经达到或超过了设置的数量),新的连接请求可能会被拒绝或者暂时悬置,具体行为取决于操作系统的实现。

IV、接下来我们使用客户端对服务器进行连接,然后观察客户端断开连接前后,服务端和客户端所处的状态。 

可以看到

  1. 客户端发起连接之后,连接成功,服务端和客户端相互维护连接,都是ESTABLISHED状态。
  2. 客户端主动关闭连接之后,服务端会进入COLSE_WAIT状态。
  3.  之后若服务端一直不停止运行,其会一直处于COLSE_WAIT状态
  4. 只有服务端停止运行了,对应的连接状态才会关闭
  • 客户端主动关闭连接之后,可以看到先是进入了FIN_WAIT2状态,在之后,客户端就主动关闭了连接,这个状态也就不见了,但是我们在上面的“四次挥手”中看到,如果客户端没有收到服务端的FIN报文的话,不是会一直处于FIN_WAIT2状态吗?

                理论上是这样的,但是Linux却在实际中并没有这么实现,Linux中 tcp_fin_timeout 指定了在强制关闭套接字前,等待最终FIN数据包的秒数。虽然它违反了TCP的规范,但是却是防止服务器被攻击所必需的,在Linux2.2中,该值默认为180

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

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

我们也可以发现,客户端主动关闭连接之后,服务端会一直处于COLSE_WAIT状态,尽管客户端已经关闭了连接,所以这就会导致服务器一直无效地占用系统的资源。这是因为服务端没有调用close()接口来关闭socket。 

下面我们将注释的部分打开,然后做同样的测试,如下

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

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

2、TIME_WAIT 状态

可以看到在 客户端收到服务端的FIN 报文后依然处于TIME_WAIT 状态一段时间后才关闭连接。

为什么需要维护一个TIME_WAIT状态?

  • TIME_WAIT是"四次挥手"的主动发起方需要维持的一个状态,我们知道, 主动端想要关闭连接, 被动端可能还存在数据未发送完毕 并不想要关闭连接,主动端是如何进入TIME_WAIT状态的呢? 是被动端将数据发送完毕之后, 向主动端发送FIN报文, 主动端收到此报文并应答之后, 进入TIME_WAIT状态,也就是说, 主动端向被动端 发送ACK应答报文之后, 才进入了TIME_WAIT,被动端是需要收到主动端的ACK应答报文才能正常关闭连接的, 所以主动端是需要保证 被动端确实收到了ACK应答报文的,如果, 被动端没有收到主动端的ACK报文, 那么被动端是会重传FIN报文的,因为, 被动端可能重传FIN报文, 所以 主动端需要维持一段时间的TIME_WAIT状态, 保证可能重传的FIN报文不被漏掉
  • 其次, 如果在主动端应答了ACK报文之后, 不维护TIME_WAIT状态 直接关闭连接, 被动端也收到了ACK报文正常的关闭了连接. 但是, 实际上网络中还有报文在有效传输,如果, 此时 恰好 双端使用了相同的四元组(源IP/目的IP:源Port/目的Port)建立了新的连接,那么 新的连接有没有可能会收到 旧的有效报文呢? 旧报文会不会影响此次的连接呢?,答案当然是有可能的. 虽然因为序号和确认序号等标识 被影响的概率很低,所以, 需要维护一段时间的TIME_WAIT状态, 保证旧报文传输完毕或失效。

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应答报文之后, 此报文会有两种结果:

  • 丢失, 即被动端一直没收到ACK报文,此时, 被动端会一直重传FIN, 直到达到重传的上限,否则, 双端的状态一般是不会变化的,你可能会想, 如果被动端一直在重传FIN, 但是每一个都没有被主动端收到,然后重传时间超出了2*MSL, 主动端都关闭连接了, 被动端还在重传FIN,如果出现了这种情况, 那么在被动端达到重传上限之前, 网络中会一直存在有效的FIN报文,这怎么解决?只能说出现这种情况的概率, 非常非常低. 不过 在此种情况中, 由于被动端在达到重传上线之前一直没有关闭连接, 也就没有释放资源, 系统一直占用着四元组资源所以, 也不会出现 双端使用相同四元组建立新连接的情况,当然, 还有最极限的一种情况: 被动端刚好达到重传上限, 重传了最后一个FIN报文, 刚好关闭连接释放资源, 双端就使用了相同的四元组建立了新的连接,此时, 网络中还传输有有效的FIN报文, 新连接就又可能被影响,但是, 好像还是无法解决, 只能说出现这种情况的概率 更更更更低了,这都是非常非常非常极端的情况, 基本不需要考虑
  • 被动端收到了ACK报文,那么, 以此报文可以被接收为前提, 最长的传输时长就是不超过MSL,并且, 被动端有可能在收到ACK的前一瞬 刚好重传了一份FIN报文, 那么 这一份FIN在网络中传输失效需要的时间就是MSL,ACK最长的传输时间+FIN传输失效需要的时间<=2*MSL,所以, TCP协议标准 推荐TIME_WAIT持续时长为2*MSL

3、setsockopt() 

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之后, 即使 没有正式关闭连接, 让服务使用相同的port和IP创建socket并bind. 不过前提是, 需要是同一个服务

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

2、SO_REUSEPORT 

允许不同服务在任何状态下, 使用相同的port和IP创建socket并bind, 之前的服务的连接不用在TIME_WAIT状态

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


感谢阅读! 

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

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

相关文章

某碰瓷国赛美赛,号称第三赛事的数模竞赛

首先我非常不能理解的就是怎么好意思自称第三赛事的呢&#xff1f;下面我们进行一个简单讨论&#xff0c;当然这里不对国赛和美赛进行讨论。首先我们来明确一点&#xff0c;比赛的含金量由什么来定&#xff1f;这个可能大家的评价指标可能不唯一&#xff0c;我通过DeepSeek选取…

Redis 缓存问题:缓存雪崩、缓存击穿、缓存穿透

文章目录 缓存雪崩缓存击穿缓存穿透在实际的业务场景中,Redis 通常作为缓存和其他数据库(例如 MySQL)搭配使用,用来减轻数据库的压力。但是在使用 Redis 作为缓存数据库的过程中,可能会遇到一些常见问题,例如缓存穿透、缓存击穿和缓存雪崩等。 缓存雪崩 缓存雪崩是指缓存…

Qt 入门 4 之标准对话框

Qt 入门 4 之标准对话框 Qt提供了一些常用的对话框类型,它们全部继承自QDialog类,并增加了自己的特色功能,比如获取颜色、显示特定信息等。下面简单讲解这些对话框,可以在帮助索引中查看Standard Dialogs关键字,也可以直接索引相关类的类名。 本文将以一个新的项目为主介绍不…

买不起了,iPhone 或涨价 40% ?

周知的原因&#xff0c;新关税对 iPhone 的打击&#xff0c;可以说非常严重。 根据 Rosenblatt Securities分析师的预测&#xff0c;若苹果完全把成本转移给消费者。 iPhone 16 标配版的价格&#xff0c;可能上涨43%。 iPhone 16 标配的价格是799美元&#xff0c;上涨43%&am…

软件需求分析习题汇编

需求工程练习题 一、选择题 1. 软件需求规格说明书的内容不应包括对&#xff08; &#xff09;的描述。 A. 主要功能B. 算法的详细过程C. 用户界面及运行环境D. 软件的性能 *正确答案:*B:算法的详细过程; 2. 需求分析最终结果是产生&#xff08; &#xff09; A. 项目开发…

clickhouse注入手法总结

clickhouse 遇到一题clickhouse注入相关的&#xff0c;没有见过&#xff0c;于是来学习clickhouse的使用&#xff0c;并总结相关注入手法。 环境搭建 直接在docker运行 docker pull clickhouse/clickhouse-server docker run -d --name some-clickhouse-server --ulimit n…

智能语音识别工具开发手记

智能语音识别工具开发手记 序言&#xff1a;听见数字化的声音 在县级融媒体中心的日常工作中&#xff0c;我们每天需要处理大量音频素材——从田间地头的采访录音到演播室的节目原声&#xff0c;从紧急会议记录到专题报道素材。二十多年前&#xff0c;笔者刚入职时&#xff0…

TDengine 3.3.6.0 版本中非常实用的 Cols 函数

简介 在刚刚发布的 TDengine 3.3.6.0 版本 中&#xff0c;新增了一个非常实用的 函数COLS &#xff0c;此函数用于获取选择函数所在行列信息&#xff0c;主要应用在生成报表数据&#xff0c;每行需要出现多个选择函数结果&#xff0c;如统计每天最大及最小电压&#xff0c;并报…

【AI学习】AI Agent(人工智能体)

1&#xff0c;AI agent 1&#xff09;定义 是一种能够感知环境、基于所感知到的信息进行推理和决策&#xff0c;并通过执行相应动作来影响环境、进而实现特定目标的智能实体。 它整合了多种人工智能技术&#xff0c;具备自主学习、自主行动以及与外界交互的能力&#xff0c;旨…

【MCP】VSCode Cline配置MongoDB连接

VSCode MCP插件配置MongoDB连接教程 前言 本文将介绍如何在VSCode中配置Cline插件连接MongoDB。 环境准备 VSCodeNode.jsMongoDB服务器Cline插件 配置步骤 1. 安装MCP插件 在VSCode扩展商店中搜索"Cline"并安装。 安装完之后需要配置API平台以及设置API-KEY。…

this指针 和 类的继承

一、this指针 Human类的属性fishc与Human&#xff08;&#xff09;构造器的参数fishc同名&#xff0c;但却是两个东西。使用this指针让构造器知道哪个是参数&#xff0c;哪个是属性。 this指针&#xff1a;指向当前的类生成的对象 this -> fishc fishc当前对象&#xff08;…

使用PyTorch训练VGG11模型:Fashion-MNIST图像分类实战

本文将通过代码实战&#xff0c;详细讲解如何使用 PyTorch 和 VGG11 模型在 Fashion-MNIST 数据集上进行图像分类任务。代码包含数据预处理、模型定义、训练与评估全流程&#xff0c;并附上训练结果的可视化图表。所有代码可直接复现&#xff0c;适合深度学习初学者和进阶开发者…

汽车BMS技术分享及其HIL测试方案

一、BMS技术简介 在全球碳中和目标的战略驱动下&#xff0c;新能源汽车产业正以指数级速度重塑交通出行格局。动力电池作为电动汽车的"心脏"&#xff0c;其性能与安全性不仅直接决定了车辆的续航里程、使用寿命等关键指标&#xff0c;更深刻影响着消费者对电动汽车的…

打造船岸“5G+AI”智能慧眼 智驱力赋能客船数智管理

项目介绍 船舶在航行、作业过程中有着严格的规范要求&#xff0c;但在实际航行与作业中往往会因为人为的疏忽&#xff0c;发生事故&#xff0c;导致人员重大伤亡和财产损失&#xff1b; 为推动安全治理模式向事前预防转型&#xff0c;实现不安全状态和行为智能预警&#xff0c…

C#二叉树

C#二叉树 二叉树是一种常见的数据结构&#xff0c;它是由节点组成的一种树形结构&#xff0c;其中每个节点最多有两个子节点。二叉树的一个节点通常包含三部分&#xff1a;存储数据的变量、指向左子节点的指针和指向右子节点的指针。二叉树可以用于多种算法和操作&#xff0c;…

WinForm真入门(11)——ComboBox控件详解

WinForm中 ComboBox 控件详解‌ ComboBox 是 WinForms 中一个集文本框与下拉列表于一体的控件&#xff0c;支持用户从预定义选项中选择或直接输入内容。以下从核心属性、事件、使用场景到高级技巧的全面解析&#xff1a; 一、ComboBox 核心属性‌ 属性说明示例‌Items‌下拉…

超详细解读:数据库MVCC机制

之前文章&#xff1a;Mysql锁_exclusivelock for update写锁-CSDN博客 中有提到通过MVCC来实现快照读&#xff0c;从而解决幻读问题&#xff0c;这里详细介绍下MVCC。 一、前言 表1&#xff1a;实例表t idk1122 表2&#xff1a;事务A、B、C的执行流程 事务A事务B事务Cstart …

【SpringCloud】从入门到精通【上】

今天主播我把黑马新版微服务课程MQ高级之前的内容都看完了&#xff0c;虽然在看视频的时候也记了笔记&#xff0c;但是看完之后还是忘得差不多了&#xff0c;所以打算写一篇博客再温习一下内容。 课程坐标:黑马程序员SpringCloud微服务开发与实战 微服务 认识单体架构 单体架…

力扣hot100_回溯(2)_python版本

一、39. 组合总和&#xff08;中等&#xff09; 代码&#xff1a; class Solution:def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:ans []path []def dfs(i: int, left: int) -> None:if left 0:# 找到一个合法组合ans.append(pa…

AI平台如何实现推理?数算岛是一个开源的AI平台(主要用于管理和调度分布式AI训练和推理任务。)

数算岛是一个开源的AI平台&#xff0c;主要用于管理和调度分布式AI训练和推理任务。它基于Kubernetes构建&#xff0c;支持多种深度学习框架&#xff08;如TensorFlow、PyTorch等&#xff09;。以下是数算岛实现模型推理的核心原理、架构及具体实现步骤&#xff1a; 一、数算岛…