套接字的多种可选项

Posted by KalosAner on January 16, 2025

一、引言

套接字有很多可选项,可以用来自定义细节。

1、通用套接字选项

协议层 选项名 作用 可读(R) 可写(W)
SOL_SOCKET SO_REUSEADDR 允许重用本地地址和端口(防止绑定失败)。
SOL_SOCKET SO_REUSEPORT 允许多个套接字绑定到同一地址和端口(部分系统支持)。
SOL_SOCKET SO_RCVBUF 设置接收缓冲区大小。
SOL_SOCKET SO_SNDBUF 设置发送缓冲区大小。
SOL_SOCKET SO_KEEPALIVE 开启 TCP 的保活机制。
SOL_SOCKET SO_LINGER 设置延迟关闭行为。
SOL_SOCKET SO_BROADCAST 允许发送广播数据包(仅对 UDP 生效)。
SOL_SOCKET SO_ERROR 获取套接字的错误状态。
SOL_SOCKET SO_TYPE 获取套接字类型(如 SOCK_STREAMSOCK_DGRAM)。

2、TCP 专用选项

协议层 选项名 作用 可读(R) 可写(W)
IPPROTO_TCP TCP_NODELAY 禁用 Nagle 算法,数据会立即发送而不是等待缓冲区填满。
IPPROTO_TCP TCP_MAXSEG 设置 TCP 的最大段大小(MSS)。
IPPROTO_TCP TCP_KEEPIDLE 设置 TCP 保活探测启动前的空闲时间。
IPPROTO_TCP TCP_KEEPINTVL 设置 TCP 保活探测之间的间隔时间。
IPPROTO_TCP TCP_KEEPCNT 设置 TCP 保活探测的最大失败次数。

3、IP 协议选项

协议层 选项名 作用 可读(R) 可写(W)
IPPROTO_IP IP_TTL 设置 IP 数据报的生存时间(TTL)。
IPPROTO_IP IP_MULTICAST_TTL 设置多播数据报的生存时间(TTL)。
IPPROTO_IP IP_MULTICAST_LOOP 启用或禁用多播回环功能(发送的多播数据是否会回到本地)。
IPPROTO_IPV6 IPV6_UNICAST_HOPS 设置 IPv6 单播数据包的跳数限制(类似于 IPv4 的 TTL)。
IPPROTO_IPV6 IPV6_MULTICAST_HOPS 设置 IPv6 多播数据包的跳数限制。

读取选项值(getsockopt):

1
2
3
4
5
int value;
socklen_t optlen = sizeof(value);
if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &value, &optlen) == 0) {
    printf("Receive buffer size: %d\n", value);
}

设置选项值(setsockopt):

1
2
3
4
int new_size = 8192; // 8 KB
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &new_size, sizeof(new_size)) == 0) {
    printf("Receive buffer size updated.\n");
}

Tips:输入输出缓存区不可以并不能完全设置,系统会有上限和下限。

二、函数原型

getsockopt 函数原型

1
2
3
4
5
6
7
8
9
// Linux
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

// Windows
#include <winsock2.h>

int getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen);
  • 参数说明

    • sockfd:套接字描述符。

    • level:协议层级,常见值包括:
      • SOL_SOCKET:通用套接字选项。
      • IPPROTO_TCP:TCP 专用选项。
      • IPPROTO_IP:IPv4 专用选项。
      • IPPROTO_IPV6:IPv6 专用选项。
    • optname:选项名(如 SO_RCVBUFTCP_NODELAY 等)。

    • optval:指向存储选项值的缓冲区。

    • optlen:指向缓冲区长度的指针,调用后包含实际返回的选项值的大小。
  • 返回值

    • 成功时返回 0
    • 失败时返回 -1 并设置 errno

setsockopt 函数原型

1
2
3
4
5
6
7
8
9
// Linux
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

// Windows
#include <winsock2.h>

int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen);
  • 参数说明
    • sockfd:套接字描述符。
    • level:协议层级(与 getsockopt 相同)。
    • optname:选项名。
    • optval:指向设置选项值的缓冲区。
    • optlen:缓冲区大小,单位是字节。
  • 返回值
    • 成功时返回 0
    • 失败时返回 -1 并设置 errno

三、连接和断开所经历的状态

TCP 三次握手(建立连接)中的状态

  1. CLOSED
    • 初始状态,表示套接字未使用或连接已关闭。
  2. LISTEN
    • 服务端套接字进入监听状态,等待来自客户端的连接请求。
  3. SYN-SENT
    • 客户端发送 SYN 报文后进入此状态,等待服务端的 SYN+ACK 报文。
  4. SYN-RECEIVED
    • 服务端接收到客户端的 SYN 报文并回复 SYN+ACK 后进入此状态,等待客户端的确认(ACK)。
  5. ESTABLISHED
    • 表示连接已建立,双方可以开始数据传输。

TCP 四次挥手(断开连接)中的状态

  1. ESTABLISHED
    • 连接正常建立,数据传输过程中。
  2. FIN-WAIT-1
    • 主动关闭的一方(发送 FIN 报文的一方)进入此状态,等待对方的 ACKFIN+ACK
  3. CLOSE-WAIT
    • 被动关闭的一方接收到 FIN 后发送 ACK,进入此状态,准备关闭连接。
  4. FIN-WAIT-2
    • 主动关闭的一方接收到对方的 ACK 后进入此状态,等待对方的 FIN
  5. LAST-ACK
    • 被动关闭的一方发送 FIN 后进入此状态,等待对方的 ACK
  6. TIME-WAIT
    • 主动关闭的一方接收到对方的 FIN 并回复 ACK 后进入此状态,等待足够的时间以确保对方收到 ACK(通常是 2 倍的最大报文段寿命,2MSL)。
  7. CLOSED
    • 最终状态,表示连接已完全关闭。

四、重要的可选项

1、SO_REUSEADDR

SO_REUSEADDR 及其相关的 TIME-WAIT 状态很重要。

TIME-WAIT 状态就是四次挥手时,主动关闭连接的一方接收对方的 FIN 返回 ACK 之后为了避免 ACK 丢包而进入的等待状态。在 TIME-WAIT 状态下,会一直占用端口号,导致无法重新分配给新的套接字。

SO_REUSEADDR 可以解决 TIME-WAIT 端口占用问题,但是新的套接字必须绑定到相同的本地地址和端口,而且操作系统允许在端口处于 TIME-WAIT 状态时重新绑定。

2、SO_REUSEPORT

SO_REUSEPORT 允许多个套接字绑定到同一 IP 地址和端口,可以提高服务器性能,通过多个进程或线程共享同一端口,支持负载均衡。

3、TCP_NODELAY

为防止数据包过多而发生网络过载,Nagle 算法在1984 年诞生了。它应用于 TCP 层,只有收到前一数据的 ACK 消息时,Nagle 算法才发送下一数据。

Snipaste_2025-01-16_17-10-59

Nagle 算法不适用“传输大文件数据”,因为将文件数据传入输出缓存区不会花费太多时间,使用 Nagle 算法会导致无法连续传输。

TCP 默认开启 Nagle 算法,如果需要禁用 Nagle 算法只需要将套接字的 TCP_NODELAY 设置为 1 即可。

1
2
int opt_val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, sizeof(opt_val));