• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2026-07-18 14:51 Aet 隐藏边栏 |   抢沙发  1 
文章评分 1 次,平均分 5.0

常用socket选项

概述

  1. Socket 选项通过 setsockopt() / getsockopt() 函数来设置和获取,用于控制套接字的行为
  2. 参数
    1. level:选项所在的协议层(如 SOL_SOCKETIPPROTO_TCPIPPROTO_IP
    2. optname:具体选项名
    3. optval:选项值的指针
    4. optlen:选项值长度

SO_REUSEADDR

  1. 最常用的选项之一,允许绑定处于 TIME_WAIT 状态的地址
  2. 服务器重启时经常需要它,否则会报 "Address already in use"

SO_REUSEPORT

  1. 允许多个 socket 绑定到同一个 IP+端口,内核会做负载均衡(Linux 3.9+ 支持)
  2. 常用于多进程/多线程服务器提升 accept 性能

SO_KEEPALIVE

  1. 开启 TCP 保活机制,定期发送探测包检测对端是否存活,避免连接假死
  2. 具体探测间隔可通过 TCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNTIPPROTO_TCP 层)调整

SO_LINGER

  1. 控制 close() 时的行为
    1. l_onoff=0:默认行为,close 立即返回,内核尽力发送剩余数据
    2. l_onoff=1, l_linger=0close 立即返回,丢弃未发送数据并发 RST(常用于强制关闭)
    3. l_onoff=1, l_linger>0close 阻塞直到数据发送完或超时

SO_RCVBUF / SO_SNDBUF

  1. 设置接收/发送缓冲区大小,影响吞吐量,尤其在高延迟高带宽网络(长肥管道)中需要调大

SO_RCVTIMEO / SO_SNDTIMEO

  1. 设置阻塞式 recv/send 的超时时间

SO_ERROR

  1. 获取并清除 socket 上待处理的错误
  2. 常配合 select/epoll 检测非阻塞 connect 是否成功

SO_BROADCAST

  1. 允许发送广播数据报(UDP

常用IPPROTO_TCP 层选项

TCP_NODELAY

  1. 禁用 Nagle 算法,数据立即发送而不等待缓冲区填满或 ACK
  2. 对低延迟场景(如实时游戏、RPC)很重要

TCP_KEEPIDLE / TCP_KEEPINTVL / TCP_KEEPCNT

  1. 配合 SO_KEEPALIVE 使用,分别控制:空闲多久后开始探测、探测间隔、探测失败次数上限

TCP_CORK

  1. TCP_NODELAY 相反,将多次小写入合并成一个包发送(Linux 特有),适合先写头部再写body的场景

TCP_FASTOPEN

  1. 支持 TFO,减少三次握手带来的一次 RTT 延迟

常用IPPROTO_IP 层选项

IP_TTL

  1. 设置 IP 包的生存时间(跳数限制)

IP_MULTICAST_TTL / IP_ADD_MEMBERSHIP

  1. 用于多播场景,加入/离开多播组

一个典型服务器设置组合

RTT (Round-Trip Time,往返时延)

定义

  1. 数据包从发送端出发,到达接收端,再收到接收端响应(如 ACK)所经历的总时间

RTT 的组成

  1. 传播时延:
    1. 信号在物理介质中传播的时间,由距离和介质决定(光纤中约 200,000 km/s
    2. 这部分有物理下限,无法通过技术手段消除(比如跨太平洋光缆传播时延就是几十毫秒起)
  2. 传输时延:
    1. 把数据包"推"上链路所需的时间,取决于包大小和链路带宽
  3. 排队时延:
    1. 数据包在路由器/交换机队列里等待转发的时间,网络拥塞时这部分会显著增大
  4. 处理时延:
    1. 设备处理包头、路由查找等的时间,通常很小

RTT 的测量与作用

  1. TCP 通过给每个包打时间戳、观察 ACK 返回时间来估算 RTT
  2. RTT 是计算 RTO 的基础,也是拥塞控制算法(如 BBR)判断网络状况的关键信号
  3. RTT 会动态波动(网络拥塞、路由变化),所以 TCP/KCP 都用移动平均而非单次采样值

RTO (Retransmission TimeOut,重传超时)

定义

  1. 发送端发出一个包后,如果在 RTO 时间内没收到对应的确认(ACK),就认为这个包丢失,触发重传

为什么 RTO 不能是固定值

  1. 设太小:
    1. 网络稍有波动就误判丢包,触发不必要的重传,浪费带宽,甚至引发拥塞崩溃
  2. 设太大:
    1. 真丢包后要等很久才重传,浪费时间,影响实时性
  3. 所以 RTO 必须根据实时 RTT 动态计算,这就是经典的 Jacobson/Karels 算法
    1. 直觉理解:
    2. srttRTT 的"平滑估计值"(滤除瞬时抖动)
    3. rttvar 反映 RTT 的波动程度,网络越不稳定这个值越大
    4. RTO = 平均RTT + 4倍的抖动余量,网络越抖动,RTO 留的安全边际越大

RTO 超时后怎么办

  1. TCP 采用指数退避
    1. 每次因超时重传后,RTO 翻倍(1s → 2s → 4s → ...),直到收到 ACK 或达到重传上限断开连接
  2. KCP 的关键区别在这里
    1. KCP 认为很多场景(游戏)宁可承受一点冗余流量也要降低延迟,所以它的 RTO 增长策略更激进(可配置为线性增长而非指数翻倍)
    2. 并且提供"快速重传"机制(不等 RTO,只要某个包被跳过确认达到阈值次数就立即重传),这是它比原生 TCP 更"快"的核心原因

TLS (Transport Layer Security,传输层安全协议)

作用

  1. TLS 跑在 TCP 之上(或者 QUIC 场景下集成在传输层里),为应用层数据提供加密、完整性校验、身份认证三大能力
    1. HTTPS 本质就是 HTTP + TLS

TLS 握手过程( TLS 1.3 为例)

  1. TLS 1.3 相比 1.2 最大的改进是握手轮次从 2-RTT 压缩到 1-RTT
    1. 1.2 需要先协商加密套件再交换密钥,是两个来回
    2. 1.3 把这些合并到一次往返里

完整连接建立的延迟叠加问题

  1. 这是理解 QUIC 设计动机的关键

0-RTT 的原理

  1. 客户端如果之前和这个服务器建立过连接,会缓存一个叫 PSK (Pre-Shared Key) 的密钥材料
  2. 下次连接时,客户端可以在第一个包里就直接带上加密的应用数据(不用等握手完成),服务端验证 PSK 有效就能立即解密处理
  3. 代价:0-RTT 数据存在重放攻击风险(攻击者截获这个0-RTT包可以重放给服务器),所以只有"幂等"的请求(比如 GET,不会因为重复执行造成副作用)才适合放在 0-RTT 里发送

为什么 QUIC 强制集成 TLS 1.3

  1. 这不是偶然设计,而是刻意为之:
    1. QUIC 从协议层面就不允许跑明文,传输层握手和加密握手合二为一,这样既减少了握手延迟,也从设计上消灭了"裸奔的 QUIC 连接"这种可能性
    2. 对比 TCP,裸 TCP 是可以不加 TLS 明文跑的

队头阻塞

TCP 层面的队头阻塞

  1. TCP 是严格有序的字节流协议,接收端必须按序把数据交给应用层
    1. 如果序号为 3 的包丢了,即使序号 4、5、6 已经到达并且被接收端缓存,应用层也拿不到 4、5、6 的数据,必须死等 3 号包重传到达后才能一起交付:

  1. 这是传输层(TCP自身)造成的阻塞

HTTP/1.1 层面的队头阻塞

  1. HTTP/1.1 单个 TCP 连接上,请求必须排队串行处理
    1. 虽然有 pipelining 技术允许连续发多个请求,但响应必须按请求顺序返回
  2. 如果第一个请求处理慢,后面的请求即使已经处理完,也要排在它后面等着返回:

  1. 浏览器的应对方案是
    1. 对同一个域名开多个 TCP 连接(通常 6 个)绕开这个限制,但这带来了额外的连接建立开销和拥塞控制资源浪费

HTTP/2 层面:

  1. 解决了应用层阻塞,但引入了新的传输层阻塞
  2. HTTP/2 引入了多路复用(Multiplexing):
    1. 单个 TCP 连接上可以并行跑多个 Stream,每个 Stream 独立编号,互不阻塞对方在应用层的处理顺序

  1. 这解决了 HTTP/1.1 的应用层队头阻塞
    1. 但所有 Stream 的数据最终都是同一个 TCP 字节流的一部分
    2. 如果这个 TCP 连接中间丢了一个包,属于该包位置的所有 Stream 数据都要等待重传,即使这个丢失的包只影响 Stream 2
      Stream 1Stream 3 的数据也会被这个 TCP 层面的顺序保证给拖住

  1. 这就是所谓"HTTP/2 把应用层队头阻塞问题转移到了传输层"

QUIC (HTTP/3) 如何真正解决

  1. QUIC 跑在 UDP 之上,每个 Stream 有自己独立的序号空间和独立的可靠性保证,不共享一个全局字节流顺序:
    1. 因为底层是 UDP,一个 QUIC 包丢失只意味着它所携带的那些 Stream 帧需要重传,其他 Stream 的数据包该怎么到达就怎么到达,不会被没有关系的丢包连累
    2. 这是 QUIC 相比 HTTP/2-over-TCP 的核心优势,也是队头阻塞问题在传输层第一次被真正消除,而不是"转移"

UDP

基本特性

  1. UDPUser Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,与 TCP 相对
  2. 核心特点:
    1. 无连接:发送数据前不需要建立连接(没有三次握手)
    2. 不可靠:不保证数据到达、不保证顺序、不做重传
    3. 无拥塞控制:发送速率不受网络拥塞状况限制
    4. 面向报文:应用层交给 UDP 多长的数据,UDP 就照原样发送,不会拆分或合并
    5. 头部开销小:只有 8 字节,比 TCP20 字节小很多
    6. 支持一对一、一对多(多播)、广播

报文格式

  1. 源端口/目的端口:各 2 字节
  2. 长度:UDP 首部+数据的总长度,最小值为 8(只有首部无数据)
  3. 校验和:可选(IPv4 中可以为 0 表示不校验,IPv6 中必须计算),校验伪首部+UDP首部+数据

UDP vs TCP 对比

特性 UDP TCP
连接方式 无连接 面向连接
可靠性 不可靠 可靠(确认、重传)
顺序保证 不保证 保证
拥塞/流量控制
头部开销 8字节 20字节起
传输方式 面向报文 面向字节流
效率 高(少了握手和确认开销) 相对低
适用场景 实时性要求高、可容忍丢包 要求可靠传输

适用场景

  1. DNS 查询
    1. 一问一答,简单高效,重传交给应用层处理
  2. 视频/音频流、直播、VoIP
    1. 实时性比可靠性更重要,丢一帧比卡顿更能接受
  3. 在线游戏
    1. 低延迟优先,客户端会自己处理状态同步和丢包补偿
  4. DHCP
    1. 还没有 IP 地址时无法建立 TCP 连接
  5. SNMP
    1. 网络管理协议
  6. 广播/多播场景
    1. TCP 天生不支持一对多

Socket 编程示例(Linux C

  1. 服务端

  1. 客户端

丢包与乱序

  1. UDP 本身不处理,需要应用层自己实现(如加序号、超时重传机制)

报文大小限制

  1. 理论最大 65507 字节(65535 - 8字节UDP头 - 20字节IP头)
  2. 实际受 MTU 限制(以太网通常 1500 字节),超过会在 IP 层分片,分片会增加丢包概率(一片丢失整个报文就废了)
    1. 所以实践中通常控制在 548 字节以内(保证不分片)或应用层自己做分片重组
  3. 为什么控制在 548 字节以内?
    1. 这个数字来源于一个历史标准:
    2. IPv4 规定所有主机都必须能够接收至少 576 字节的 IP 数据包,且不需要分片重组失败(RFC 791/RFC 1122

  1. 也就是说,任何一台联网的主机,无论其网络配置、MTU 大小是多少,都必须能正确接收 576 字节的 IP 包,这是 IPv4 协议栈的最低保证
    1. 如果你发的 UDP 数据报总大小(IP头+UDP头+数据)不超过 576 字节,那么无论中间经过多少台不同的路由器、无论这些路由器的接口 MTU 是多少,都不会出现"目标主机因为重组缓冲区太小而丢弃分片"的情况
  2. 为什么不是直接用以太网 MTU (1500)?
    1. 以太网 MTU 通常是 1500 字节,减去 IP 头和 UDP 头,也能算出一个"不分片"的安全值(1500-20-8=1472
    2. 问题在于:
    3. 中间链路可能有更小的 MTU(比如某些隧道、VPNPPPoE 会把 MTU 降到 1492 甚至更低)
    4. 你不知道对端的完整网络路径,公网环境下无法保证全程 MTU 都是 1500
    5. 576 字节这个数字是协议规范里的硬性保证,而 1500 只是"以太网通常如此"的经验值,不是标准强制的下限

常用 socket 选项

  1. SO_RCVBUF/SO_SNDBUF:调大缓冲区避免因处理不及时导致丢包
  2. SO_BROADCAST:发送广播时需要
  3. IP_ADD_MEMBERSHIP:加入多播组

半连接的 connect()

  1. UDP 也可以调用 connect(),但这不会建立真正连接,只是把对端地址"绑定"到 socket
  2. 之后可以用 send/recv 代替 sendto/recvfrom,效率略高(内核不用每次都做地址检查)

UDP多播编程细节

基本概念

  1. 多播允许一份数据发送给一组感兴趣的接收者,而不需要像广播那样发给网段内所有主机,也不需要像单播那样对每个接收者发一份

多播地址范围

  1. IPv4 多播地址是 D 类地址:224.0.0.0 ~ 239.255.255.255

  1. 常见保留地址:
    1. 224.0.0.1:本网段所有主机
    2. 224.0.0.2:本网段所有路由器
    3. 224.0.0.5/224.0.0.6OSPF

核心流程

  1. 多播编程和普通 UDP 的区别主要在于:
    1. 接收端需要显式"加入多播组",发送端一般不需要特殊处理(就是往多播地址发 UDP 包)

接收端:加入多播组

  1. 创建 socket 并绑定
    1. bind() 通常绑定 INADDR_ANY(也有些系统允许直接绑定多播地址,但跨平台兼容性较差,推荐绑 INADDR_ANY
    2. 真正决定"接收哪个组的数据"的是下一步的 IP_ADD_MEMBERSHIP

  1. 加入多播组(关键步骤)
    1. 这一步的本质是:
    2. 通知网卡驱动和内核把这个多播MAC地址加入网卡的接收过滤表
    3. 同时(如果有路由器)触发 IGMP 协议向上游路由器发送 Membership Report,告诉路由器"这个网段有人对这个组感兴趣,请把相应多播流量转发过来"

  1. 接收数据

  1. 离开多播组(可选,进程退出时内核会自动清理)

发送端

  1. 发送端不需要加入组也能发送(但如果发送端自己也想接收自己发的包,需要设置 IP_MULTICAST_LOOP,且也要 join

发送端专属选项

  1. IP_MULTICAST_TTL:控制多播范围

  1. TTL 的含义和普通 IP 包一样,但对多播有约定俗成的范围划分:

  1. IP_MULTICAST_IF:指定发送用的网卡
    1. 多网卡主机上,指定多播数据从哪个网卡接口发出

  1. IP_MULTICAST_LOOP:是否接收自己发的多播包

源特定多播 SSMSource-Specific Multicast

  1. 传统多播(ASM, Any-Source Multicast)中,接收端加入一个组就能收到任何人发到这个组的数据,容易被恶意源攻击或产生冲突
  2. SSM 让接收端可以指定"只接收来自特定源IP的多播流",地址范围通常在 232.0.0.0/8

IGMP:底层怎么工作的

  1. 多播能跨网段传递,依赖 IGMPInternet Group Management Protocol) 协议,运行在主机与直连路由器之间:
    1. 主机执行 IP_ADD_MEMBERSHIP 后,内核自动发送 IGMP Membership Report,告诉路由器"我要加入组 224.1.1.1"
    2. 路由器记录下来,并通过多播路由协议(如 PIM)向上游请求这个组的流量
    3. 路由器周期性发送 IGMP Query,询问网段内还有没有人在监听这个组
    4. 主机如果还需要就回应 Report;如果 IP_DROP_MEMBERSHIP 或进程退出,路由器发现网段内没人回应后会停止转发对应组的流量

多播的局限与实践问题

  1. 不可靠(和普通UDP一样)
    1. 多播同样没有确认、重传机制,丢包完全无感知
  2. 公网环境支持差
    1. 互联网上的路由器大多默认不开启多播路由(PIM等),多播基本只能用在你能控制的网络里
  3. 网卡/系统层的组数限制
    1. 每个网卡能加入的多播组数量有限制(内核参数、网卡硬件过滤表大小),大量小群组场景需要注意
  4. IPv6 多播
    1. IPv6 没有广播概念,全部用多播替代,API 类似但结构体和地址族不同:

UDP 上实现可靠传输

概述

  1. TCP 已经是可靠传输了,为什么还要在 UDP 上重新造轮子?
    1. 核心原因是 TCP 的可靠性策略是操作系统内核实现的、不可定制的,而很多场景需要按需定制可靠性策略
    2. 比如牺牲一点带宽换取更低延迟
    3. KCPQUIC 就是典型代表

为什么不用 TCP:核心痛点

  1. 队头阻塞(Head-of-Line Blocking
    1. TCP 是字节流,必须严格按序交付
    2. 如果第 3 个包丢了,即使第 4、5 个包先到,应用层也拿不到,内核会一直攒着等第 3 个包重传
    3. QUIC 用多个独立的 stream 来解决这个问题,一个 stream 丢包不影响其他 stream
  2. 重传策略保守
    1. TCP 默认使用超时重传(RTO)为主,RTO 的计算比较保守(基于 RTT 均值和方差),丢包后往往要等一个较长的超时时间才重传,不适合实时性要求高的场景
    2. KCP 采用更激进的快速重传+选择性重传策略,用带宽换延迟
  3. 拥塞控制不可控
    1. TCP 的拥塞控制(如 CubicReno)是内核实现的,应用层难以介入或替换
    2. QUIC 跑在用户态,可以灵活切换拥塞控制算法(BBR等)
  4. 连接建立慢
    1. TCP 三次握手 + TLS 握手叠加起来延迟很高
    2. QUIC 把传输层和加密层握手合并,实现 0-RTT / 1-RTT 建连

核心设计要素

  1. 要在 UDP 上做可靠传输,本质上要在应用层重新实现 TCP 干的几件事:
    1. 序号机制:给每个包编号,接收端识别乱序、去重
    2. 确认机制(ACK):接收方告诉发送方哪些包收到了
    3. 重传机制:没收到 ACK 就重传
    4. 流量控制:防止接收方缓冲区被打爆
    5. 拥塞控制:防止网络被打爆
    6. (可选)多路复用:解决队头阻塞

KCP 简化版实现

包头设计

序号与确认:ARQ (Automatic Repeat reQuest)

  1. 最基本的思路:
    1. 发送端给每个数据包一个递增序号 sn
    2. 接收端收到后记录哪些 sn 到了,回一个 ACK 包告诉发送端
    3. 发送端维护一个"发送窗口",未被确认的包留在窗口里,超时或收到重复 ACK 就重传
  2. 有两种确认方式:
    1. 累计确认(Cumulative ACK,类似TCP
      只告诉发送方"这个序号之前的都收到了",简单但精度低,丢一个包发送方不知道具体是哪个
    2. 选择性确认(SACK
      明确告诉发送方哪些序号收到了、哪些没收到,KCPQUIC 都用这个,效率更高

重传策略:这是 KCP 的核心优化点

  1. 超时重传(RTO
    1. 维护每个包的发送时间戳,超过 RTO 没收到 ACK 就重传
    2. RTO 的计算通常参考 TCP 的做法(Jacobson 算法)
    3. KCP 的关键改动:
    4. RTO 增长不是翻倍(不像TCP的指数退避),而是线性增长,重传更激进,用带宽换延迟
      这是它比 TCP 快的核心原因之一

  1. 快速重传(Fast Retransmit
    1. 不等超时,如果某个包被跳过确认的次数超过阈值(比如 skip=2),就认为它大概率丢了,立即重传

早期重传 / 尾部丢包处理

  1. QUIC 还引入了 Tail Loss Probe (TLP)
    1. 如果发送队列尾部包迟迟没被确认,主动发探测包触发对端 ACK,避免死等超时

流量控制:滑动窗口

  1. 接收端通过 ACK 包中的 wnd 字段告诉发送端自己还能接收多少数据,发送端据此调整发送速率,防止把对端缓冲区打爆:

拥塞控制:这是最复杂也最能体现差异化的部分

  1. KCP 的做法(简化的类TCP拥塞控制)
    1. 慢启动:cwnd 从小窗口开始,指数增长直到出现丢包
    2. 拥塞避免:丢包后 cwnd 减半,之后线性增长
    3. KCP 提供"普通模式"和"极速模式"(ikcp_nodelay),极速模式关闭拥塞控制的保守策略,用更小的探测间隔、更激进的重传,牺牲带宽换延迟
      适合游戏这种小数据量高频率的场景
  2. QUIC 的做法
    1. QUIC 默认使用类似 TCP CUBICBBR 的拥塞控制算法,且因为跑在用户态,可以针对不同场景插拔式替换算法,比如:
    2. BBR:基于带宽和RTT建模而非丢包信号,更适合高丢包率但带宽充足的网络(如4G/5G
    3. CUBIC:传统的基于丢包反馈调整

多路复用解决队头阻塞(QUIC 的杀手锏)

  1. QUIC 在一个连接内部支持多个独立的 Stream

  1. 每个 Stream 有自己独立的序号空间,一个 Stream 丢包重传,不会阻塞其他 Stream 的数据交付
    1. 这是 HTTP/3 选择 QUIC 而不是 TCP 的核心原因
    2. HTTP/2 多路复用跑在单个 TCP 连接上,一旦丢包全部 Stream 都会被卡住

连接迁移(QUIC 独有)

  1. TCP 连接是用四元组(源IP、源端口、目的IP、目的端口)标识的
    1. 换网络(如WiFi4G)四元组变了连接就断了
  2. QUIC 用一个独立的 Connection ID 标识连接,与IP/端口解耦
    1. 网络切换时连接可以无缝迁移,不需要重新握手

总结对比

方面 TCP KCP QUIC
运行位置 内核 用户态 用户态
重传策略 保守(指数退避) 激进(线性退避+快速重传) 灵活可配置
拥塞控制 固定算法,不可插拔 简化的类TCP算法 可插拔(BBR/CUBIC等)
队头阻塞 有(单流) 无(多Stream)
连接迁 不支持 不涉及(无连接概念) 支持
握手延迟 高(TCP+TLS分离) 无握手概念 低(0-RTT/1-RTT合并)
目标场景 通用可靠传输 游戏、实时性优先 Web、HTTP/3

KCP

  1. kcp

QUIC

  1. QUIC协议相关学习一

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享