setsockopt系统调用及示例

1. 函数介绍

setsockopt (Set Socket Options) 是一个 Linux 系统调用,用于在已创建的套接字上设置各种选项(options)或参数(parameters)。这些选项控制着套接字的行为和底层协议的特定方面。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

你可以把 setsockopt 想象成调整收音机或电视机的设置:

  • 你有一个设备(套接字)。

  • 这个设备有很多可以调整的旋钮和开关(选项),比如音量(SO_RCVBUF)、音质(SO_REUSEADDR)、电源(SO_KEEPALIVE)等。

  • setsockopt 就是让你转动这些旋钮、切换这些开关,来改变设备的工作方式。

这些选项可以影响套接字的方方面面,例如:

  • 地址重用: 允许绑定到一个处于 TIME_WAIT 状态的地址(SO_REUSEADDR)。

  • 缓冲区大小: 调整发送和接收缓冲区的大小(SO_SNDBUF, SO_RCVBUF)。

  • 连接保活: 启用 TCP 的 keep-alive 机制来检测死连接(SO_KEEPALIVE)。

  • ** linger**: 控制 close 调用在有未发送数据时的行为(SO_LINGER)。

  • 广播: 允许在 UDP 套接字上发送广播数据报(SO_BROADCAST)。

  • 错误: 控制是否接收带外数据的错误指示(SO_OOBINLINE)。

  • 超时: 设置发送和接收的超时时间(SO_SNDTIMEO, SO_RCVTIMEO)。

  • 以及其他许多高级选项。

2. 函数原型

1
2
3
4
#include <sys/socket.h> // 必需

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

3. 功能

  • 设置选项: 为套接字 sockfd 设置由 level 和 optname 共同指定的选项。

  • 传递参数: 通过 optval 指针将选项的具体值传递给内核。这个值的类型和含义取决于具体的选项。

  • 指定长度: 通过 optlen 指定 optval 指向的数据的大小(以字节为单位)。

4. 参数

  • int sockfd: 这是一个已创建(通过 socket())的有效套接字文件描述符。

int level: 指定选项定义的协议层(level)。

  • SOL_SOCKET: 套接字层本身。这是最常用的级别,用于设置与具体网络协议(如 TCP/IP)无关的通用套接字选项。

  • IPPROTO_IP: IP 层。用于设置 IP 协议特有的选项(如 IP_TTL)。

  • IPPROTO_TCP: TCP 层。用于设置 TCP 协议特有的选项(如 TCP_NODELAY)。

  • IPPROTO_IPV6: IPv6 层。用于设置 IPv6 协议特有的选项。

int optname: 指定在 level 层要设置的具体选项名称。
例如,在 SOL_SOCKET 级别,常见的 optname 有:

  • SO_REUSEADDR: 允许重用本地地址。

  • SO_KEEPALIVE: 启用 keep-alive 机制。

  • SO_LINGER: 设置 linger 选项。

  • SO_BROADCAST: 允许发送广播数据报。

  • SO_SNDBUF: 设置发送缓冲区大小。

  • SO_RCVBUF: 设置接收缓冲区大小。

  • SO_SNDTIMEO: 设置发送超时。

  • SO_RCVTIMEO: 设置接收超时。

在 IPPROTO_TCP 级别,常见的 optname 有:

  • TCP_NODELAY: 禁用 Nagle 算法(发送小包时立即发送,不等待)。

const void *optval: 这是一个指向选项值的指针。

  • 选项值的类型取决于 optname。

  • 对于布尔型选项(如 SO_REUSEADDR),通常是一个指向 int 的指针,非 0 表示启用,0 表示禁用。

  • 对于整型选项(如 SO_SNDBUF),通常是一个指向 int 的指针,值为所需的大小。

  • 对于结构体选项(如 SO_LINGER),则是一个指向相应结构体的指针。

socklen_t optlen: 指定 optval 指向的数据的大小(以字节为单位)。

  • 例如,如果 optval 指向一个 int,则 optlen 应为 sizeof(int)。

  • 如果 optval 指向一个 struct linger,则 optlen 应为 sizeof(struct linger)。

5. 返回值

  • 成功时: 返回 0。套接字选项已成功设置。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 无效,EINVAL level 或 optname 无效或 optval/optlen 不匹配,ENOPROTOOPT 协议不支持该选项等)。

6. 相似函数,或关联函数

  • getsockopt: 用于获取套接字的当前选项值。与 setsockopt 相对应。

  • socket: 创建套接字,是 setsockopt 操作的对象。

  • bind / listen / connect: 其他套接字操作函数,通常与 setsockopt 结合使用来配置套接字。

7. 示例代码

示例 1:设置 SO_REUSEADDR 选项

这个例子演示了如何在服务器套接字上设置 SO_REUSEADDR 选项,这是服务器编程中的一个常见且重要的实践。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// setsockopt_reuseaddr.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8095
#define BACKLOG 10

int main() {
int server_fd;
struct sockaddr_in address;
int opt = 1; // 用于 SO_REUSEADDR 的值

// 1. 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
printf("Socket created successfully (fd: %d)\n", server_fd);

// --- 关键: 设置 SO_REUSEADDR 选项 ---
// 这允许服务器在重启时立即绑定到同一个地址,
// 即使旧的连接可能处于 TIME_WAIT 状态。
printf("Setting SO_REUSEADDR option...\n");
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR failed");
// 注意:即使失败,程序也可以继续,但这不是好习惯
// 最佳实践是处理错误并决定是否继续
close(server_fd);
exit(EXIT_FAILURE);
}
printf("SO_REUSEADDR option set successfully.\n");

// 2. 配置服务器地址结构
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

// 3. 绑定套接字
printf("Binding socket to port %d...\n", PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Socket bound successfully.\n");

// 4. 监听
if (listen(server_fd, BACKLOG) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is listening.\n");

printf("Server setup complete. Press Ctrl+C to exit.\n");
pause(); // 挂起等待信号

close(server_fd);
return 0;
}

代码解释:

创建 TCP 套接字。

关键步骤: 调用 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))。

  • server_fd: 要设置选项的套接字。

  • SOL_SOCKET: 选项所在的协议层(套接字层)。

  • SO_REUSEADDR: 要设置的选项名称。

  • &opt: 指向选项值的指针。这里 opt 是一个 int 变量,值为 1,表示启用该选项。

  • sizeof(opt): 选项值的大小。

如果调用成功,SO_REUSEADDR 选项就被设置为启用了。

继续进行 bind 和 listen。

设置 SO_REUSEADDR 的好处是,当服务器进程因某种原因(如崩溃或重启)终止后,它之前绑定的地址和端口可能会在内核中处于 TIME_WAIT 状态一段时间。如果没有设置 SO_REUSEADDR,立即重启服务器并尝试绑定同一个地址端口会失败(EADDRINUSE)。设置了这个选项后,就可以立即重用该地址。

示例 2:设置 SO_RCVBUF 和 SO_SNDBUF 选项

这个例子演示了如何调整套接字的接收和发送缓冲区大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// setsockopt_buffer.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8096
#define CUSTOM_BUFFER_SIZE 64 * 1024 // 64 KB

void print_buffer_sizes(int sock, const char* role) {
int rcv_buf_size, snd_buf_size;
socklen_t len = sizeof(rcv_buf_size);

if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, &len) == 0) {
printf("%s SO_RCVBUF: %d bytes\n", role, rcv_buf_size);
} else {
perror("getsockopt SO_RCVBUF failed");
}

len = sizeof(snd_buf_size); // Reset len
if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, &len) == 0) {
printf("%s SO_SNDBUF: %d bytes\n", role, snd_buf_size);
} else {
perror("getsockopt SO_SNDBUF failed");
}
}

int main() {
int server_fd, client_sock;
struct sockaddr_in address, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int opt = 1;

server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

// 设置 SO_REUSEADDR
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR failed");
close(server_fd);
exit(EXIT_FAILURE);
}

// --- 设置服务器套接字的缓冲区大小 ---
printf("--- Before setting buffer sizes ---\n");
print_buffer_sizes(server_fd, "Server (before)");

int rcv_buf_size = CUSTOM_BUFFER_SIZE;
int snd_buf_size = CUSTOM_BUFFER_SIZE;

printf("\nSetting custom buffer sizes to %d bytes...\n", CUSTOM_BUFFER_SIZE);
if (setsockopt(server_fd, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, sizeof(rcv_buf_size))) {
perror("setsockopt SO_RCVBUF failed");
// 注意:内核可能会调整这个值到一个合理的范围
}
if (setsockopt(server_fd, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, sizeof(snd_buf_size))) {
perror("setsockopt SO_SNDBUF failed");
}

printf("--- After setting buffer sizes ---\n");
print_buffer_sizes(server_fd, "Server (after)");

// 绑定和监听
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}

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

// --- 客户端连接 ---
client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock < 0) {
perror("client socket failed");
close(server_fd);
exit(EXIT_FAILURE);
}

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(client_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("client connect failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}

// --- 检查客户端套接字的缓冲区大小 ---
printf("\n--- Client socket buffer sizes ---\n");
print_buffer_sizes(client_sock, "Client");

// --- 服务器 accept 连接并检查其套接字 ---
int accepted_sock = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (accepted_sock < 0) {
perror("accept failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}

printf("\n--- Server accepted socket buffer sizes ---\n");
print_buffer_sizes(accepted_sock, "Server-Accepted");

// 简单通信后关闭
close(client_sock);
close(accepted_sock);
close(server_fd);

return 0;
}

代码解释:

定义了一个 print_buffer_sizes 函数,它使用 getsockopt 来获取并打印套接字的当前接收和发送缓冲区大小。

创建服务器套接字。

设置 SO_REUSEADDR。

关键: 在 bind 之前,调用 setsockopt 来设置服务器套接字的 SO_RCVBUF 和 SO_SNDBUF。

  • 传递一个 int 变量的指针和其大小。

打印设置前后的缓冲区大小。注意,内核可能会将请求的大小调整为一个内部支持的值。

继续设置服务器监听。

创建客户端套接字并连接。

打印客户端套接字的缓冲区大小。

服务器 accept 连接,得到一个新的已连接套接字。

打印这个新套接字的缓冲区大小。通常,accept 返回的套接字会继承监听套接字的一些属性,包括缓冲区大小。

示例 3:设置 TCP_NODELAY 选项

这个例子演示了如何在 TCP 套接字上设置 TCP_NODELAY 选项,以禁用 Nagle 算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// setsockopt_tcp_nodelay.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h> // For TCP_NODELAY
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8097

int main() {
int server_fd, client_sock;
struct sockaddr_in address, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int opt = 1;

server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR failed");
close(server_fd);
exit(EXIT_FAILURE);
}

memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}

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

// --- 客户端连接 ---
client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock < 0) {
perror("client socket failed");
close(server_fd);
exit(EXIT_FAILURE);
}

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(client_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("client connect failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}
printf("Client connected.\n");

// --- 服务器 accept 连接 ---
int accepted_sock = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (accepted_sock < 0) {
perror("accept failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}
printf("Server accepted connection.\n");

// --- 关键: 在已连接的套接字上设置 TCP_NODELAY ---
// 这会禁用 Nagle 算法,使得小的数据包能够立即发送,而不必等待 ACK 或积累更多数据。
// 这对于实时性要求高的应用(如游戏、交互式应用)很有用。
printf("\nSetting TCP_NODELAY on server's accepted socket...\n");
int flag = 1; // Enable TCP_NODELAY
if (setsockopt(accepted_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
perror("setsockopt TCP_NODELAY on server socket failed");
// 不是致命错误,可以继续
} else {
printf("TCP_NODELAY enabled on server's accepted socket (fd: %d).\n", accepted_sock);
}

printf("\nSetting TCP_NODELAY on client socket...\n");
if (setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
perror("setsockopt TCP_NODELAY on client socket failed");
} else {
printf("TCP_NODELAY enabled on client socket (fd: %d).\n", client_sock);
}

// ... 这里可以进行数据传输 ...

close(client_sock);
close(accepted_sock);
close(server_fd);

printf("Sockets closed.\n");
return 0;
}

代码解释:

设置标准的服务器和客户端连接。

服务器 accept 连接后,得到 accepted_sock。

关键: 在 accepted_sock 上调用 setsockopt(accepted_sock, IPPROTO_TCP, TCP_NODELAY, …)。

  • IPPROTO_TCP: 选项所在的协议层(TCP 层)。

  • TCP_NODELAY: 要设置的选项名称。

  • &flag (flag=1): 选项值,1 表示启用(禁用 Nagle 算法)。

  • sizeof(int): 选项值大小。

同样地,在客户端套接字 client_sock 上也设置 TCP_NODELAY。

启用 TCP_NODELAY 后,TCP 连接将立即发送小的数据包,而不会等待将多个小包合并成一个更大的包(Nagle 算法的默认行为)或等待前一个包的 ACK。这可以减少延迟,但可能会增加网络上的小包数量。

重要提示与注意事项:

调用时机: setsockopt 可以在 socket() 之后、bind()/connect()/listen() 之前或之后调用,具体取决于选项。有些选项(如 SO_REUSEADDR)必须在 bind 之前设置才有效。

level 和 optname 的匹配: 确保 level 和 optname 正确匹配。例如,TCP_NODELAY 必须在 IPPROTO_TCP 级别设置。

optval 和 optlen: 确保传递给 optval 的数据类型和大小与 optname 要求的完全一致。

内核调整: 对于某些选项(如缓冲区大小),内核可能会将你请求的值调整为一个它认为更合适的值。

错误处理: 始终检查返回值。虽然某些选项设置失败可能不会导致程序无法运行,但最好处理错误并了解原因。

常见用途: SO_REUSEADDR、TCP_NODELAY、SO_KEEPALIVE、SO_LINGER 是网络编程中非常常见的选项。

总结:

setsockopt 是一个功能强大的函数,允许程序员精细地调整套接字的行为。理解其参数(特别是 level 和 optname 的组合)以及各种常用选项的作用,对于编写高效、健壮的网络应用程序至关重要。它是网络编程中不可或缺的工具之一。

https://www.calcguide.tech/2025/08/26/linux开源软件路线图/

data-ad-format="auto" data-full-width-responsive="true">