【Linux高级全栈开发】2.2.1 Linux服务器百万并发实现2.2.2 Posix API与网络协议栈

news2025/5/22 6:31:21

【Linux高级全栈开发】2.2.1 Linux服务器百万并发实现2.2.2 Posix API与网络协议栈

高性能网络学习目录

基础内容(两周完成):
  • 2.1网络编程

    • 2.1.1多路复用select/poll/epoll
    • 2.1.2事件驱动reactor
    • 2.1.3http服务器的实现
  • 2.2网络原理

    • 百万并发
    • PosixAPI
    • QUIC
  • 2.3协程库

    • NtyCo的实现
  • 2.4dpdk

    • 用户态协议栈的实现
  • 2.5高性能异步io机制

项目内容(两周完成):
  • 9.1 KV存储项目
  • 9.2 RPC项目
  • 9.3 DPDK项目

2.2.1 Linux服务器百万并发实现

1 同步处理与异步处理的数据差异

同步处理

  • 服务器线程在读取或写入数据时会阻塞,直到操作完成
  • 数据处理是顺序的,一个连接处理完成后才会处理下一个
  • 示例:使用read()/write()系统调用时线程会等待 I/O 完成

异步处理

  • 服务器线程不会阻塞,I/O 操作由内核或线程池异步处理
  • 通过回调、事件通知机制获取 I/O 完成状态
  • 数据处理是非阻塞的,线程可以同时处理多个连接
  • 示例:使用epoll/kqueue事件通知或异步 I/O 接口(如aio_*系列函数)

数据差异影响

  • 同步模式下需要为每个连接分配独立线程 / 进程,内存消耗大(约 1MB / 线程)
  • 异步模式下单个线程可以管理数万连接,内存效率高(约 1KB / 连接)
2 网络io线程池异步处理

实现方式

  1. 主 Reactor 线程:负责 accept 新连接,将 socket 分配给 Worker 线程
  2. Worker 线程池:每个线程维护一个epoll实例,处理读写事件
  3. 任务队列:线程间通过队列传递任务(如新建连接、关闭连接)

代码示例(伪代码):

// 主Reactor线程
while (1) {
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < nfds; i++) {
        if (events[i].data.fd == listen_fd) {
            // 接受新连接
            int conn_fd = accept(listen_fd, ...);
            // 选择一个Worker线程
            worker = get_least_busy_worker();
            // 将conn_fd加入该Worker的任务队列
            enqueue(worker->task_queue, conn_fd);
            // 唤醒Worker线程
            wakeup(worker);
        }
    }
}

// Worker线程
while (1) {
    // 从任务队列获取新连接
    int conn_fd = dequeue(task_queue);
    // 将conn_fd加入epoll监听
    epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);
    
    // 处理epoll事件
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < nfds; i++) {
        if (events[i].events & EPOLLIN) {
            // 读取数据并处理
            read_and_process(events[i].data.fd);
        }
    }
}

优势

  • 避免单线程处理瓶颈
  • 充分利用多核 CPU 资源
  • 隔离 I/O 处理和业务逻辑
3 ulimit的fd的百万级别支持

限制原因

  • Linux 默认每个进程最多打开 1024 个文件描述符(FD)
  • 百万并发需要修改多个系统限制

修改步骤

  1. 临时修改(当前会话有效)

    ulimit -n 1048576  # 设置最大FD数量
    
  2. 永久修改(所有用户)
    编辑/etc/security/limits.conf

    * soft nofile 1048576
    * hard nofile 1048576
    root soft nofile 1048576
    root hard nofile 1048576
    
  3. 系统级限制
    编辑/etc/sysctl.conf

    fs.file-max = 1048576  # 系统最大文件数
    

    执行sysctl -p生效

验证方法

ulimit -n  # 查看当前FD限制
cat /proc/sys/fs/file-max  # 查看系统文件限制
4 sysctl.conf的rmem与wmem的调优

TCP 内存参数说明

  • net.core.rmem_max:单个 socket 接收缓冲区最大字节数
  • net.core.wmem_max:单个 socket 发送缓冲区最大字节数
  • net.ipv4.tcp_rmem:TCP 接收缓冲区大小范围(min, default, max)
  • net.ipv4.tcp_wmem:TCP 发送缓冲区大小范围(min, default, max)

推荐配置

net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_mem = 786432 1048576 1572864

参数作用

  • 增大缓冲区可以提高吞吐量,但会增加内存压力
  • 动态调整范围允许系统根据负载自动优化内存使用
5 conntrack的原理分析(在ubuntu16.04中需要修改)

功能

  • 跟踪 IP 数据包的连接状态(如 TCP 的 SYN_SENT、ESTABLISHED 等)
  • 为 NAT(网络地址转换)提供状态保持功能
  • 用于防火墙规则匹配(如状态防火墙)

问题场景

  • 高并发下 conntrack 表会耗尽内存
  • 每个连接在表中占用约 300-600 字节内存
  • 默认限制较低(如 65536 条记录)

优化方法

  1. 增加表大小

    net.netfilter.nf_conntrack_max = 1048576
    net.netfilter.nf_conntrack_buckets = 262144  # 建议为max的1/4
    
  2. 缩短超时时间

    net.netfilter.nf_conntrack_tcp_timeout_established = 1200  # 默认432000秒
    
  3. 禁用不必要的跟踪

    # 对特定端口禁用conntrack
    iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK
    iptables -t raw -A PREROUTING -p tcp --dport 443 -j NOTRACK
    

验证命令

cat /proc/sys/net/netfilter/nf_conntrack_count  # 当前连接数
cat /proc/sys/net/netfilter/nf_conntrack_max    # 最大连接数
总结

实现百万并发需要综合优化:

  1. 架构层面:采用异步非阻塞 I/O 模型 + 线程池
  2. 系统限制:调整 FD 数量、内存参数、conntrack 表大小
  3. 网络栈优化:调整 TCP 缓冲区、窗口大小、超时参数
  4. 内存管理:使用内存池减少内存碎片
  5. 监控工具:使用ssnetstatconntrack等工具监控系统状态

2.2.2 Posix API与网络协议栈

1 网络基础知识复习
1.1 客户端部分
  1. socket()
    • 功能:创建一个套接字描述符(文件描述符 fd ),用于后续的网络通信。它在内核中分配一个网络 I/O 控制块(如 tcp control block ,简称 tcb ),并返回一个整数类型的文件描述符(fd ,是一个整数)。对于 TCP 或 UDP 协议,这是网络编程的起始操作。例如在 C 语言中,int sockfd = socket(AF_INET, SOCK_STREAM, 0); 表示创建一个 IPv4 的 TCP 套接字 。
  2. bind()(可选 )
    • 功能:对于服务器端,一般是必须操作,但客户端有时可省略。它将套接字与特定的 IP 地址和端口号绑定。在客户端使用时,可指定客户端自己监听的 IP 和端口,若不调用,系统会自动分配可用端口等。如 bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); ,其中 addr 是包含 IP 和端口信息的结构体。
  3. connect()(udp协议 )
    • 功能:在 TCP 协议中,用于客户端发起与服务器端的连接请求。它向服务器的 IP 地址和端口发送连接请求报文(SYN 包),并经历三次握手过程来建立可靠连接。对于 UDP ,也可使用 connect 来指定目标 IP 和端口,后续可直接用 sendrecv 收发数据,而不必每次都指定地址信息 。例如 connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); ,这里 servaddr 是服务器的地址信息。
  4. send()
    • 功能:用于向已连接的套接字发送数据。在 TCP 中,它将应用层数据复制到内核发送缓冲区,由 TCP 协议负责分段、编号、重传等操作,将数据可靠地发送到对端。在 UDP 中,它直接将数据封装成 UDP 报文发送出去,不保证可靠性 。如 send(sockfd, buffer, strlen(buffer), 0);buffer 是存放要发送数据的缓冲区。
  5. recv()
    • 功能:从已连接的套接字接收数据。在 TCP 中,它从内核接收缓冲区读取数据到应用层缓冲区。在 UDP 中,接收 UDP 报文并将数据存储到指定的缓冲区。例如 recv(sockfd, buffer, sizeof(buffer), 0); ,接收的数据存放在 buffer 中。
  6. close()
    • 功能:关闭套接字,释放与该套接字相关的资源。在 TCP 中,会发起四次挥手过程来终止连接。例如 close(sockfd); ,通知内核不再使用该套接字进行通信 。
1.2 服务器端部分
  1. socket()
    • 功能:同客户端的 socket() ,创建套接字描述符,分配网络 I/O 控制块等资源,为后续网络通信做准备 。
  2. bind()
    • 功能:将套接字绑定到特定的 IP 地址和端口号上,这是服务器端的关键步骤,使得服务器能够在指定的地址和端口上监听客户端请求。如 bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); ,让服务器监听 servaddr 对应的 IP 和端口 。
  3. listen()
    • 功能:将套接字设置为监听状态,在内核中为该套接字创建同步队列(syn_queue )和接受队列(accept_queue ),用于存放客户端的连接请求。它将套接字的状态设置为 TCP_STATUS_LISTEN ,并准备好接收客户端的连接请求 。例如 listen(listenfd, BACKLOG);BACKLOG 表示监听队列的最大长度
  4. accept()
    • 功能:从监听队列中取出一个已完成三次握手的客户端连接请求,创建一个新的套接字(与监听套接字不同 )用于与该客户端进行后续的数据通信。返回的新套接字描述符用于和对应的客户端进行数据收发等操作 。如 int connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);cliaddr 存放客户端地址信息,clilen 是其长度 。
  5. recv()
    • 功能:同客户端的 recv() ,从与客户端连接的套接字接收数据,将内核接收缓冲区的数据读取到应用层缓冲区
  6. send()
    • 功能:同客户端的 send() ,向与客户端连接的套接字发送数据,将应用层数据复制到内核发送缓冲区进行发送 。仅限于把数据从应用程序拷贝到内核中,而内核的发送时间和大小send无法决定。
  7. close()
    • 功能:同客户端的 close() ,关闭与客户端连接的套接字,释放相关资源,在 TCP 情况下发起四次挥手终止连接 。
1.3 epoll 相关函数(epoll_create ()、epoll_ctl ()、epoll_wait () )
  1. epoll_create()
    • 功能:创建一个 epoll 实例,在内核中分配一个数据结构用于管理事件,返回一个 epoll 句柄(文件描述符 ),后续通过这个句柄对 epoll 实例进行操作 。如 int epfd = epoll_create(1024); ,这里参数可指定初始大小(现在该参数意义不大 )。
  2. epoll_ctl()
    • 功能:用于对 epoll 实例进行事件的添加、修改或删除操作。它可以将套接字与感兴趣的事件(如可读、可写、异常等 )关联起来。例如 epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); ,表示向 epfd 对应的 epoll 实例中添加 sockfd 套接字的相关事件,ev 是包含事件类型等信息的结构体 。
  3. epoll_wait()
    • 功能:阻塞等待在 epoll 实例中注册的套接字上有事件发生。当有事件发生时,它会返回发生事件的套接字数量,并将发生事件的相关信息填充到用户提供的数组中。如 int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);events 是存放事件信息的数组,MAX_EVENTS 是数组大小,-1 表示一直阻塞直到有事件发生 。
1.4 fcntl()
  • 功能:用于对文件描述符(包括套接字描述符 )进行各种控制操作,如设置文件描述符为非阻塞模式(fcntl(fd, F_SETFL, O_NONBLOCK); ) ,获取或设置文件描述符的标志等 ,还可用于复制文件描述符等操作 。
2 connect, listen, accept 与三次握手

connect 是客户端发起连接请求的函数;listen 用于服务器端,将套接字设置为监听状态,准备接收客户端连接;accept 是服务器端接受客户端连接请求的函数 。三次握手是 TCP 协议中建立连接的过程,客户端发送 SYN 包,服务器回复 SYN+ACK 包,客户端再回复 ACK 包,以此确认双方都能正常收发数据。

2.1 TCP三次握手过程

请添加图片描述

  • 第一次握手:客户端发送一个带有 SYN(同步序列号)标志的 TCP 报文段,其中包含客户端初始序列号(ISN),向服务器发起连接请求 ,此时客户端进入 SYN_SENT 状态。

  • 第二次握手:服务器收到客户端的 SYN 报文后,会回复一个 SYN + ACK 报文,其中 SYN 标志表示同意建立连接,ACK 标志用于确认客户端的请求,同时包含服务器的初始序列号以及对客户端序列号的确认号(客户端序列号 + 1) ,服务器进入 SYN_RCVD 状态。

  • 第三次握手:客户端收到服务器的 SYN + ACK 报文后,再发送一个 ACK 报文给服务器,确认收到服务器的响应(确认号为服务器序列号 + 1 ),至此客户端和服务器都进入 ESTABLISHED 状态,连接建立成功。

  • 建立连接三次握手原因:主要是为了防止已失效的连接请求报文段突然又传送到服务器,产生错误。通过三次握手,客户端和服务器都能确认彼此的发送和接收能力正常,保证连接可靠建立。

请添加图片描述

  • TCP 首部长度:TCP 首部长度可变,最小为 20 字节(没有选项时),最大为 60 字节(包含 40 字节选项) 。首部长度以 4 字节为单位来表示,通过首部中的 “首部长度” 字段(4 位)来指明。
  • 字段:
    • 源端口和目的端口:各占 2 字节,标识发送端和接收端应用程序的端口号。
    • 序号:4 字节,用于标识发送字节流中的位置,保证数据按序传输。
    • 确认号:4 字节,期望收到对方下一个报文段的第一个数据字节的序号。
    • 首部长度:4 位,指出 TCP 首部的长度。
    • 标志位:6 位,包含 SYN(同步)、ACK(确认)、FIN(结束)、RST(复位)、PSH(推送)、URG(紧急)等标志。
    • 窗口:2 字节,用于流量控制,表示接收方当前允许接收的字节数。
    • 校验和:2 字节,对 TCP 首部和数据进行校验,确保数据的完整性。
    • 紧急指针:2 字节,配合 URG 标志使用,指出紧急数据的末尾在数据流中的位置 。
    • 选项:长度可变,最多 40 字节,常见选项有 MSS(最大报文段长度)、窗口扩大因子、时间戳等 。

请添加图片描述

2.2 三次握手发生在哪些函数里面
connect()
  • 功能:在 TCP 协议中,用于客户端发起与服务器端的连接请求。它向服务器的 IP 地址和端口发送连接请求报文(SYN 包),并经历三次握手过程来建立可靠连接。对于 UDP ,也可使用 connect 来指定目标 IP 和端口,后续可直接用 sendrecv 收发数据,而不必每次都指定地址信息 。例如 connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); ,这里 servaddr 是服务器的地址信息。
listen()
  • 功能:将套接字设置为监听状态,在内核中为该套接字创建一个监听队列,用于存放客户端的连接请求。它将套接字的状态设置为 TCP_STATUS_LISTEN ,并准备好接收客户端的连接请求 。例如 listen(listenfd, BACKLOG);BACKLOG 表示监听队列的最大长度
accept()
  • 功能:服务器在接收到客户端的第三次握手 ACK 报文,完成三次握手流程,建立起与客户端的连接后,处于 ESTABLISHED 状态时,调用 accept 函数从监听队列中取出一个已完成三次握手的客户端连接请求,创建一个新的套接字(与监听套接字不同 )用于与该客户端进行后续的数据通信。返回的新套接字描述符用于和对应的客户端进行数据收发等操作 。如 int connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);cliaddr 存放客户端地址信息,clilen 是其长度 。
2.3 三次握手过程中有哪些不安全性
  • SYN 洪泛攻击:攻击者向服务器发送大量带有 SYN 标志的 TCP 报文,且源 IP 地址是伪造的不可达地址。服务器收到这些 SYN 报文后,会分配资源并进入 SYN_RCVD 状态,等待第三次握手的 ACK 报文。但由于源 IP 不可达,服务器收不到 ACK,这些半连接会一直占用服务器资源,导致正常客户端的连接请求无法被处理,使服务器拒绝服务。
  • 中间人攻击:攻击者在客户端和服务器之间的通信路径上,拦截双方的通信报文。在三次握手过程中,攻击者可以截获客户端的 SYN 报文,然后伪装成客户端向服务器发送自己的 SYN 报文,同时伪装成服务器向客户端发送 SYN + ACK 报文,使客户端和服务器都误以为与对方直接建立了连接,从而攻击者可以窃取和篡改双方传输的数据。
3 listen 参数 backlog

在 TCP 服务器端调用 listen 函数时,backlog 参数用于指定处于半连接状态(SYN_RCVD)完全连接状态(ESTABLISHED)的连接队列的最大长度。它表示内核为该套接字维护的两个队列的总长度上限(未分配fd的tcb的数量)。当队列已满,再有新的连接请求到达时,客户端可能会收到 ECONNREFUSED 错误(取决于具体实现)。合理设置 backlog 有助于服务器应对不同的并发连接请求情况,避免资源耗尽。

  • 含义:backlog 是 listen 函数的一个参数,它定义了等待连接队列的最大长度。即服务器在还没调用 accept 处理连接请求时,能够缓存的最大连接请求数量。
  • 作用:合理设置 backlog 可平衡服务器处理连接的能力和客户端连接请求的流量,避免过多连接请求丢失,防止 syn 泛洪
  • 老版本:syn链接长度,中版本syn+accept总长度,新版本:accept队列长度
4 syn 泛洪的解决方案
  • 含义:syn 泛洪是一种网络攻击,攻击者短时间内发送大量 SYN 包,占用服务器的半连接队列资源,使正常客户端无法建立连接。
  • 解决方案:常见的有 SYN 缓存、SYN cookie 等。SYN 缓存通过记录半连接信息,减少内存占用;SYN cookie 则是在收到 SYN 包时,根据特定算法生成 cookie 代替传统的半连接记录,在后续 ACK 包到来时验证 cookie 合法性,过滤掉非法连接请求。
5 close 与四次挥手
四次挥手
  • 含义:close 是关闭套接字连接的函数。四次挥手是 TCP 协议中关闭连接的过程,当一方发起关闭请求(FIN 包),另一方回复 ACK 包,然后另一方也准备好关闭时发送 FIN 包,最初发起关闭的一方再回复 ACK 包,完成连接关闭。

    • 第一次挥手:客户端想要断开连接,发送一个带有 FIN(结束标志)的 TCP 报文段给服务器,请求释放连接,此时客户端进入 FIN_WAIT_1 状态。
    • 第二次挥手:服务器收到客户端的 FIN 报文后,发送一个 ACK 报文给客户端,确认收到客户端的断开请求,此时服务器进入 CLOSE_WAIT 状态,客户端收到 ACK 后进入 FIN_WAIT_2 状态。
    • 第三次挥手:服务器数据传输完毕,也准备好断开连接时,向客户端发送一个 FIN 报文,请求断开连接,此时服务器进入 LAST_ACK 状态。
    • 第四次挥手:客户端收到服务器的 FIN 报文后,发送一个 ACK 报文给服务器,确认收到服务器的断开请求,然后客户端进入 TIME_WAIT 状态,服务器收到 ACK 后进入 CLOSED 状态。经过 2MSL(最长报文段寿命)时间后,客户端也进入 CLOSED 状态,连接彻底断开。
  • 断开连接四次挥手原因:TCP 是全双工通信,当一方请求断开连接(发送 FIN)时,只是表示它自己不再发送数据,但仍可以接收数据。另一方收到 FIN 后,先回复 ACK 确认,等自己数据传输完后再发送 FIN 断开连接,所以需要四次挥手来确保双方数据都能完整传输和连接正常释放。

close流程

请添加图片描述

close 函数的调用与触发

  • 主动关闭方:当应用程序调用close(sockfd)shutdown(sockfd, SHUT_WR)(不建议)时(假设是客户端主动关闭),TCP 协议会:
    1. 停止发送数据:关闭发送缓冲区,不再允许应用程序通过该套接字发送新数据。
    2. 发送 FIN 包:TCP 协议自动向对端(服务器)发送一个 FIN 包,表示 “我已完成数据发送,请准备关闭连接”。
    3. 进入 FIN_WAIT_1 状态:等待对端的 ACK 确认。

2. 四次挥手流程中的 close 交互

第一次挥手

  • 客户端操作:调用 close(sockfd) → 发送 FIN 包 → 进入 FIN_WAIT_1
  • 含义:客户端告诉服务器 “我不再发送数据,但仍可接收”。

第二次挥手

  • 服务器操作:收到 FIN 包 → 发送 ACK 包 → 进入 CLOSE_WAIT
  • 客户端状态:收到 ACK 后 → 从 FIN_WAIT_1 进入 FIN_WAIT_2
  • 关键点:此时客户端的发送通道已关闭,但接收通道仍开放,可继续接收服务器的数据。

第三次挥手

  • 服务器操作:
    • 若服务器还有数据要发送,会先完成数据传输。
    • 数据发送完毕后,应用程序调用 close(sockfd) → 发送 FIN 包 → 进入 LAST_ACK
  • 含义:服务器告诉客户端 “我也完成数据发送,准备关闭”。

第四次挥手

  • 客户端操作:收到服务器的 FIN 包 → 发送 ACK 包 → 进入 TIME_WAIT
  • 服务器状态:收到 ACK 后 → 进入 CLOSED 状态,连接彻底关闭。
  • 客户端的 TIME_WAIT:需等待 2MSL(最大段生存期) 时间后才进入 CLOSED,目的是确保最后一个 ACK 能被服务器收到(若丢失,服务器会重发 FIN)。

TIME_WAIT 状态的意义

  • 确保连接可靠关闭:若客户端的最后一个 ACK 丢失,服务器会重发 FIN,客户端在 TIME_WAIT 状态可响应重发的 FIN。
  • 防止旧连接数据干扰:2MSL 时间足够让本次连接的所有数据包在网络中自然消失,避免新连接复用相同端口号时,旧数据包被误认为是新连接的数据。

6. 常见问题与优化

  • TIME_WAIT 过多

    :高并发短连接场景下,大量

    TIME_WAIT
    

    状态可能耗尽系统资源。可通过以下方式优化:

    • 调整内核参数net.ipv4.tcp_tw_reuse = 1(允许复用处于 TIME_WAIT 的端口)。
    • 使用长连接:减少频繁建立 / 关闭连接的开销。
  • 半关闭场景:若需要双向数据传输的精细控制(如文件传输),可使用 shutdown(SHUT_WR) 实现半关闭,而非直接 close

6 11 个状态迁移
  • 简介:TCP 连接存在多种状态,如 LISTEN(监听)、SYN_SENT(发送同步包)、SYN_RECV(收到同步包并回复 )、ESTABLISHED(已建立连接 )、FIN_WAIT_1(主动关闭方发送 FIN 包后 )、FIN_WAIT_2(收到对方 ACK 后 )、CLOSE_WAIT(被动关闭方收到 FIN 包后 )、CLOSING(双方同时发送 FIN 包时 )、LAST_ACK(被动关闭方发送 FIN 包后等待 ACK )、TIME_WAIT(主动关闭方收到对方 FIN 包并回复 ACK 后 )、CLOSED(连接关闭 ) ,这些状态之间依据连接的建立、数据传输、关闭等操作进行迁移。

请添加图片描述

状态转换图相关补充
  • CLOSED:表示初始状态,套接字没有在使用,不占用任何资源。
  • LISTEN:服务器端调用 listen 函数后进入此状态,此时套接字处于监听状态,等待客户端的连接请求 。
  • SYN_SENT:客户端调用 connect 后发送 SYN 包,进入此状态,等待服务器的 SYN + ACK 包 。
  • SYN_RCVD:服务器接收到客户端的 SYN 包后,发送 SYN + ACK 包,自身进入此状态,等待客户端的 ACK 包 。
  • ESTABLISHED:经过三次握手完成后,客户端和服务器端都进入此状态,此时可以进行数据的正常收发 。
  • FIN_WAIT_1:主动关闭连接的一方(通常是客户端 )发送 FIN 包后进入此状态,等待对方的 ACK 包 。
  • FIN_WAIT_2:收到对方 ACK 包后进入此状态,此时等待对方发送 FIN 包 。
  • TIME_WAIT:主动关闭方收到对方 FIN 包,发送 ACK 包后进入此状态,在此状态停留 2 倍的报文段最大生存时间(MSL ),用于确保网络中延迟的报文段都消失,防止新连接中出现旧连接的残留数据 。
  • CLOSE_WAIT:被动关闭连接的一方收到对方 FIN 包后,进入此状态,此时表示已经收到对方关闭连接请求,但还未完全处理完自身的工作(如数据发送等 ) 。
  • LAST_ACK:被动关闭方处理完自身工作后,发送 FIN 包,进入此状态,等待对方的 ACK 包 。
  • CLOSING:双方同时发送 FIN 包时可能出现的状态,即双方都在关闭连接的过程中 。
7 大量 close_wait 与 time_wait 的原因与解决方案
close_wait
  • 大量 close_wait 原因:通常是被动关闭连接的一方,没有及时调用 close 函数关闭连接,导致资源未释放,积累大量处于 close_wait 状态的连接。
  • 大量 close_wait 解决方案:检查代码中关闭连接的逻辑,确保在合适时机调用 close 关闭连接。
time_wait
  • time_wait 持续时间:通常为 2 倍的 MSL(Maximum Segment Lifetime,最长报文段寿命),在大多数系统中 MSL 默认值为 1 分钟,所以 TIME_WAIT 一般为 2 分钟 。

  • 原因:一是为了确保最后一个 ACK 报文能够到达服务器,如果服务器没有收到这个 ACK,会重发 FIN 报文,客户端在 TIME_WAIT 状态可以重新发送 ACK;二是防止在本连接中出现的报文段在网络中延迟,在新的连接中被误认,保证旧连接的所有报文段都从网络中消失,避免对新连接造成干扰。若短时间内有大量连接主动关闭,就会产生大量 time_wait 状态连接。

  • 解决方案:调整系统参数,如设置合适的 2MSL 时长;使用 SO_REUSEADDR 套接字选项,允许在 TIME_WAIT 状态下重用本地地址。

8 tcp keepalive 与应用层心跳包
  • tcp keepalive:是 TCP 协议提供的一种保活机制,在一段时间内如果没有数据传输,TCP 会自动发送探测报文,以检测连接是否正常。若对端无响应,会尝试多次后判定连接已失效。
  • 应用层心跳包:是在应用程序层面实现的类似机制,应用程序定时向对端发送特定的心跳报文,以确认连接状态,相比 tcp keepalive 更灵活,可自定义检测逻辑和频率。
  • 作用:二者都是为了检测连接是否存活,及时发现并处理失效连接,保障网络通信的可靠性。
9 拥塞控制与滑动窗口
  • 简介: TCP 协议为防止网络出现拥塞而采取的机制。当网络出现拥塞时,通过调整发送方的发送速率来缓解网络压力。常见算法有慢开始、拥塞避免、快重传、快恢复等。慢开始是从一个较小的拥塞窗口开始,逐渐增大;拥塞避免是在拥塞窗口达到一定阈值后,线性增加;快重传是在收到多个重复 ACK 时,快速重传丢失的报文段;快恢复是在快重传后,调整拥塞窗口大小。
慢启动
  • 原理:发送方从一个较小的拥塞窗口(cwnd ,Congestion Window)大小开始发送数据,初始值常为 1 个 MSS(Maximum Segment Size,最大报文段长度 )。每收到一个 ACK(确认报文 ),拥塞窗口大小就增加 1 个 MSS ,呈现指数增长趋势。例如,初始窗口为 1 个 MSS ,收到 1 个 ACK 后变为 2 个 MSS ,收到 2 个 ACK 后变为 4 个 MSS ,以此类推。直到拥塞窗口达到慢开始阈值(ssthresh ,Slow Start Threshold ),或者发生丢包情况,此时慢开始阶段结束。
  • 作用:在连接建立初期,以较小速率试探网络状况,避免一开始就发送大量数据导致网络拥塞,逐步探测网络的承载能力。
拥塞控制
  • 原理:当慢开始阶段的拥塞窗口达到慢开始阈值后,进入拥塞避免阶段。此阶段不再采用指数增长方式,而是每经过一个往返时间(RTT ,Round - Trip Time ),拥塞窗口大小线性增加 1 个 MSS 。若发生丢包,ssthresh 设置为当前拥塞窗口大小的一半,同时拥塞窗口大小设置为 ssthresh 加 1 个 MSS ,然后继续在拥塞避免阶段调整。
  • 作用:在网络已经有一定数据传输量基础上,更平稳地增加发送速率,防止因增长过快再次引发拥塞 。
滑动窗口
  • 简介:是 TCP 协议中用于流量控制的机制。接收方通过通告窗口大小给发送方,发送方根据这个窗口大小来控制发送数据量,窗口大小会随着接收方处理数据情况动态调整,实现发送方和接收方数据传输速率的匹配,避免接收方因来不及处理数据而丢包。
  • 窗口结构
    • 已发送且已确认:这部分数据已经成功发送,并且也收到了接收方的确认。
    • 已发送但未确认:数据已经发送出去了,不过还没有收到接收方的确认。
    • 未发送但可发送:这部分数据虽然还没有发送,但处于发送窗口允许的范围内,是可以发送的。
    • 未发送且不可发送:数据不在发送窗口范围内,暂时不可以发送。

请添加图片描述

  • 工作流程:
    1. 初始化:在 TCP 连接建立时,双方会协商初始的窗口大小。一般来说,接收方会在 SYN 包中通告自己的初始窗口大小。
    2. 数据传输:发送方依据接收方通告的窗口大小,发送相应数量的数据。每发送一个数据段,发送方都会启动一个计时器。
    3. 确认机制:接收方在收到数据后,会发送 ACK 确认报文,其中包含两个关键信息:
      • 确认号(ACK Number):表示期望接收的下一个字节的序号。
      • 窗口大小(Window Size):用于告知发送方自己当前的接收窗口大小。
    4. 窗口滑动:当发送方收到 ACK 确认后,发送窗口会向前滑动,从而允许发送新的数据。窗口滑动的距离取决于确认号的增长情况。
    5. 窗口动态调整:接收方可以根据自身的处理能力,动态地调整通告窗口的大小。例如,当接收缓冲区快满时,接收方可以减小窗口大小,甚至将窗口大小设置为 0,此时发送方需要暂停发送数据,直到收到新的窗口更新通知。
  • 窗口大小的动态变化:
    • 窗口增大:如果接收方处理数据的速度很快,能够及时清空接收缓冲区,就会增大通告窗口的大小,这样发送方就可以以更高的速率发送数据。
    • 窗口减小:当接收方处理数据的速度变慢,接收缓冲区接近满状态时,会减小通告窗口的大小,以此限制发送方的发送速率。
    • 零窗口(Zero Window):当接收缓冲区完全满时,接收方会通告窗口大小为 0。这时,发送方必须停止发送数据,进入等待状态,直到收到接收方发送的窗口更新通知(Window Update)。
    • 窗口探测(Window Probe):在发送方收到零窗口通知后,为了避免因接收方的窗口更新通知丢失而导致的死锁情况,发送方会定期发送窗口探测报文,以确认接收方的窗口是否有变化。
  • 优点:
    • 高效性:滑动窗口机制允许发送方在等待确认的同时继续发送后续数据,这大大提高了数据传输的效率,实现了流水线式的数据传输。
    • 流量控制:通过动态调整窗口大小,接收方可以有效地控制发送方的发送速率,防止自己因处理能力不足而导致数据溢出和丢包。
    • 可靠性:结合 ACK 确认和重传机制,滑动窗口确保了数据传输的可靠性,即使在网络出现丢包的情况下,也能保证数据准确无误地到达接收方。
  • 与拥塞控制的关系:
    • 滑动窗口主要负责流量控制,关注的是接收方的处理能力,它决定了发送方在一定时间内可以发送的数据量上限。
    • 拥塞控制则着眼于整个网络的拥塞状况,通过调整拥塞窗口(cwnd)来控制发送方的发送速率,防止因网络过载而导致的性能下降。
    • 在实际运行中,发送窗口的大小是接收窗口(rwnd)和拥塞窗口(cwnd)中的最小值,即发送窗口大小 = min (rwnd, cwnd)。
延迟确认

延迟确认(Delayed Acknowledgment)是 TCP 协议中的一种机制,主要作用是减少网络中 ACK 报文数量,降低网络开销 。具体解释如下:

  • 常规确认:在传统 TCP 确认机制中,接收方每收到一个报文段,就会立即发送一个 ACK 报文来确认收到的数据。
  • 延迟确认:接收方在收到报文段后,并不马上发送 ACK 报文,而是等待一段时间(通常在 200ms 以内 )。在这段时间内,如果接收方又收到了发送方的新数据,那么可以将多个确认合并在一个 ACK 报文中发送出去;若等待超时,即使期间没有收到新数据,接收方也会发送 ACK 报文。
超时重传

发送方每发送一个报文段,就启动一个定时器。在定时器规定时间内(即重传超时时间 RTO,Retransmission Time - Out ),若未收到接收方针对该报文段的 ACK(确认)报文,TCP 协议就认为这个报文段中的数据可能已丢失或损坏,于是重新组织并发送该报文段 ,直到成功收到确认或达到最大重传次数。

工作流程示例:

  1. 发送报文段:发送方发送一个报文段,并启动定时器。
  2. 等待确认:等待接收方的 ACK 报文。
  3. 超时判断:若定时器超时,仍未收到 ACK,发送方重传该报文段,并重置定时器。
  4. 重复过程:若重传后还是未收到 ACK,继续重传,直到收到 ACK 或达到系统设定的最大重传次数。不同系统对重传次数设置不同,有些系统一个报文最多重传 3 次,之后若仍无确认,就重置 TCP 连接;高要求业务系统可能会不断重传 。
  • 快重传(Fast Retransmit)

    • 原理:当接收方检测到某个报文段丢失时,后续收到其他报文段会不断发送重复的 ACK 给发送方。当发送方接收到三个重复的 ACK 时,就认为对应的报文段丢失,不等超时就立即重传该丢失的报文段。重传后,将 ssthresh 设置为当前拥塞窗口大小的一半,然后进入快速恢复阶段。
    • 作用:快速检测到丢包情况并及时重传,避免因等待超时才重传导致的传输延迟,提高网络传输效率 。
  • 快恢复(Fast Recovery)

    • 原理:进入快恢复阶段后,发送窗口大小设置为 ssthresh 加 1 个 MSS 。此后,每收到一个非重复的 ACK,发送窗口大小增加 1 个 MSS 。当收到所有丢失报文段的 ACK 后,退出快速恢复阶段,进入拥塞避免阶段继续调整。

    • 作用:在快重传之后,让发送方较快地恢复数据发送速率,避免长时间处于低速率传输状态,维持网络的高效传输 。

2 「代码实现」

2.1 作业:实现 TCP 点对点 P2P 通信的代码

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main(int argc, char const *argv[]) {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    const char *hello = "Hello from client";

    // 检查是否提供了服务器IP地址
    if (argc < 2) {
        printf("Usage: %s <server_ip>\n", argv[0]);
        return -1;
    }

    // 创建socket文件描述符
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\nSocket creation error\n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将IPv4地址从点分十进制转换为二进制形式
    if (inet_pton(AF_INET, argv[1], &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported\n");
        return -1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed\n");
        return -1;
    }

    // 发送消息
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent to server\n");

    // 接收消息
    int valread = read(sock, buffer, BUFFER_SIZE);
    printf("Server: %s\n", buffer);

    close(sock);
    return 0;
}  

服务端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    const char *hello = "Hello from server";

    // 创建socket文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置socket选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定socket到指定地址和端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // 接受连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 接收消息
    int valread = read(new_socket, buffer, BUFFER_SIZE);
    printf("Client: %s\n", buffer);

    // 发送消息
    send(new_socket, hello, strlen(hello), 0);
    printf("Hello message sent to client\n");

    close(new_socket);
    close(server_fd);
    return 0;
}  

下一章:2.2.3 UDP的可靠传输协议QUIC

https//github.com.0voice

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

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

相关文章

LlamaIndex

1、大语言模型开发框架的价值是什么? SDK:Software Development Kit,它是一组软件工具和资源的集合,旨在帮助开发者创建、测试、部署和维护应用程序或软件。 所有开发框架(SDK)的核心价值,都是降低开发、维护成本。 大语言模型开发框架的价值,是让开发者可以更方便地…

springboot使用xdoc-report包导出word

背景&#xff1a;项目需要使用xdoc-report.jar根据设置好的word模版&#xff0c;自动填入数据 导出word 框架使用 我的需求是我做一个模板然后往里面填充内容就导出我想要的word文件&#xff0c;问了下chatgpt还有百度&#xff0c;最后选用了xdocreport这个框架&#xff0c;主…

重拾GMP

目录 GMP总结 线程协程三家对比GMP调度模型 mgp过一遍流程 g 一个G的生命周期 mpschedt全局队列g0视角看看G的调度流程 四大调度类型 主动调度被动调度正常调度抢占调度 宏观的调度流程上面流程的具体细节 schedule()findRunnable()execute()gosched_m()park_m()与ready()goe…

实验分享|基于千眼狼sCMOS科学相机的流式细胞仪细胞核成像实验

实验背景 流式细胞仪与微流控技术&#xff0c;为细胞及细胞核成像提供新的路径。传统流式细胞仪在细胞核成像检测方面存在检测通量低&#xff0c;荧光信号微弱等局限&#xff0c;故某光学重点实验室开发一种基于高灵敏度sCMOS科学相机并集成在自组荧光显微镜的微流控细胞核成像…

【Linux笔记】——线程池项目与线程安全单例模式

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;Linux &#x1f339;往期回顾&#x1f339;&#xff1a; 【Linux笔记】——简单实习一个日志项目 &#x1f516;流水不争&#xff0c;争的是滔滔不息 一、线程池设计二…

ZooKeeper 原理解析及优劣比较

大家好&#xff0c;这里是架构资源栈&#xff01;点击上方关注&#xff0c;添加“星标”&#xff0c;一起学习大厂前沿架构&#xff01; 引言 在分布式系统中&#xff0c;服务注册、配置管理、分布式锁、选举等场景都需要一个高可用、一致性强的协调服务。Apache ZooKeeper 凭…

是德科技 | 单通道448G未来之路:PAM4? PAM6? PAM8?

内容来源&#xff1a;是德科技 随着数据中心规模的不断扩大以及AI大模型等技术的兴起&#xff0c;市场对高速、大容量数据传输的需求日益增长。例如&#xff0c;AI训练集群中GPU等设备之间的互联需要更高的传输速率来提升效率。在技术升级方面&#xff0c;SerDes技术的不断进步…

OceanBase 开发者大会,拥抱 Data*AI 战略,构建 AI 数据底座

5 月 17 号以“当 SQL 遇见 AI”为主题的 OceanBase 开发者大会在广州举行&#xff0c;因为行程的原因未能现场参会&#xff0c;仍然通过视频直播观看了全部的演讲。总体来说&#xff0c;这届大会既有对未来数据库演进方向的展望&#xff0c;也有 OceanBase 新产品的发布&#…

STM32IIC协议基础及Cube配置

STM32IIC协议基础及Cube配置 一&#xff0c;IC协议简介1&#xff0c;核心特点2&#xff0c;应用场景 二&#xff0c;IC协议基础概念1&#xff0c;总线结构2&#xff0c;主从架构3&#xff0c;设备寻址4&#xff0c;起始和停止条件5&#xff0c;数据传输6&#xff0c;应答机制 三…

CNN vs ViT:图像世界的范式演进

一、图像建模&#xff0c;是不是也可以“大一统” 在前文中我们提到&#xff0c;多模态大模型打破“只能处理文字”的限制。 在 NLP 世界里&#xff0c;Transformer 已经证明自己是理解语言的王者。那么在图像世界&#xff0c;我们是否也能有一种“通用架构”&#xff0c;让模…

cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins

cocos 版本2.4.11 在windows上jenkins的具体配置和部署&#xff0c;可参考上一篇文章cocos creator使用jenkins打包流程&#xff0c;打包webmobile_jenkins打包,发布,部署cocoscreator-CSDN博客 特别注意&#xff0c;windows上运行jenkins需要关闭windows自己的jenkins服务&a…

定时器的两种实现方式

1、基于优先级队列/堆 队列是先进先出&#xff0c;优先级队列是优先级越高就存放在队列之前&#xff0c;我们可以将过期时间越早设置为优先级越高&#xff0c;那么临近过期时间的任务就会在队列前面&#xff0c;距离过期时间越晚的任务就在队列后面。 可以分配一个线程&#…

[Java实战]Spring Boot整合MinIO:分布式文件存储与管理实战(三十)

[Java实战]Spring Boot整合MinIO&#xff1a;分布式文件存储与管理实战&#xff08;三十&#xff09; 一、MinIO简介与核心原理 MinIO 是一款高性能、开源的分布式对象存储系统&#xff0c;兼容 Amazon S3 API&#xff0c;适用于存储图片、视频、日志等非结构化数据。其核心特…

AI在人力资源领域的应用:把握时代浪潮

借鉴历史经验&#xff0c;引领技术变革 历史总是呈现出惊人的相似性。十年前&#xff0c;众多企业未能及时洞察移动技术与社交技术的潜在价值&#xff0c;迟迟没有将这些创新引入职场环境。随着时间推移&#xff0c;这些组织才意识到BYOD&#xff08;自带设备办公&#xff09;…

vr制作公司提供什么服务?

随着科技的迅猛进步&#xff0c;虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;技术已经悄然渗透到我们的日常生活与工作中&#xff0c;成为推动数字化转型的重要力量。VR制作公司&#xff0c;作为前沿领域的探索者和实践者&#xff0c;以专业的技术和创新…

下一代电子电气架构(EEA)的关键技术

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

matlab慕课学习3.5

于20250520 3.5 用while 语句实现循环结构 3.5.1while语句 多用于循环次数不确定的情况&#xff0c;循环次数确定的时候用for更为方便。 3.5.2break语句和continue语句 break用来跳出循环体&#xff0c;结束整个循环。 continue用来结束本次循环&#xff0c;接着执行下一次…

Qt音视频开发过程中一个疑难杂症的解决方法/ffmpeg中采集本地音频设备无法触发超时回调

一、前言 最近在做实时音视频通话的项目中&#xff0c;遇到一个神奇的问题&#xff0c;那就是用ffmpeg采集本地音频设备&#xff0c;当音频设备拔掉后&#xff0c;采集过程会卡死在av_read_frame函数中&#xff0c;尽管设置了超时时间&#xff0c;也设置了超时回调interrupt_c…

PEFT库PromptTuningConfig 配置

PEFT库 PromptTuningConfig 配置 "Prompt Tuning"的参数高效微调 PromptTuningConfig 核心参数解析 1. task_type="CAUSAL_LM" 作用:指定任务类型为因果语言模型(Causal LM)。说明:因果语言模型从左到右生成文本(如GPT系列),这与任务需求匹配(模…

操作系统----软考中级软件工程师(自用学习笔记)

目录 1、计算机系统层次结构 2、程序顺序执行的特征 3、程序并发执行的特征 4、三态模型 5、同步与互斥 6、信号量机制 7、PV操作 8、死锁 9、进程资源图 10、死锁避免 11、线程 12、程序局部性原理 13、分页存储管理 14、单缓冲器 15、双缓冲区 16、磁盘调度算…