一、引言
套接字有很多可选项,可以用来自定义细节。
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_STREAM 或 SOCK_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_RCVBUF
、TCP_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 三次握手(建立连接)中的状态
- CLOSED:
- 初始状态,表示套接字未使用或连接已关闭。
- LISTEN:
- 服务端套接字进入监听状态,等待来自客户端的连接请求。
- SYN-SENT:
- 客户端发送
SYN
报文后进入此状态,等待服务端的SYN+ACK
报文。
- 客户端发送
- SYN-RECEIVED:
- 服务端接收到客户端的
SYN
报文并回复SYN+ACK
后进入此状态,等待客户端的确认(ACK
)。
- 服务端接收到客户端的
- ESTABLISHED:
- 表示连接已建立,双方可以开始数据传输。
TCP 四次挥手(断开连接)中的状态
- ESTABLISHED:
- 连接正常建立,数据传输过程中。
- FIN-WAIT-1:
- 主动关闭的一方(发送
FIN
报文的一方)进入此状态,等待对方的ACK
或FIN+ACK
。
- 主动关闭的一方(发送
- CLOSE-WAIT:
- 被动关闭的一方接收到
FIN
后发送ACK
,进入此状态,准备关闭连接。
- 被动关闭的一方接收到
- FIN-WAIT-2:
- 主动关闭的一方接收到对方的
ACK
后进入此状态,等待对方的FIN
。
- 主动关闭的一方接收到对方的
- LAST-ACK:
- 被动关闭的一方发送
FIN
后进入此状态,等待对方的ACK
。
- 被动关闭的一方发送
- TIME-WAIT:
- 主动关闭的一方接收到对方的
FIN
并回复ACK
后进入此状态,等待足够的时间以确保对方收到ACK
(通常是 2 倍的最大报文段寿命,2MSL)。
- 主动关闭的一方接收到对方的
- 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 算法才发送下一数据。
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));