计算机网络-TCP协议详解
TCP协议详解
- 2. TCP协议详解
- 2.1 TCP协议概述
- 2.1.1 TCP的历史背景
- 2.1.2 TCP的设计目标
- 2.1.3 TCP的基本特性
- 2.1.4 TCP与其他传输协议的比较
- 2.1.5 TCP的应用场景
- 2.2 TCP头部结构
- 2.2.1 TCP头部格式
- 2.2.2 TCP头部字段详解
- 源端口号和目的端口号(各16位)
- 序列号(32位)
- 确认号(32位)
- 数据偏移(4位)
- 保留字段(4位)
- 控制位(6位)
- 窗口大小(16位)
- 校验和(16位)
- 紧急指针(16位)
- 选项(可变长度)
- 2.2.3 TCP头部在Linux内核中的实现
- TCP头部的构建
- TCP头部的解析
- TCP选项的处理
- 2.2.4 TCP头部的实际应用
- 网络编程
- 协议分析
- 故障排除
- 2.3 TCP三次握手
- 2.3.1 三次握手的基本过程
- 2.3.2 三次握手的详细分析
- 第一次握手(SYN)
- 第二次握手(SYN+ACK)
- 第三次握手(ACK)
- 2.3.3 三次握手的源码实现
- 客户端发送SYN(第一次握手)
- 服务器发送SYN+ACK(第二次握手)
- 客户端发送ACK(第三次握手)
- 2.3.4 三次握手的作用与必要性
- 同步序列号
- 防止历史连接干扰
- 确认双方的收发能力
- 协商连接参数
- 2.3.5 三次握手的常见问题与优化
- SYN洪泛攻击
- 连接建立延迟
- 半连接队列溢出
- 全连接队列溢出
- 2.4 TCP四次挥手
- 2.4.1 四次挥手的基本过程
- 2.4.2 四次挥手的详细分析
- 第一次挥手(FIN)
- 第二次挥手(ACK)
- 第三次挥手(FIN)
- 第四次挥手(ACK)
- 2.4.3 四次挥手的源码实现
- 主动关闭方发送FIN(第一次挥手)
- 被动关闭方发送ACK(第二次挥手)
- 被动关闭方发送FIN(第三次挥手)
- 主动关闭方发送ACK(第四次挥手)
- 2.4.4 四次挥手的作用与必要性
- 确保数据完整传输
- 处理半关闭状态
- TIME_WAIT状态的作用
- 资源的正确释放
- 2.4.5 四次挥手的常见问题与优化
- TIME_WAIT状态过多
- FIN_WAIT2状态卡住
- CLOSE_WAIT状态过多
- 连接重置(RST)
- 2.5 TCP状态转换图
- 2.5.1 TCP状态概述
- 2.5.2 TCP状态转换图
- 2.5.3 TCP状态在Linux内核中的实现
- 连接建立阶段的状态转换
- 连接关闭阶段的状态转换
- 2.5.4 TCP状态的实际应用
- 网络编程
- 协议分析
- 故障排除
- 2.5.5 TCP状态的常见问题与优化
- 大量TIME_WAIT状态
- 大量CLOSE_WAIT状态
- 大量SYN_RCVD状态
- 大量FIN_WAIT2状态
2. TCP协议详解
2.1 TCP协议概述
传输控制协议(Transmission Control Protocol,TCP)是互联网协议族中最核心的协议之一,由Vint Cerf和Bob Kahn在1974年设计并于1981年在RFC 793中正式定义。TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,为应用程序提供了可靠的数据传输服务,确保数据能够无错、按序地从源主机传输到目标主机。
2.1.1 TCP的历史背景
TCP的诞生源于美国国防部高级研究计划局(DARPA)对可靠军事通信网络的需求。在早期的ARPANET中,网络通信主要依赖于网络控制协议(NCP),但随着网络规模的扩大和异构网络的出现,需要一种更加健壮和灵活的协议来处理复杂的网络环境。
1973年,Vint Cerf和Bob Kahn开始设计一种能够在不同网络之间传输数据的协议,这就是TCP/IP的前身。最初,TCP和IP是一个统一的协议,后来为了模块化和灵活性,在1978年被分离为两个独立的协议。
1981年,TCP协议在RFC 793中被正式定义,并逐渐成为互联网的核心协议。随着互联网的发展,TCP也经历了多次改进和优化,如拥塞控制算法的引入(1988年)、选择性确认(SACK)的支持(1996年)等。
2.1.2 TCP的设计目标
TCP的设计目标主要包括:
-
可靠传输:确保数据能够无错、完整地从源主机传输到目标主机,即使底层网络不可靠。
-
有序交付:确保数据按照发送顺序交付给接收方,即使底层网络可能导致数据包乱序到达。
-
流量控制:防止发送方发送数据的速率超过接收方处理能力,避免接收方缓冲区溢出。
-
拥塞控制:防止过多数据注入到网络中,导致网络拥塞和性能下降。
-
面向连接:在数据传输前建立连接,确保双方都准备好进行通信,并在传输完成后正常关闭连接。
-
全双工通信:支持数据在两个方向上同时传输,提高通信效率。
-
可靠性与效率的平衡:在提供可靠传输的同时,尽量减少额外开销,提高网络利用率。
2.1.3 TCP的基本特性
TCP具有以下基本特性:
-
面向连接:TCP在传输数据前需要先建立连接(三次握手),传输完成后需要释放连接(四次挥手)。
-
可靠传输:TCP通过确认、重传、校验和等机制确保数据的可靠传输。
-
面向字节流:TCP将应用层交付的数据视为无结构的字节流,不保留应用层的消息边界。
-
流量控制:TCP使用滑动窗口机制进行流量控制,防止发送方发送速率过快导致接收方缓冲区溢出。
-
拥塞控制:TCP通过慢启动、拥塞避免、快速重传和快速恢复等算法进行拥塞控制,适应网络状况。
-
全双工通信:TCP连接的两端都可以同时发送和接收数据。
-
面向报文段:TCP将字节流分割成报文段进行传输,每个报文段都有序列号,用于重组和确认。
2.1.4 TCP与其他传输协议的比较
与其他传输协议相比,TCP有其独特的优势和局限性:
与UDP的比较:
- TCP提供可靠传输,UDP不保证可靠性
- TCP面向连接,UDP无连接
- TCP有流量控制和拥塞控制,UDP没有
- TCP开销较大,UDP开销小
- TCP适用于对可靠性要求高的应用,UDP适用于对实时性要求高的应用
与SCTP的比较:
- SCTP支持多流和多宿主,TCP不支持
- SCTP提供消息边界保护,TCP是纯字节流
- SCTP有内置的心跳机制,TCP需要通过keepalive选项实现
- SCTP防止SYN洪泛攻击,TCP较易受此类攻击
与QUIC的比较:
- QUIC基于UDP构建,减少了连接建立的延迟
- QUIC支持连接迁移,TCP不支持
- QUIC在应用层实现拥塞控制,可以更灵活地定制
- QUIC集成了TLS加密,提供更好的安全性
- QUIC解决了TCP的队头阻塞问题
2.1.5 TCP的应用场景
TCP广泛应用于各种需要可靠数据传输的场景,主要包括:
-
Web浏览:HTTP/HTTPS协议通常基于TCP,确保网页内容的完整传输。
-
电子邮件:SMTP、POP3、IMAP等电子邮件协议使用TCP,确保邮件内容不丢失。
-
文件传输:FTP、SFTP等文件传输协议使用TCP,确保文件的完整性。
-
远程登录:SSH、Telnet等远程登录协议使用TCP,确保命令和响应的可靠传输。
-
数据库访问:大多数数据库客户端与服务器之间的通信使用TCP,确保数据的一致性。
-
流媒体传输:某些流媒体应用使用TCP,特别是在带宽充足且对可靠性要求高的场景。
-
即时通讯:许多即时通讯应用使用TCP,确保消息的可靠传递。
TCP的这些特性和应用场景使其成为互联网的基石,支撑着各种各样的网络应用和服务。
2.2 TCP头部结构
TCP头部是TCP协议的核心组成部分,包含了控制信息和元数据,用于实现TCP的各种功能。理解TCP头部结构对于深入理解TCP协议的工作原理至关重要。
2.2.1 TCP头部格式
TCP头部的标准长度为20字节(不包含选项),最大可达60字节(包含选项)。下面是TCP头部的详细结构:
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源端口号 | 目的端口号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序列号 (Sequence Number) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 确认号 (Acknowledgment Number) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据偏移 | 保留 |U|A|P|R|S|F| 窗口大小 (Window) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和 (Checksum) | 紧急指针 (Urgent Pointer)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 选项 (Options) [可变长度] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
在Linux内核中,TCP头部结构定义如下:
// file: include/uapi/linux/tcp.h
struct tcphdr {__u16 source; // 源端口__u16 dest; // 目的端口__u32 seq; // 序列号__u32 ack_seq; // 确认号__u16 doff:4, // 数据偏移(头部长度)res1:4, // 保留cwr:1, // 拥塞窗口减少标志ece:1, // ECN回显标志urg:1, // 紧急标志ack:1, // 确认标志psh:1, // 推送标志rst:1, // 复位标志syn:1, // 同步标志fin:1; // 结束标志__u16 window; // 窗口大小__sum16 check; // 校验和__u16 urg_ptr; // 紧急指针
};
2.2.2 TCP头部字段详解
下面详细解释TCP头部的各个字段:
源端口号和目的端口号(各16位)
- 源端口号:发送方的端口号,用于标识发送数据的应用程序。
- 目的端口号:接收方的端口号,用于标识接收数据的应用程序。
这两个字段共同构成了传输层的寻址机制,使得数据能够准确地交付给正确的应用程序。
序列号(32位)
序列号用于标识从TCP发送端向TCP接收端发送的数据字节流的第一个字节的编号。在建立连接时,通过SYN包交换随机的初始序列号(ISN)。
序列号的主要作用是:
- 确保数据按顺序交付
- 检测丢失的数据
- 去除重复的数据
- 支持TCP的可靠传输机制
确认号(32位)
确认号表示接收方期望从发送方收到的下一个字节的序列号。换句话说,确认号等于已成功接收的数据的最高序列号加1。
确认号的主要作用是:
- 告知发送方已成功接收的数据
- 触发发送方重传丢失的数据
- 支持TCP的可靠传输机制
数据偏移(4位)
数据偏移字段指示TCP头部的长度,以32位(4字节)为单位。由于TCP头部可能包含可变长度的选项,因此需要这个字段来指示数据的起始位置。
数据偏移的最小值为5(表示20字节的标准头部),最大值为15(表示60字节的头部,包含40字节的选项)。
保留字段(4位)
这些位保留供将来使用,目前应该设置为0。
控制位(6位)
TCP头部包含6个标志位,用于控制TCP连接的状态和行为:
- URG(Urgent):表示紧急指针字段有效,用于标识紧急数据。
- ACK(Acknowledgment):表示确认号字段有效,几乎在所有已建立连接的数据包中都会设置。
- PSH(Push):表示应该立即将数据推送给应用程序,而不是等待缓冲区填满。
- RST(Reset):表示应该立即重置连接,通常用于处理异常情况。
- SYN(Synchronize):表示请求建立连接,用于三次握手的第一步和第二步。
- FIN(Finish):表示发送方已经没有数据要发送了,用于四次挥手。
此外,还有两个与显式拥塞通知(ECN)相关的标志位:
- CWR(Congestion Window Reduced):表示发送方已经收到了设置了ECE标志的TCP段,并已经响应了拥塞通知。
- ECE(ECN-Echo):在建立连接时表示支持ECN;在已建立连接时表示网络中存在拥塞。
窗口大小(16位)
窗口大小字段指示发送方当前可以接收的数据量,以字节为单位。它是TCP流量控制的关键机制,用于防止发送方发送过多数据导致接收方缓冲区溢出。
由于16位字段的限制,标准窗口大小最大为65535字节。为了支持更大的窗口,TCP引入了窗口缩放选项,允许将窗口大小乘以一个缩放因子(最大为14),从而支持最大约1GB的窗口。
校验和(16位)
校验和字段用于检测TCP头部和数据在传输过程中是否被损坏。它是对TCP头部、数据以及一个伪头部(包含源IP地址、目的IP地址、协议号和TCP长度)进行计算得出的。
校验和的计算方法是:将所有16位字进行反码求和,然后取反码。接收方使用相同的算法计算校验和,如果结果为0,则认为数据完整;否则,认为数据已损坏,将丢弃该段。
紧急指针(16位)
当URG标志位设置时,紧急指针字段有效,它指示紧急数据的末尾相对于当前序列号的偏移量。紧急数据应该优先处理,不受流量控制的限制。
紧急指针机制在现代网络中使用较少,但它提供了一种在正常数据流之外传输紧急信息的方法。
选项(可变长度)
TCP头部可以包含多种选项,用于扩展TCP的功能。常见的选项包括:
- 最大段大小(MSS):指示发送方愿意接收的最大段大小,通常在SYN包中设置。
- 窗口缩放:允许将窗口大小乘以一个缩放因子,支持更大的窗口。
- 选择性确认(SACK):允许接收方确认非连续的数据块,提高重传效率。
- 时间戳:用于计算更准确的往返时间(RTT)和防止序列号回绕问题。
- TCP快速打开(TFO):允许在三次握手期间发送数据,减少连接建立的延迟。
选项的格式通常为:选项类型(1字节)、选项长度(1字节,对于某些选项可能省略)和选项数据(可变长度)。
2.2.3 TCP头部在Linux内核中的实现
在Linux内核中,TCP头部的处理涉及多个函数和数据结构。以下是一些关键的实现细节:
TCP头部的构建
当发送TCP段时,内核需要构建TCP头部。这通常在tcp_transmit_skb
函数中完成:
// 简化版的TCP头部构建过程
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,gfp_t gfp_mask)
{// ...th = tcp_hdr(skb);th->source = inet->inet_sport;th->dest = inet->inet_dport;th->seq = htonl(tcb->seq);th->ack_seq = htonl(tp->rcv_nxt);*(((__u16 *)th) + 6) = htons(((tp->tcp_header_len >> 2) << 12) |tcb->tcp_flags);// ...// 设置窗口大小if (likely(!tp->rx_opt.wscale_ok)) {th->window = htons(min(tp->rcv_wnd, 65535U));} else {th->window = htons(min(tp->rcv_wnd >> tp->rx_opt.rcv_wscale, 65535U));}// ...// 计算校验和if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) {th->check = ~tcp_v4_check(skb->len, saddr, daddr, 0);skb->csum_start = skb_transport_header(skb) - skb->head;skb->csum_offset = offsetof(struct tcphdr, check);} else {th->check = tcp_v4_check(skb->len, saddr, daddr,csum_partial(th, thlen, skb->csum));}// ...
}
TCP头部的解析
当接收TCP段时,内核需要解析TCP头部。这通常在tcp_v4_rcv
或类似函数中完成:
// 简化版的TCP头部解析过程
int tcp_v4_rcv(struct sk_buff *skb)
{// ...th = tcp_hdr(skb);// 检查校验和if (!pskb_may_pull(skb, sizeof(struct tcphdr)) ||(skb_checksum_complete(skb))) {goto discard_it;}// 解析TCP头部字段source = th->source;dest = th->dest;seq = ntohl(th->seq);ack_seq = ntohl(th->ack_seq);// ...// 根据TCP状态和头部字段进行处理// ...
}
TCP选项的处理
TCP选项的处理相对复杂,通常在tcp_parse_options
函数中完成:
// 简化版的TCP选项解析过程
void tcp_parse_options(struct sk_buff *skb, struct tcp_options_received *opt_rx,int estab, struct tcp_fastopen_cookie *foc)
{// ...ptr = (unsigned char *)(th + 1);while (ptr < end) {switch (*ptr) {case TCPOPT_EOL:// 选项列表结束return;case TCPOPT_NOP:// 无操作,用于填充ptr++;continue;case TCPOPT_MSS:// 最大段大小选项// ...break;case TCPOPT_WINDOW:// 窗口缩放选项// ...break;case TCPOPT_TIMESTAMP:// 时间戳选项// ...break;case TCPOPT_SACK_PERM:// 选择性确认许可选项// ...break;// 其他选项的处理// ...}// 移动到下一个选项ptr += optlen;}
}
2.2.4 TCP头部的实际应用
理解TCP头部结构对于网络编程、协议分析和故障排除都非常重要。以下是一些实际应用场景:
网络编程
在使用套接字API进行网络编程时,虽然TCP头部的细节通常由操作系统处理,但了解TCP头部结构有助于理解TCP的行为和优化应用程序的网络性能。例如:
- 设置适当的套接字选项(如TCP_NODELAY、TCP_CORK等)
- 理解和处理TCP连接的建立和关闭
- 优化应用层协议的设计,考虑TCP的特性
协议分析
使用Wireshark等网络分析工具时,了解TCP头部结构有助于解读捕获的数据包,分析网络通信过程:
- 跟踪TCP连接的建立和关闭
- 分析TCP的流量控制和拥塞控制行为
- 诊断网络性能问题,如重传、延迟等
故障排除
在排查网络问题时,了解TCP头部结构有助于定位和解决问题:
- 识别连接建立失败的原因(如SYN包丢失)
- 分析数据传输中断的原因(如RST包)
- 诊断性能下降的原因(如窗口大小减小、频繁重传等)
TCP头部结构看似简单,但其设计精巧,每个字段都有其特定的用途,共同支撑了TCP的各种功能。深入理解TCP头部结构是掌握TCP协议的基础。
2.3 TCP三次握手
TCP三次握手(Three-Way Handshake)是TCP连接建立过程中的关键机制,它确保了通信双方都具备发送和接收数据的能力,并协商了初始序列号等连接参数。三次握手的过程精确而高效,是TCP可靠连接的基础。
2.3.1 三次握手的基本过程
TCP三次握手的基本过程如下:
-
第一次握手(SYN):客户端发送一个SYN(同步)包,其中包含随机生成的初始序列号(ISN_c),并将连接状态设置为SYN_SENT。
-
第二次握手(SYN+ACK):服务器收到SYN包后,回复一个SYN+ACK(同步+确认)包,其中包含服务器生成的初始序列号(ISN_s)和对客户端序列号的确认(ACK=ISN_c+1),并将连接状态设置为SYN_RCVD。
-
第三次握手(ACK):客户端收到SYN+ACK包后,发送一个ACK(确认)包,确认服务器的序列号(ACK=ISN_s+1),并将连接状态设置为ESTABLISHED。服务器收到ACK包后,也将连接状态设置为ESTABLISHED。
这个过程可以用下图表示:
客户端 服务器| || SYN ||------------------------------------->|| seq=ISN_c, ack=0 || || SYN+ACK ||<-------------------------------------|| seq=ISN_s, ack=ISN_c+1 || || ACK ||------------------------------------->|| seq=ISN_c+1, ack=ISN_s+1 || |
连接建立| |连接建立
2.3.2 三次握手的详细分析
第一次握手(SYN)
客户端通过调用connect()
函数发起连接请求,这会触发TCP协议栈发送SYN包。SYN包的特点是:
- SYN标志位设置为1
- 序列号设置为客户端的初始序列号(ISN_c)
- 确认号设置为0(因为还没有收到对方的序列号)
- 可能包含TCP选项,如MSS(最大段大小)、窗口缩放、SACK许可等
客户端发送SYN包后,进入SYN_SENT状态,等待服务器的响应。如果在一定时间内没有收到响应,客户端会重传SYN包,重传次数和间隔由系统参数控制。
第二次握手(SYN+ACK)
服务器通过listen()
函数进入监听状态,等待客户端的连接请求。当服务器收到SYN包后,会执行以下操作:
- 分配资源创建一个新的传输控制块(TCB),用于存储连接信息
- 生成自己的初始序列号(ISN_s)
- 发送SYN+ACK包,其特点是:
- SYN和ACK标志位都设置为1
- 序列号设置为服务器的初始序列号(ISN_s)
- 确认号设置为客户端序列号加1(ISN_c+1)
- 可能包含TCP选项,响应客户端的选项请求
服务器发送SYN+ACK包后,进入SYN_RCVD状态,等待客户端的确认。此时连接处于"半开"状态,服务器已分配资源但连接尚未完全建立。
第三次握手(ACK)
客户端收到服务器的SYN+ACK包后,会执行以下操作:
- 验证确认号是否为ISN_c+1,确保这是对自己SYN包的响应
- 发送ACK包,其特点是:
- ACK标志位设置为1
- 序列号设置为ISN_c+1
- 确认号设置为ISN_s+1
- 可能包含数据(在某些实现中)
客户端发送ACK包后,进入ESTABLISHED状态,可以开始发送数据。服务器收到ACK包后,也进入ESTABLISHED状态,连接完全建立。
2.3.3 三次握手的源码实现
下面我们通过Linux内核源码来分析TCP三次握手的实现。
客户端发送SYN(第一次握手)
当应用程序调用connect()
函数时,最终会调用到tcp_v4_connect()
函数,该函数负责发送SYN包:
// 简化版的tcp_v4_connect函数
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{// ...// 生成初始序列号tp->write_seq = secure_tcp_seq(saddr, daddr, sport, dport);// 设置SYN_SENT状态tcp_set_state(sk, TCP_SYN_SENT);// 设置连接参数tp->ts_recent = 0;tp->rcv_wnd = 0;tp->rcv_wup = 0;tp->snd_wl1 = 0;// 发送SYN包err = tcp_transmit_skb(sk, skb, 1, sk->sk_allocation);// 启动重传定时器inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,inet_csk(sk)->icsk_rto, TCP_RTO_MAX);// ...return 0;
}
在tcp_transmit_skb
函数中,会构建TCP头部并设置SYN标志位:
// 简化版的TCP头部构建过程
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,gfp_t gfp_mask)
{// ...// 构建TCP头部th = tcp_hdr(skb);th->source = inet->inet_sport;th->dest = inet->inet_dport;th->seq = htonl(tcb->seq);th->ack_seq = htonl(tp->rcv_nxt);// 设置SYN标志位tcp_header_size = tcp_options_size + sizeof(struct tcphdr);th->doff = (tcp_header_size >> 2);TCP_SKB_CB(skb)->tcp_flags = TCPHDR_SYN;th->syn = 1;// ...// 发送数据包err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);// ...return err;
}
服务器发送SYN+ACK(第二次握手)
服务器通过listen()
函数进入监听状态,当收到SYN包时,会调用tcp_v4_do_rcv()
函数处理:
// 简化版的tcp_v4_do_rcv函数
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{// ...// 根据套接字状态处理if (sk->sk_state == TCP_LISTEN) {// 处理监听状态下收到的包struct sock *nsk = tcp_v4_hnd_req(sk, skb);if (!nsk)goto discard;// 如果是新连接,处理SYN包if (nsk != sk) {if (tcp_child_process(sk, nsk, skb)) {// 子套接字处理成功,释放skb__kfree_skb(skb);}return 0;}}// ...// 处理已建立连接的数据return tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len);
}
在tcp_v4_hnd_req
函数中,会检查是否有匹配的请求套接字,如果没有,则创建一个新的请求套接字:
// 简化版的tcp_v4_hnd_req函数
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{// ...// 查找匹配的请求struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,iph->saddr, iph->daddr);if (req)return tcp_check_req(sk, skb, req, prev, false);// 没有匹配的请求,处理新的SYNif (th->syn) {// 处理SYN包,创建新的请求套接字return tcp_v4_cookie_check(sk, skb);}// ...
}
当收到SYN包并创建新的请求套接字后,会调用tcp_v4_send_synack
函数发送SYN+ACK包:
// 简化版的tcp_v4_send_synack函数
static int tcp_v4_send_synack(struct sock *sk, struct dst_entry *dst,struct request_sock *req,struct tcp_fastopen_cookie *foc)
{// ...// 构建SYN+ACK包skb = tcp_make_synack(sk, dst, req, foc);if (!skb)return -ENOMEM;// 发送SYN+ACK包err = ip_build_and_send_pkt(skb, sk, req->rsk_ops->saddr(req),req->rsk_ops->daddr(req), NULL);// ...return err;
}
在tcp_make_synack
函数中,会构建SYN+ACK包的TCP头部:
// 简化版的tcp_make_synack函数
struct sk_buff *tcp_make_synack(struct sock *sk, struct dst_entry *dst,struct request_sock *req,struct tcp_fastopen_cookie *foc)
{// ...// 构建TCP头部th = tcp_hdr(skb);th->source = htons(ireq->ir_num);th->dest = ireq->ir_rmt_port;th->seq = htonl(tcp_rsk(req)->snt_isn);th->ack_seq = htonl(tcp_rsk(req)->rcv_nxt);// 设置SYN和ACK标志位th->syn = 1;th->ack = 1;// ...return skb;
}
客户端发送ACK(第三次握手)
客户端收到服务器的SYN+ACK包后,会调用tcp_rcv_state_process
函数处理:
// 简化版的tcp_rcv_state_process函数
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...// 根据套接字状态处理switch (sk->sk_state) {case TCP_SYN_SENT:// 处理SYN_SENT状态下收到的包queued = tcp_rcv_synsent_state_process(sk, skb, th, len);// ...break;// 其他状态的处理// ...}// ...
}
在tcp_rcv_synsent_state_process
函数中,会验证SYN+ACK包并发送ACK包:
// 简化版的tcp_rcv_synsent_state_process函数
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...// 验证SYN+ACK包if (th->ack) {// 检查确认号if (!tcp_ack(sk, skb, FLAG_SLOWPATH))return 1;}// 检查SYN标志位if (th->syn) {// 处理SYN+ACKtcp_rcv_synrecv_state_fastpath(sk, skb, th);// 发送ACK包tcp_send_ack(sk);// 设置ESTABLISHED状态tcp_set_state(sk, TCP_ESTABLISHED);// ...}// ...
}
在tcp_send_ack
函数中,会构建并发送ACK包:
// 简化版的tcp_send_ack函数
void tcp_send_ack(struct sock *sk)
{// ...// 构建ACK包buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));if (!buff)return;// 设置TCP头部skb_reserve(buff, MAX_TCP_HEADER);tcp_init_nondata_skb(buff, tp->snd_una, TCPHDR_ACK);// 发送ACK包tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));// ...
}
2.3.4 三次握手的作用与必要性
TCP三次握手的设计有其深刻的考虑,主要解决以下问题:
同步序列号
三次握手使得通信双方能够同步各自的初始序列号(ISN),这是TCP可靠传输的基础。序列号的同步确保了:
- 数据能够按顺序重组
- 丢失的数据能够被检测和重传
- 重复的数据能够被识别和丢弃
防止历史连接干扰
如果网络中存在延迟的、过期的连接请求,三次握手可以有效防止这些请求干扰新的连接。假设一个场景:
- 客户端发送SYN包,但由于网络问题,该包在网络中延迟了
- 客户端超时后重新发送SYN包,与服务器建立了连接,然后关闭连接
- 之前延迟的SYN包最终到达服务器
在这种情况下,如果只有两次握手,服务器会认为这是一个新的连接请求,并分配资源。但由于客户端已经不再期望这个连接,这会导致服务器资源的浪费。
有了三次握手,服务器发送SYN+ACK后,客户端不会回应最后的ACK,服务器会在超时后释放资源,从而避免了资源浪费。
确认双方的收发能力
三次握手确保了通信双方都具备发送和接收数据的能力:
- 第一次握手:客户端证明自己有发送能力
- 第二次握手:服务器证明自己有接收和发送能力
- 第三次握手:客户端证明自己有接收能力
只有在双方都确认了对方的收发能力后,连接才被视为完全建立。
协商连接参数
三次握手过程中,双方可以协商多种连接参数,如:
- 最大段大小(MSS)
- 窗口缩放因子
- 选择性确认(SACK)的支持
- 时间戳选项
- 快速打开(TFO)cookie
这些参数的协商使得TCP连接能够适应不同的网络环境和应用需求。
2.3.5 三次握手的常见问题与优化
SYN洪泛攻击
SYN洪泛攻击是一种常见的拒绝服务攻击,攻击者发送大量的SYN包但不完成三次握手,导致服务器的半连接队列被填满,无法处理正常的连接请求。
防御措施包括:
- SYN Cookie:在收到SYN包时不立即分配资源,而是将连接信息编码在SYN+ACK包的序列号中,只有在收到最后的ACK包后才分配资源
- 增加半连接队列大小:通过调整系统参数增加半连接队列的容量
- 减少SYN+ACK重传次数和超时时间:加快对未完成的握手的清理
- 使用防火墙或负载均衡器:过滤可疑的SYN包或限制单一来源的SYN包数量
连接建立延迟
传统的三次握手需要1.5个往返时间(RTT)才能完成,在高延迟网络中可能导致明显的延迟。为了减少这种延迟,TCP引入了一些优化技术:
- TCP快速打开(TFO):允许在SYN包中携带数据,并在第三次握手完成前就将数据交付给应用程序,减少了一个RTT的延迟
- 并行连接:同时建立多个TCP连接,分散数据传输,提高并行度
- 持久连接:复用已建立的TCP连接,避免频繁的连接建立和关闭
半连接队列溢出
当服务器收到大量的SYN包时,半连接队列可能会溢出,导致新的连接请求被丢弃。解决方法包括:
- 增加半连接队列大小:通过调整
net.ipv4.tcp_max_syn_backlog
参数 - 启用SYN Cookie:通过设置
net.ipv4.tcp_syncookies=1
- 调整SYN+ACK重传参数:通过
net.ipv4.tcp_synack_retries
控制重传次数 - 使用负载均衡:分散连接请求到多个服务器
全连接队列溢出
当服务器完成三次握手但应用程序未及时接受连接时,全连接队列可能会溢出。解决方法包括:
- 增加全连接队列大小:通过调整
net.core.somaxconn
和listen()
函数的backlog参数 - 优化应用程序:确保应用程序能够及时处理新的连接
- 使用异步I/O或事件驱动模型:提高连接处理的效率
TCP三次握手是TCP协议的基础机制之一,它通过精心设计的交互过程,确保了连接的可靠建立。理解三次握手的原理和实现,对于网络编程、协议优化和故障排除都有重要意义。
2.4 TCP四次挥手
TCP四次挥手(Four-Way Handshake)是TCP连接终止过程中的关键机制,它确保了连接的优雅关闭,使得双方都能够完成数据传输并释放资源。四次挥手的过程精确而可靠,是TCP连接生命周期的最后阶段。
2.4.1 四次挥手的基本过程
TCP四次挥手的基本过程如下:
-
第一次挥手(FIN):主动关闭方发送一个FIN(结束)包,表示已经没有数据要发送了,但仍然可以接收数据,并将连接状态设置为FIN_WAIT_1。
-
第二次挥手(ACK):被动关闭方收到FIN包后,发送一个ACK(确认)包,确认收到了FIN包,并将连接状态设置为CLOSE_WAIT。主动关闭方收到ACK后,将连接状态设置为FIN_WAIT_2。
-
第三次挥手(FIN):被动关闭方完成所有数据发送后,发送一个FIN包,表示也没有数据要发送了,并将连接状态设置为LAST_ACK。
-
第四次挥手(ACK):主动关闭方收到FIN包后,发送一个ACK包,确认收到了FIN包,并将连接状态设置为TIME_WAIT。被动关闭方收到ACK后,将连接状态设置为CLOSED。主动关闭方在TIME_WAIT状态等待2MSL(最大报文生存时间)后,也将连接状态设置为CLOSED。
这个过程可以用下图表示:
主动关闭方 被动关闭方| || FIN ||------------------------------------->|| seq=u, ack=v |
FIN_WAIT_1| |CLOSE_WAIT| ACK ||<-------------------------------------|| seq=v, ack=u+1 |
FIN_WAIT_2| || || FIN ||<-------------------------------------|| seq=w, ack=u+1 |
TIME_WAIT | |LAST_ACK| ACK ||------------------------------------->|| seq=u+1, ack=w+1 || |CLOSED| || (等待2MSL) || |
CLOSED | |
2.4.2 四次挥手的详细分析
第一次挥手(FIN)
当应用程序调用close()
或shutdown()
函数时,会触发TCP协议栈发送FIN包。FIN包的特点是:
- FIN标志位设置为1
- 序列号设置为当前已发送数据的最后一个字节的序列号加1
- 确认号设置为已接收数据的最后一个字节的序列号加1
- 可能包含最后的数据
主动关闭方发送FIN包后,进入FIN_WAIT_1状态,表示自己已经完成了数据发送,但仍然可以接收数据。
第二次挥手(ACK)
被动关闭方收到FIN包后,会执行以下操作:
- 向应用程序发送一个文件结束(EOF)信号,通知应用程序对方已经关闭了发送通道
- 发送ACK包,确认收到了FIN包,其特点是:
- ACK标志位设置为1
- 确认号设置为收到的FIN包的序列号加1
- 序列号设置为当前已发送数据的最后一个字节的序列号加1
被动关闭方发送ACK包后,进入CLOSE_WAIT状态,表示自己已经知道对方不再发送数据,但自己还可能有数据要发送。主动关闭方收到ACK包后,进入FIN_WAIT_2状态,等待被动关闭方的FIN包。
第三次挥手(FIN)
被动关闭方在完成所有数据发送后(可能是立即的,也可能是经过一段时间的处理),会发送FIN包,其特点是:
- FIN标志位设置为1
- 序列号设置为当前已发送数据的最后一个字节的序列号加1
- 确认号设置为已接收数据的最后一个字节的序列号加1
- 可能包含最后的数据
被动关闭方发送FIN包后,进入LAST_ACK状态,等待最后的确认。
第四次挥手(ACK)
主动关闭方收到FIN包后,会执行以下操作:
- 发送ACK包,确认收到了FIN包,其特点是:
- ACK标志位设置为1
- 确认号设置为收到的FIN包的序列号加1
- 序列号设置为当前已发送数据的最后一个字节的序列号加1
主动关闭方发送ACK包后,进入TIME_WAIT状态,等待2MSL(最大报文生存时间)后才会完全关闭连接。被动关闭方收到ACK包后,立即进入CLOSED状态,释放所有资源。
2.4.3 四次挥手的源码实现
下面我们通过Linux内核源码来分析TCP四次挥手的实现。
主动关闭方发送FIN(第一次挥手)
当应用程序调用close()
函数时,最终会调用到tcp_close()
函数,该函数负责发送FIN包:
// 简化版的tcp_close函数
void tcp_close(struct sock *sk, long timeout)
{// ...// 检查是否有未发送的数据if (sk->sk_send_head) {// 还有数据未发送,设置LINGER定时器if (timeout) {// ...} else {// 没有设置超时,立即关闭tcp_send_active_reset(sk, sk->sk_allocation);goto adjudge_to_death;}}// 没有未发送的数据,发送FIN包if (tcp_close_state(sk)) {tcp_send_fin(sk);}// ...
}
在tcp_send_fin
函数中,会构建并发送FIN包:
// 简化版的tcp_send_fin函数
void tcp_send_fin(struct sock *sk)
{// ...// 构建FIN包buff = tcp_write_queue_tail(sk);if (buff && skb_queue_len(&sk->sk_write_queue) == 1) {// 如果写队列中只有一个包,直接在这个包上设置FIN标志TCP_SKB_CB(buff)->tcp_flags |= TCPHDR_FIN;TCP_SKB_CB(buff)->end_seq++;tp->write_seq++;} else {// 创建一个新的FIN包buff = alloc_skb_fclone(MAX_TCP_HEADER, sk->sk_allocation);if (!buff)return;// 设置FIN标志tcp_init_nondata_skb(buff, tp->write_seq, TCPHDR_ACK | TCPHDR_FIN);tcp_queue_skb(sk, buff);}// 发送FIN包tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);// ...
}
被动关闭方发送ACK(第二次挥手)
被动关闭方收到FIN包后,会调用tcp_rcv_state_process
函数处理:
// 简化版的tcp_rcv_state_process函数
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...// 根据套接字状态处理switch (sk->sk_state) {// 其他状态的处理// ...case TCP_ESTABLISHED:// 处理ESTABLISHED状态下收到的包if (th->fin) {// 收到FIN包,进入CLOSE_WAIT状态tcp_set_state(sk, TCP_CLOSE_WAIT);// 通知应用程序sk->sk_shutdown |= RCV_SHUTDOWN;sock_set_flag(sk, SOCK_DONE);// 发送ACK确认FINtcp_send_ack(sk);// ...}break;// 其他状态的处理// ...}// ...
}
被动关闭方发送FIN(第三次挥手)
被动关闭方在应用程序调用close()
函数后,会发送FIN包,过程与主动关闭方类似:
// 简化版的tcp_close函数
void tcp_close(struct sock *sk, long timeout)
{// ...// 检查是否有未发送的数据if (sk->sk_send_head) {// 还有数据未发送,设置LINGER定时器if (timeout) {// ...} else {// 没有设置超时,立即关闭tcp_send_active_reset(sk, sk->sk_allocation);goto adjudge_to_death;}}// 没有未发送的数据,发送FIN包if (tcp_close_state(sk)) {tcp_send_fin(sk);}// ...
}
不同的是,此时套接字的状态是CLOSE_WAIT,调用tcp_close_state
会将状态设置为LAST_ACK:
// 简化版的tcp_close_state函数
int tcp_close_state(struct sock *sk)
{// ...switch (sk->sk_state) {case TCP_CLOSE_WAIT:// 从CLOSE_WAIT状态转换为LAST_ACK状态tcp_set_state(sk, TCP_LAST_ACK);break;// 其他状态的处理// ...}// ...
}
主动关闭方发送ACK(第四次挥手)
主动关闭方收到FIN包后,会调用tcp_rcv_state_process
函数处理:
// 简化版的tcp_rcv_state_process函数
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...// 根据套接字状态处理switch (sk->sk_state) {// 其他状态的处理// ...case TCP_FIN_WAIT2:// 处理FIN_WAIT2状态下收到的包if (th->fin) {// 收到FIN包,进入TIME_WAIT状态tcp_time_wait(sk, TCP_TIME_WAIT, 0);// 发送ACK确认FINtcp_send_ack(sk);// ...}break;// 其他状态的处理// ...}// ...
}
在tcp_time_wait
函数中,会将套接字状态设置为TIME_WAIT,并启动2MSL定时器:
// 简化版的tcp_time_wait函数
void tcp_time_wait(struct sock *sk, int state, int timeo)
{// ...// 设置TIME_WAIT状态tcp_set_state(sk, state);// 启动2MSL定时器inet_csk_reset_keepalive_timer(sk, TCP_TIMEWAIT_LEN);// ...
}
2.4.4 四次挥手的作用与必要性
TCP四次挥手的设计有其深刻的考虑,主要解决以下问题:
确保数据完整传输
四次挥手确保了双方都能够完成数据的发送,避免了数据丢失。具体来说:
- 第一次挥手后,主动关闭方不再发送数据,但仍然可以接收数据
- 第二次挥手后,被动关闭方知道主动关闭方不再发送数据,但自己仍然可以发送数据
- 第三次挥手后,被动关闭方也不再发送数据
- 第四次挥手后,连接完全关闭
这种设计使得即使一方决定关闭连接,另一方仍然有机会发送剩余的数据,确保了数据的完整传输。
处理半关闭状态
TCP支持半关闭(Half-Close)状态,即一方关闭了发送通道,但仍然保持接收通道开放。四次挥手的设计使得半关闭状态能够被正确处理:
- 第一次挥手和第二次挥手完成后,连接处于半关闭状态,主动关闭方不再发送数据,但仍然可以接收数据
- 应用程序可以通过
shutdown()
函数实现半关闭,只关闭发送通道或接收通道
半关闭状态在某些应用场景中非常有用,如文件传输完成后,客户端可以关闭发送通道,但仍然保持接收通道开放,以接收服务器的确认或错误信息。
TIME_WAIT状态的作用
四次挥手中,主动关闭方在发送最后的ACK后会进入TIME_WAIT状态,并等待2MSL(最大报文生存时间)才会完全关闭连接。这个设计有两个重要作用:
-
确保最后的ACK能够到达:如果最后的ACK丢失,被动关闭方会重传FIN包,主动关闭方可以再次发送ACK。如果没有TIME_WAIT状态,主动关闭方可能已经关闭连接,无法响应重传的FIN包,导致被动关闭方无法正常关闭连接。
-
防止历史连接干扰:如果没有TIME_WAIT状态,可能会出现这样的情况:旧连接的延迟数据包在新连接建立后到达,被误认为是新连接的数据包。TIME_WAIT状态确保了旧连接的所有数据包都已经从网络中消失,不会干扰新的连接。
资源的正确释放
四次挥手确保了连接资源的正确释放,避免了资源泄漏:
- 第二次挥手后,被动关闭方知道主动关闭方不再发送数据,可以释放接收缓冲区
- 第四次挥手后,被动关闭方完全关闭连接,释放所有资源
- TIME_WAIT状态结束后,主动关闭方也完全关闭连接,释放所有资源
这种设计确保了连接资源的有序释放,避免了资源泄漏和不必要的资源占用。
2.4.5 四次挥手的常见问题与优化
TIME_WAIT状态过多
在高并发服务器中,可能会出现大量的TIME_WAIT状态的连接,占用系统资源。解决方法包括:
- 启用TIME_WAIT复用:通过设置
net.ipv4.tcp_tw_reuse=1
,允许新连接复用TIME_WAIT状态的端口 - 减少TIME_WAIT超时时间:通过设置
net.ipv4.tcp_fin_timeout
参数(注意,这不会改变2MSL的时间,但会影响FIN_WAIT2状态的超时时间) - 使用长连接:减少连接的建立和关闭次数,避免产生大量TIME_WAIT状态
- 使用SO_LINGER选项:在特定场景下,可以通过设置SO_LINGER选项为0,使
close()
函数立即发送RST包而不是FIN包,跳过四次挥手过程(注意,这可能导致数据丢失)
FIN_WAIT2状态卡住
如果主动关闭方在发送FIN包并收到ACK后,由于某种原因(如被动关闭方崩溃)没有收到被动关闭方的FIN包,就会一直停留在FIN_WAIT2状态。解决方法包括:
- 设置FIN_WAIT2超时时间:通过设置
net.ipv4.tcp_fin_timeout
参数,控制FIN_WAIT2状态的最长时间 - 启用TCP保活机制:通过设置
SO_KEEPALIVE
选项,定期检测连接是否仍然有效 - 实现应用层心跳:在应用层实现心跳机制,及时检测对方是否仍然在线
CLOSE_WAIT状态过多
如果被动关闭方收到FIN包后,应用程序没有及时调用close()
函数,就会一直停留在CLOSE_WAIT状态。这通常是应用程序的问题,解决方法包括:
- 优化应用程序:确保应用程序能够及时关闭不再使用的连接
- 实现连接监控:监控CLOSE_WAIT状态的连接数量,及时发现问题
- 设置应用层超时:在应用层设置超时机制,确保连接不会无限期地保持打开状态
连接重置(RST)
在某些情况下,TCP连接可能会被重置,而不是通过四次挥手正常关闭。常见的原因包括:
- 访问已关闭的连接:尝试向已关闭的连接发送数据
- 连接超时:连接长时间没有活动,被一方超时关闭
- 异常关闭:应用程序异常退出,操作系统发送RST包关闭连接
- 使用SO_LINGER选项:设置SO_LINGER选项为0,使
close()
函数发送RST包
连接重置会导致缓冲区中的数据丢失,应该尽量避免。正常情况下,应该通过四次挥手正常关闭连接,确保数据的完整传输。
TCP四次挥手是TCP连接生命周期的最后阶段,它通过精心设计的交互过程,确保了连接的优雅关闭。理解四次挥手的原理和实现,对于网络编程、协议优化和故障排除都有重要意义。
2.5 TCP状态转换图
TCP状态转换图是理解TCP连接生命周期的重要工具,它描述了TCP连接在不同事件下的状态变化。TCP状态转换图不仅是理论上的概念模型,也是TCP协议实现的基础。
2.5.1 TCP状态概述
TCP连接在其生命周期中会经历多个状态,每个状态都有其特定的含义和行为。以下是TCP的11个标准状态:
- CLOSED:表示没有连接,这是初始状态和最终状态。
- LISTEN:服务器等待来自客户端的连接请求。
- SYN_SENT:客户端已发送SYN包,等待服务器的SYN+ACK包。
- SYN_RCVD:服务器已收到SYN包并发送SYN+ACK包,等待客户端的ACK包。
- ESTABLISHED:连接已建立,数据可以双向传输。
- FIN_WAIT_1:主动关闭方已发送FIN包,等待对方的ACK包。
- FIN_WAIT_2:主动关闭方已收到对方的ACK包,等待对方的FIN包。
- CLOSE_WAIT:被动关闭方已收到对方的FIN包并发送ACK包,等待应用程序关闭连接。
- LAST_ACK:被动关闭方已发送FIN包,等待对方的ACK包。
- TIME_WAIT:主动关闭方已收到对方的FIN包并发送ACK包,等待2MSL时间后关闭连接。
- CLOSING:双方同时关闭连接时的特殊状态,表示已发送FIN包并收到对方的FIN包,但尚未收到对方对自己FIN包的ACK。
除了这些标准状态外,还有一些特殊状态或子状态,如:
- NEW_SYN_RECV:在Linux实现中,当使用SYN Cookie时,服务器收到SYN包后不会创建完整的连接结构,而是进入这个特殊状态。
- TCP_NEW_SYN_RECV:在较新的Linux内核中,这个状态替代了NEW_SYN_RECV。
- CLOSE_WAIT_2:在某些实现中,CLOSE_WAIT状态可能有两个子状态,表示不同的处理阶段。
2.5.2 TCP状态转换图
TCP状态转换图描述了TCP连接在不同事件下的状态变化。以下是一个简化的TCP状态转换图:
+---------+| CLOSED |+---------+|passive open | active open------------- | -------------create TCB | create TCB------------- | -------------| | |V V V+---------+ +---------+| LISTEN | | SYN_SENT|+---------+ +---------+| |rcv SYN | | rcv SYN+ACK--------- | | -----------send SYN+ACK| | send ACK--------- | | -----------V V+---------+ +---------+| SYN_RCVD| |ESTABLISHED|+---------+ +---------+| |rcv ACK | | close-------- | | -------| | send FIN| | -------V V+---------+ +---------+|ESTABLISHED| |FIN_WAIT_1|+---------+ +---------+| |close | | rcv ACK------- | | -------send FIN | |------- | |V V+---------+ +---------+|CLOSE_WAIT| |FIN_WAIT_2|+---------+ +---------+| |close | | rcv FIN------- | | -------send FIN | | send ACK------- | | -------V V+---------+ +---------+| LAST_ACK| | TIME_WAIT|+---------+ +---------+| |rcv ACK | | 2MSL timeout-------- | | ------------| |V V+---------+ +---------+| CLOSED | | CLOSED |+---------+ +---------+
这个图展示了TCP连接的主要状态转换路径,包括连接的建立、数据传输和连接的关闭。实际的TCP实现可能更复杂,包含更多的状态和转换路径。
2.5.3 TCP状态在Linux内核中的实现
在Linux内核中,TCP状态是通过枚举类型定义的:
// file: include/net/tcp_states.h
enum {TCP_ESTABLISHED = 1,TCP_SYN_SENT,TCP_SYN_RECV,TCP_FIN_WAIT1,TCP_FIN_WAIT2,TCP_TIME_WAIT,TCP_CLOSE,TCP_CLOSE_WAIT,TCP_LAST_ACK,TCP_LISTEN,TCP_CLOSING,TCP_NEW_SYN_RECV,TCP_MAX_STATES /* 最大状态数 */
};
TCP状态的转换是通过tcp_set_state
函数实现的:
// 简化版的tcp_set_state函数
void tcp_set_state(struct sock *sk, int state)
{int oldstate = sk->sk_state;// 更新状态sk->sk_state = state;// 处理状态变化的副作用if (state == TCP_ESTABLISHED) {// 连接建立,可能需要启动保活定时器if (oldstate != TCP_ESTABLISHED)tcp_init_xmit_timers(sk);} else if (state == TCP_CLOSE) {// 连接关闭,需要清理资源if (oldstate == TCP_CLOSE_WAIT || oldstate == TCP_ESTABLISHED)tcp_send_fin(sk);} else if (state == TCP_CLOSE_WAIT) {// 进入CLOSE_WAIT状态,需要通知应用程序sk->sk_shutdown |= RCV_SHUTDOWN;sock_set_flag(sk, SOCK_DONE);}// 更新统计信息if (state == TCP_CLOSE)inet_csk_destroy_sock(sk);
}
TCP状态的转换是由各种事件触发的,如收到特定类型的TCP段、应用程序调用特定的函数等。以下是一些关键的状态转换函数:
连接建立阶段的状态转换
// 客户端调用connect()函数,进入SYN_SENT状态
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{// ...tcp_set_state(sk, TCP_SYN_SENT);// ...
}// 服务器收到SYN包,进入SYN_RCVD状态
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{// ...if (sk->sk_state == TCP_LISTEN) {// ...tcp_set_state(nsk, TCP_SYN_RECV);// ...}// ...
}// 客户端收到SYN+ACK包,进入ESTABLISHED状态
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...tcp_set_state(sk, TCP_ESTABLISHED);// ...
}// 服务器收到ACK包,进入ESTABLISHED状态
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...if (sk->sk_state == TCP_SYN_RECV) {// ...tcp_set_state(sk, TCP_ESTABLISHED);// ...}// ...
}
连接关闭阶段的状态转换
// 主动关闭方调用close()函数,进入FIN_WAIT_1状态
int tcp_close_state(struct sock *sk)
{// ...switch (sk->sk_state) {case TCP_ESTABLISHED:// ...tcp_set_state(sk, TCP_FIN_WAIT1);break;// ...}// ...
}// 被动关闭方收到FIN包,进入CLOSE_WAIT状态
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...if (sk->sk_state == TCP_ESTABLISHED) {if (th->fin) {// ...tcp_set_state(sk, TCP_CLOSE_WAIT);// ...}}// ...
}// 主动关闭方收到ACK包,进入FIN_WAIT_2状态
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...if (sk->sk_state == TCP_FIN_WAIT1) {if (th->ack) {// ...tcp_set_state(sk, TCP_FIN_WAIT2);// ...}}// ...
}// 被动关闭方调用close()函数,进入LAST_ACK状态
int tcp_close_state(struct sock *sk)
{// ...switch (sk->sk_state) {case TCP_CLOSE_WAIT:// ...tcp_set_state(sk, TCP_LAST_ACK);break;// ...}// ...
}// 主动关闭方收到FIN包,进入TIME_WAIT状态
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...if (sk->sk_state == TCP_FIN_WAIT2) {if (th->fin) {// ...tcp_time_wait(sk, TCP_TIME_WAIT, 0);// ...}}// ...
}// 被动关闭方收到ACK包,进入CLOSED状态
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...if (sk->sk_state == TCP_LAST_ACK) {if (th->ack) {// ...tcp_set_state(sk, TCP_CLOSE);// ...}}// ...
}
2.5.4 TCP状态的实际应用
理解TCP状态转换图对于网络编程、协议分析和故障排除都非常重要。以下是一些实际应用场景:
网络编程
在使用套接字API进行网络编程时,了解TCP状态转换有助于理解和处理各种网络事件:
- 连接建立:理解
connect()
、listen()
和accept()
函数如何影响TCP状态 - 数据传输:理解ESTABLISHED状态下的数据传输行为
- 连接关闭:理解
close()
和shutdown()
函数如何影响TCP状态,以及如何处理半关闭状态 - 错误处理:理解各种错误条件下的TCP状态变化,如连接重置、超时等
协议分析
使用netstat
、ss
等工具或Wireshark等网络分析工具时,了解TCP状态转换有助于分析网络通信过程:
- 连接状态监控:通过
netstat -ant
或ss -ant
命令查看当前系统中各TCP连接的状态 - 连接建立分析:通过Wireshark捕获TCP三次握手过程,分析SYN、SYN+ACK和ACK包的交换
- 连接关闭分析:通过Wireshark捕获TCP四次挥手过程,分析FIN和ACK包的交换
- 异常状态分析:识别和分析异常的TCP状态,如大量的TIME_WAIT或CLOSE_WAIT状态
故障排除
在排查网络问题时,了解TCP状态转换有助于定位和解决问题:
- 连接建立失败:分析SYN_SENT或SYN_RCVD状态的连接,查找连接建立失败的原因
- 连接关闭异常:分析FIN_WAIT2、CLOSE_WAIT或TIME_WAIT状态的连接,查找连接关闭异常的原因
- 资源泄漏:识别和处理长时间处于非CLOSED状态的连接,防止资源泄漏
- 性能优化:分析TCP状态分布,优化网络配置和应用程序行为
2.5.5 TCP状态的常见问题与优化
大量TIME_WAIT状态
在高并发服务器中,可能会出现大量的TIME_WAIT状态的连接,占用系统资源。解决方法包括:
- 启用TIME_WAIT复用:通过设置
net.ipv4.tcp_tw_reuse=1
,允许新连接复用TIME_WAIT状态的端口 - 启用TIME_WAIT回收:通过设置
net.ipv4.tcp_tw_recycle=1
,加速TIME_WAIT状态的回收(注意,这个选项在Linux 4.12后被移除,因为它可能导致NAT环境下的连接问题) - 减少TIME_WAIT超时时间:通过设置
net.ipv4.tcp_fin_timeout
参数(注意,这不会改变2MSL的时间,但会影响FIN_WAIT2状态的超时时间) - 使用长连接:减少连接的建立和关闭次数,避免产生大量TIME_WAIT状态
- 使用连接池:复用已建立的连接,减少连接的建立和关闭次数
大量CLOSE_WAIT状态
大量CLOSE_WAIT状态通常表示应用程序没有正确关闭连接。解决方法包括:
- 优化应用程序:确保应用程序能够及时关闭不再使用的连接
- 实现连接监控:监控CLOSE_WAIT状态的连接数量,及时发现问题
- 设置应用层超时:在应用层设置超时机制,确保连接不会无限期地保持打开状态
- 使用
SO_LINGER
选项:在特定场景下,可以通过设置SO_LINGER
选项为0,使close()
函数立即发送RST包而不是FIN包,跳过四次挥手过程(注意,这可能导致数据丢失)
大量SYN_RCVD状态
大量SYN_RCVD状态可能表示SYN洪泛攻击或网络连接问题。解决方法包括:
- 启用SYN Cookie:通过设置
net.ipv4.tcp_syncookies=1
,在收到SYN包时不立即分配资源,防止SYN洪泛攻击 - 增加半连接队列大小:通过设置
net.ipv4.tcp_max_syn_backlog
参数,增加半连接队列的容量 - 减少SYN+ACK重传次数:通过设置
net.ipv4.tcp_synack_retries
参数,减少SYN+ACK的重传次数 - 使用防火墙或负载均衡器:过滤可疑的SYN包或限制单一来源的SYN包数量
大量FIN_WAIT2状态
大量FIN_WAIT2状态可能表示网络连接问题或应用程序行为异常。解决方法包括:
- 设置FIN_WAIT2超时时间:通过设置
net.ipv4.tcp_fin_timeout
参数,控制FIN_WAIT2状态的最长时间 - 启用TCP保活机制:通过设置
SO_KEEPALIVE
选项,定期检测连接是否仍然有效 - 实现应用层心跳:在应用层实现心跳机制,及时检测对方是否仍然在线
- 优化应用程序:确保应用程序能够正确处理连接关闭事件
TCP状态转换图是理解TCP协议行为的重要工具,它描述了TCP连接在不同事件下的状态变化。深入理解TCP状态转换图对于网络编程、协议分析和故障排除都有重要意义。
相关文章:
计算机网络-TCP协议详解
TCP协议详解 2. TCP协议详解2.1 TCP协议概述2.1.1 TCP的历史背景2.1.2 TCP的设计目标2.1.3 TCP的基本特性2.1.4 TCP与其他传输协议的比较2.1.5 TCP的应用场景 2.2 TCP头部结构2.2.1 TCP头部格式2.2.2 TCP头部字段详解源端口号和目的端口号(各16位)序列号…...
[蓝桥杯 2023 省 A] 平方差
P9231 [蓝桥杯 2023 省 A] 平方差 题目描述 给定 L , R L,R L,R,问 L ≤ x ≤ R L \leq x \leq R L≤x≤R 中有多少个数 x x x 满足存在整数 y , z y,z y,z 使得 x y 2 − z 2 xy^2-z^2 xy2−z2。 输入格式 输入一行包含两个整数 L , R L,R L,Rÿ…...
隐私通信新时代:磐石云AXB平台如何重塑企业安全防线?
在数据泄露频发的当下,企业如何守护用户隐私?磐石云AXB隐私号平台以四大技术革新,构建通信安全堡垒。 技术突破: 动态号码隔离,隐私0泄露 AXB模式下,A与B的真实号码全程隐藏,仅通过虚拟号X中转…...
什么是八步工作法?
八步工作法,顾名思义,就是把一项工作拆分成八个步骤来完成。它的核心目的是让工作变得更有条理,更高效,避免忙而无序,做到事事有着落,件件有结果。这个方法在很多企业和单位中都有应用,尤其适合…...
日事清团队协作软件:智能制定计划,实时追踪任务进展,文件共享无缝协作,知识库一键沉淀成果
我们总是有微细目标,日事清可以帮助团队轻松共同制定计划、同步工作进展、共享工作资料、沉淀工作成果。 日事清具体如何帮助团队?你可以先了解以下这些基本功能模块。 从【计划】开始 在日事清中,【计划】是协同办公的开始,邀请…...
全球变暖(蓝桥杯 2018 年第九届省赛)
题目描述 你有一张某海域 NN 像素的照片,. 表示海洋、 # 表示陆地,如下所示: ....... .##.... .##.... ....##. ..####. ...###. .......其中 "上下左右" 四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。 由…...
国际物流怎么找客户?选择适合自己的企业拓客平台
在国际物流行业,获客一直是企业发展的核心难题。无论是跨境电商、传统外贸,还是国际货代,找到精准的客户资源并高效转化,是决定企业能否抢占市场蓝海的关键。今天,我们就来聊聊如何选择一个真正适合的国际物流拓客平台…...
驱动-内核空间和用户空间数据交换
内核空间与用户控件数据交换 前面了解的字符设备中对 file_operations 结构体的进行了填充, 该 结构体的每一个成员都对应着一个系统调用, 例如 read、 write 等, 在字符设备相关的文章中有实验过对 调用函数进行了标志打印, 并没…...
智膳优选 | AI赋能的智慧食堂管理专家 —— 基于飞书多维表格和扣子(Coze)的智能解决方案
智膳优选 | AI赋能的智慧食堂管理专家 基于飞书多维表格和扣子(Coze)的智能解决方案 数据驱动餐饮管理,让每一餐都是营养与经济的完美平衡! “智膳优选”通过整合飞书与Coze,将数据智能引入校园餐饮管理࿰…...
FCOS目标检测
一、模型框架 FCOS采用的网络架构和RetinaNet一样,都是采用FPN架构,如图2所示,每个特征图后是检测器,检测器包含3个分支:classification,regression和center-ness。 对于特征图Fi∈RHWC,其相对…...
Linux中动态加载两个同名so(dlopen动态链接库)
// 当前路径下 ./test1.c int Func1(int a, int b) { return ab; } //编译生成so gcc -fPIC -shared -o libTest.so test1.c // 当前路径的test2文件夹中 ./test2/test2.c int Func1(int a, int b) { return a-b; } //编译生成同名so gcc -fPIC -shared -o …...
直播电商革命:东南亚市场的“人货场”重构方程式
一、人设经济3.0:从流量收割到情感基建 东南亚直播战场正经历从"叫卖式促销"到"沉浸式信任"的质变,新加坡市场成为最佳观察样本: 数据印证趋势:Shopee直播用户日均停留28分钟,超短视频平台&#…...
【CF】Day30——Codeforces Round 824 (Div. 2) C + Codeforces Round 825 (Div. 2) BC1
C. Phase Shift 题目: 思路: 好题,值得多看 这题我们看题目就能想到一个很显然的做法,那就是贪心地把每一个字母换成最前面的没使用过的字母 但是这样直接写是有问题的,因为题目说了最后要让所有的字母成一个换&…...
STM32 模块化开发指南 · 第 2 篇 如何编写高复用的外设驱动模块(以 UART 为例)
本文是《STM32 模块化开发实战指南》的第 2 篇,聚焦于“串口驱动模块的设计与封装”。我们将从一个最基础的裸机 UART 初始化开始,逐步实现:中断支持、环形缓冲收发、模块接口抽象与测试策略,构建一个可移植、可扩展、可复用的 UART 驱动模块。 一、模块化 UART 的设计目标…...
SSRF打靶总结
文章目录 一. PortSwigger1、本地服务器的基本SSRF2、基本的目标不是漏洞机3、Referer标头的外带SSRF4、简单黑名单的SSRF黑名单绕过思路: 5、重定向的SSRF6. 简单的白名单SSRF白名单绕过思路: 二、BWAPP1. SSRF 文件包含漏洞 | 内网探测2. XXE -> S…...
第五章:5.1 ESP32物联网应用 - MQTT协议深度教程
一、MQTT协议简介 1.1 发布/订阅模式 MQTT(Message Queuing Telemetry Transport)是一种轻量级物联网通信协议,采用发布/订阅模式: 发布者(Publisher):发送消息到指定主题(如&…...
c++知识点
高级模板技术45: 模板元编程:这是一种在编译期进行计算和代码生成的技术。通过模板的递归展开、特化等操作,可以实现一些复杂的功能,例如编译期的计算、类型安全的容器等。例如,使用模板元编程可以实现一个编译期计算斐…...
【ChCore Lab 01】Bomb Lab 拆炸弹实验(ARM汇编逆向工程)
文章目录 1. 前言2. 实验代码版本问题3. 关于使用问题4. 宏观分析5. read_line 函数介绍6. phase_0 函数6.1. read_int 函数6.2. 回到 phase_0 函数继续分析6.3. 验证结果 7. phase_1 函数7.2. 验证结果 8. phase_2 函数8.1. read_8_numbers 函数8.2. 回到 phase_2 函数继续分析…...
QStackedWidget讲解
简介 QStackedWidget 类在一次仅显示1个窗口的地方提供一个窗口栈。 头文件:#include qmake:QT widgets 基类:QFrame 属性 count : const int currentIndex : int 公有槽函数 void setCurrentIndex(int index) void setCurrentWidget(QWidget *widget)信号 void current…...
宝马集团加速 ERP 转型和上云之旅
宝马集团(BMW Group)作为全球领先的豪华汽车和摩托车制造商,致力于构建更加智能、绿色、人性化的出行体验。为了支持其全球化、数字化业务战略,宝马集团正在进行大规模的 IT 体系升级和 ERP 云转型。该项目以“RISE with SAP S/4H…...
AutoEval:现实世界中通才机器人操作策略的自主评估
25年3月来自 UC Berkeley 和 Nvidia 的论文“AutoEval: Autonomous Evaluation of Generalist Robot Manipulation Policies in the Real World”。 可规模化且可复现的策略评估一直是机器人学习领域长期存在的挑战。评估对于评估进展和构建更优策略至关重要,但在现…...
ARM Cortex M内存屏障指令__dsb( )和__isb( )
ARM Cortex-M 系列处理器中的 __dsb() 和 __isb() 是内存屏障指令,用于确保内存操作的顺序性和可见性,尤其在涉及外设、多核/多线程、自修改代码或关键系统配置时至关重要。 一,详细说明和典型应用场景 1. __dsb()(Data Synchron…...
deepseek热度已过?
DeepSeek的热度并没有消退,以下是具体表现: 用户使用量和下载量方面 • 日活跃用户量增长:DeepSeek已经成为目前最快突破3000万日活跃用户量的应用程序。 • 应用商店下载量:1月26日,DeepSeek最新推出的AI聊天机器人…...
使用 Datadog 和 Slack Alerts 监控 minikube
为什么要监控 minikube 集群?这是一个不错的练习,可以让你了解 DataDog 的设置过程并探索 K8s 指标产品。 本文将分享我的以下经验: 设置最新的 minikube部署示例应用程序创建 DataDog(试用)帐户使用 Helm 安装 Data…...
深入 Redis 持久化:从原理到企业级应用的全景图
🧠 什么是 Redis 持久化?为什么需要? Redis 是内存型数据库,默认所有数据都存在内存中,一旦断电,数据就会消失。为了避免重要数据丢失,Redis 提供了持久化机制,用于将内存中的数据保…...
NET模式下如何配置虚拟机的IP地址为静态的
1.查看网关: 2.找到虚拟机的网络配置文件 cd ./etc/sysconfig/network-scripts/ vim ifcfg-ens33 3.修改配置 BROWSER_ONLY"no" IPADDR192.168.122.120 NETMASK255.255.255.0 GATEWAY192.168.122.2 DNS18.8.8.8 4.重启网路服务 sudo systemctl rest…...
VMWare Workstation Pro17.6最新版虚拟机详细安装教程(附安装包教程)
目录 前言 一、VMWare虚拟机下载 二、VMWare虚拟机安装 三、运行虚拟机 前言 VMware 是全球领先的虚拟化技术与云计算解决方案提供商,通过软件模拟计算机硬件环境,允许用户在一台物理设备上运行多个独立的虚拟操作系统或应用。其核心技术可提升硬件…...
磐石云智能语音客服系统——技术革新引领服务新体验
在人工智能技术飞速发展的今天,企业对于智能化客户服务的需求日益增长。磐石云智能语音客服系统凭借其前沿技术架构与深度场景适配能力,正在重新定义人机交互的边界。本文将深入解析该系统如何通过技术创新实现服务效率与体验的双重突破。 一、意图识别…...
什么是iPaaS?
在当今数字化时代,企业面临着日益复杂的IT环境和不断增长的业务需求。随着云计算、微服务、物联网等技术的快速发展,企业需要更加高效、灵活且安全的方式来进行数据集成和应用集成。集成平台即服务(iPaaS)应运而生,成为…...
Vue3 中 Pinia 持久化的全面解析和最佳实践
Vue3 中 Pinia 持久化的全面解析 一、Pinia 简介 Pinia 是 Vue 的新一代状态管理库,它提供了简洁的 API,支持 Composition API,并且拥有良好的代码拆分和热更新能力。相比于 Vuex,Pinia 的代码结构更加扁平,易于理…...
蓝桥杯最后一天警告!!!
1.万能头文件 #include <bits/stdc.h> 2.一道题实在一点都不会,直接碰运气骗分 #include <bits/stdc.h> using namespace std;int main() {srand(time(0));printf("%d",rand()%101);//生成一个1到10之间的随机整数,并输出print…...
el-time-picker标签的使用
需求: 实现培训日期,用户可以选择某一天的日期,这个比较简单 <el-form-item label"培训日期" prop"startTime"><el-date-picker clearablev-model"form.startTime"type"date"placeholder…...
Mysql--基础知识点--85.1--Innodb自适应哈希索引
1. 自适应哈希索引的用途 InnoDB 的自适应哈希索引(Adaptive Hash Index, AHI)是 MySQL 数据库引擎中一项智能优化查询性能的功能。其核心作用如下: 加速等值查询 哈希索引通过哈希函数将键映射到固定位置,实现 O(1) 时间复杂度的…...
Matlab 考虑电机激励力的整车垂向七自由度的被动悬架和LQR控制
1、内容简介 Matlab 200-考虑电机激励力的整车垂向七自由度的被动悬架和LQR控制 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...
统一功能处理
SpringBoot统一功能处理 本节目表 1.掌握拦截器的使用,及其原理 2.学习统一数据返回格式和统一异常处理的操作 3.了解一些Spirng的源码 文章目录 SpringBoot统一功能处理前言一、拦截器是什么?二、使用步骤1.定义拦截器2.注册配置拦截器 三,适…...
LibreOffice Writer使用01去除单词拼写判断的红色下划线
这个软件还是非常有特色的,因为大家需要office的全部功能,常常忽略了这个软件的使用体验。 csdn不是特别稳定,linux也没有什么比较好的md编辑器,所以我选择这个软件来记录我的临时博客,原因无他,它可以保存…...
JAVA基础 - 高效管理线程隔离数据结构ThreadLocalMap
欢迎光临小站:致橡树 ThreadLocalMap 是 ThreadLocal 的核心底层数据结构,负责在每个线程中存储与 ThreadLocal 实例绑定的数据。它的设计目标是高效管理线程隔离数据,同时尽量减少内存泄漏风险。以下是其核心实现细节。 数据结构与设计目标…...
每日一题(小白)暴力娱乐篇25
由题意直知,要求将给定的字符串转为符合格式的字符串。首先我们来思考一下例如02/02/02这样的数字日月肯定不用进行修改,修改后需要加上对应的年份的前两位,题目要求1960年1月1日~2059年12年12月31日,(对1960年至2059年…...
地表水-地下水耦合建模全景解析暨SWAT-MODFLOW地表与地下协同模拟及多情景专题应用
第一、模型原理与层次结构 1.1流域水循环与SWAT模型 1.2 地下水模拟与MODFLOW模型 1.3 SWAT-MODFLOW地表-地下耦合模型 1.4 QSWATMOD 插件与功能介绍 1.5 模型实现所需软件平台 第二、QGIS软件 2.1 QGIS平台 2.2 QGIS安装 2.3 QGIS界面认识 2.4 QGIS常见数据格式 2.…...
在线论坛系统
在线论坛 项目介绍项目使用技术介绍前端技术栈后端技术栈 项目运行步骤说明后端运行步骤前端运行步骤 项目使用说明用户端功能管理员端功能 部分功能说明运行截图用户端管理员端 系统源码 项目介绍 这是一个基于现代技术栈开发的在线论坛系统,提供用户交流、内容分享…...
webrtc pacer模块(一) 平滑处理的实现
Pacer起到平滑码率的作用,使发送到网络上的码率稳定。如下的这张创建Pacer的流程图,其中PacerSender就是Pacer,其中PacerSender就是Pacer。这篇文章介绍它的核心子类PacingController及Periodic模式下平滑处理的基本流程。平滑处理流程中还有…...
新能源车「大三电」与「小三电」
一、大三电系统 动力电池 作为整车的能量核心,目前主流类型包括三元锂电池(NCM/NCA)和磷酸铁锂电池(LFP),前者能量密度高,后者安全性和成本优势显著。动力电池通过模组串联形成电池包…...
xlinx GT传输器学习
xlinx有2路refclk会输入到gtbank。此2路bank输入时钟是差分输入,经过IBUFDS_GTE2源语转换为单端。如下图。 从fpga外输入高速差分时钟。 差分时钟转换为单端。 单端时钟输入到gt_common模块 下图为gt内部结构图...
C++笔记之父类引用是否可以访问到子类特有的属性?
C++笔记之父类引用是否可以访问到子类特有的属性? code review! 参考笔记 1.C++笔记之在基类和派生类之间进行类型转换的所有方法 文章目录 C++笔记之父类引用是否可以访问到子类特有的属性?1.主要原因2.示例代码3.说明4.如何访问子类特有的属性5.注意事项6.总结在 C++ 中,…...
基于SSM框架的房屋租赁小程序开发与实现
概述 一个基于SSM框架开发的微信小程序房屋租赁管理系统,该项目实现了用户管理、中介管理、房源信息管理等核心功能。 主要内容 一、管理员模块功能实现 用户管理 管理员可对通过微信小程序注册的用户信息进行修改和删除操作,确保用户数据的准…...
大模型评估论文粗读“AGIEval: A Human-Centric Benchmark for Evaluating Foundation Models“
Zhong W, Cui R, Guo Y, et al. Agieval: A human-centric benchmark for evaluating foundation models[J]. arXiv preprint arXiv:2304.06364, 2023. 摘要翻译 评估大模型在处理人类层级的任务时的通用能力,是AGI发展和应用中至关重要的一环。传统的衡量标准通常依…...
程序化广告行业(76/89):行业融资全景剖析与代码应用拓展
程序化广告行业(76/89):行业融资全景剖析与代码应用拓展 大家好!在之前的文章里,咱们一起了解了程序化广告行业的发展趋势以及PC端和移动端投放的差异。今天,咱们接着深入学习,这次聚焦在程序化…...
网工基础 | 常见英文术语注解
原创:厦门微思网络 以下是一些网工专业常见的英文术语注解: IP Address(Internet Protocol Address) 互联网协议地址,是分配给连接到互联网的设备的唯一标识符,用于在网络中定位和通信。例如,“…...
【FPGA开发技巧】Modelsim仿真中,显示状态机的名称,而非编码数字
示例 被仿真的文件名为:cmd_handle.v,其中有r_st_current和r_st_next两个状态机变量。 该模块在tb文件中,被例化的名称为cmd_handle_u0 按照如下格式写:cmd_handle_u0.r_st_current 示例tb文件: timescale 1ns / 1…...
二分答案----
二分答案 - 题目详情 - HydroOJ 问题描述 给定一个由n个数构成的序列a,你可以进行k次操作,每次操作可以选择一个数字,将其1,问k次操作以后,希望序列里面的最小值最大。问这个值是多少。 输入格式 第一行输入两个正…...