好的,我们继续按照您的列表顺序,介绍下一个函数。在 setsockopt
之后,根据您提供的列表,下一个函数是 getsockopt
。
1. 函数介绍 链接到标题
getsockopt
(Get Socket Options) 是一个 Linux 系统调用,与 setsockopt
相对应。它的功能是查询一个已创建的套接字的当前选项(options)或参数(parameters)的值。
你可以把 getsockopt
想象成查看收音机或电视机当前的设置:
- 你有一个设备(套接字)。
- 你想知道它当前的某个设置(如音量、音质、电源状态)是什么。
getsockopt
就是让你读取这些当前设置的值。
这对于以下场景非常有用:
- 调试: 检查套接字是否被正确配置,例如
SO_REUSEADDR
是否已启用。 - 验证: 在设置某个选项后,使用
getsockopt
来验证设置是否成功(因为内核可能会调整某些值)。 - 动态调整: 根据当前的缓冲区大小或其他参数来动态调整程序行为。
- 获取信息: 获取内核为套接字分配的资源信息,如实际的缓冲区大小。
2. 函数原型 链接到标题
#include <sys/socket.h> // 必需
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
3. 功能 链接到标题
- 获取选项: 为套接字
sockfd
获取由level
和optname
共同指定的选项的当前值。 - 填充缓冲区: 将获取到的选项值填充到调用者提供的
optval
指向的缓冲区中。 - 返回长度: 通过
optlen
参数返回实际存储在optval
中的数据的大小。
4. 参数 链接到标题
int sockfd
: 这是一个已创建(通过socket()
)的有效套接字文件描述符。int level
: 指定选项定义的协议层(level)。这与setsockopt
中的level
参数含义相同。SOL_SOCKET
: 套接字层本身。IPPROTO_IP
: IP 层。IPPROTO_TCP
: TCP 层。IPPROTO_IPV6
: IPv6 层。
int optname
: 指定在level
层要获取的具体选项名称。这与setsockopt
中的optname
参数含义相同。- 例如:
SO_REUSEADDR
,SO_KEEPALIVE
,SO_SNDBUF
,SO_RCVBUF
,TCP_NODELAY
等。
- 例如:
void *optval
: 这是一个指向缓冲区的指针,用于接收获取到的选项值。- 调用者需要提供一个足够大的缓冲区来存储选项值。
- 选项值的类型取决于
optname
。
socklen_t *optlen
: 这是一个指向socklen_t
类型变量的指针。- 输入: 在调用
getsockopt
时,这个变量必须被初始化为optval
指向的缓冲区的大小(以字节为单位)。 - 输出:
getsockopt
返回时,这个变量会被更新为实际存储在optval
中的选项值的大小。
- 输入: 在调用
5. 返回值 链接到标题
- 成功时: 返回 0。同时,
optval
指向的缓冲区被成功填充,*optlen
被更新为实际值大小。 - 失败时: 返回 -1,并设置全局变量
errno
来指示具体的错误原因(例如EBADF
sockfd
无效,EINVAL
level
或optname
无效或optval
/optlen
指针无效,ENOPROTOOPT
协议不支持该选项等)。
6. 相似函数,或关联函数 链接到标题
setsockopt
: 用于设置套接字选项。与getsockopt
相对应。socket
: 创建套接字,是getsockopt
操作的对象。
7. 示例代码 链接到标题
示例 1:获取 SO_REUSEADDR
选项的值
链接到标题
这个例子演示了如何使用 getsockopt
来检查 SO_REUSEADDR
选项是否已启用。
// getsockopt_reuseaddr.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 8098
void check_reuseaddr(int sock, const char* context) {
int reuse_flag = -1; // 初始化为一个无效值
socklen_t len = sizeof(reuse_flag);
printf("--- Checking SO_REUSEADDR for %s ---\n", context);
if (getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse_flag, &len) == 0) {
printf(" SO_REUSEADDR value: %d ", reuse_flag);
if (reuse_flag == 0) {
printf("(Disabled)\n");
} else {
printf("(Enabled)\n");
}
} else {
perror(" getsockopt SO_REUSEADDR failed");
}
printf("------------------------------------\n");
}
int main() {
int server_fd1, server_fd2;
struct sockaddr_in address;
int opt = 1;
// --- 创建第一个套接字 (不设置 SO_REUSEADDR) ---
server_fd1 = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd1 == 0) {
perror("socket 1 failed");
exit(EXIT_FAILURE);
}
printf("Socket 1 created (fd: %d)\n", server_fd1);
check_reuseaddr(server_fd1, "Socket 1 (before any setsockopt)");
// --- 创建第二个套接字 (设置 SO_REUSEADDR) ---
server_fd2 = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd2 == 0) {
perror("socket 2 failed");
close(server_fd1);
exit(EXIT_FAILURE);
}
printf("Socket 2 created (fd: %d)\n", server_fd2);
if (setsockopt(server_fd2, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR on socket 2 failed");
close(server_fd1);
close(server_fd2);
exit(EXIT_FAILURE);
}
printf("SO_REUSEADDR set on socket 2.\n");
check_reuseaddr(server_fd2, "Socket 2 (after setsockopt)");
// --- 配置地址并绑定两个套接字 ---
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
printf("\nBinding Socket 1 to port %d...\n", PORT);
if (bind(server_fd1, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind socket 1 failed");
// 这可能会因为端口被占用而失败,这取决于系统状态
} else {
printf("Socket 1 bound successfully.\n");
}
printf("Binding Socket 2 to port %d...\n", PORT);
if (bind(server_fd2, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind socket 2 failed");
// 如果 socket 1 占用了端口且未设置 REUSEADDR,这很可能会失败
// 但如果 socket 1 已关闭且处于 TIME_WAIT,而 socket 2 设置了 REUSEADDR,则可能成功
} else {
printf("Socket 2 bound successfully.\n");
}
close(server_fd1);
close(server_fd2);
return 0;
}
代码解释:
- 定义了一个
check_reuseaddr
函数,它封装了getsockopt
调用。- 它接受套接字文件描述符和一个上下文字符串用于打印。
- 初始化一个
int reuse_flag
变量和socklen_t len
。 - 调用
getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse_flag, &len)
。 - 检查返回值,如果成功则打印
reuse_flag
的值(0 表示禁用,非 0 表示启用)。
- 在
main
函数中:- 创建
server_fd1
,不设置任何选项,然后调用check_reuseaddr
。通常默认值是 0(禁用)。 - 创建
server_fd2
,调用setsockopt
设置SO_REUSEADDR
为 1(启用),然后调用check_reuseaddr
验证设置成功。 - 尝试将两个套接字都绑定到同一个端口,以观察
SO_REUSEADDR
的效果(虽然这个例子的重点是getsockopt
)。
- 创建
示例 2:获取套接字缓冲区大小 链接到标题
这个例子演示了如何使用 getsockopt
来获取套接字的发送和接收缓冲区的当前大小。
// getsockopt_buffer.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 8099
void print_buffer_sizes(int sock, const char* role) {
int rcv_buf_size = -1, snd_buf_size = -1;
socklen_t len = sizeof(rcv_buf_size); // len for int
printf("--- Buffer sizes for %s (fd: %d) ---\n", role, sock);
// 获取接收缓冲区大小
if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, &len) == 0) {
printf(" SO_RCVBUF: %d bytes\n", rcv_buf_size);
} else {
perror(" getsockopt SO_RCVBUF failed");
}
// 获取发送缓冲区大小
len = sizeof(snd_buf_size); // Reset len for the next call
if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, &len) == 0) {
printf(" SO_SNDBUF: %d bytes\n", snd_buf_size);
} else {
perror(" getsockopt SO_SNDBUF failed");
}
printf("-----------------------------------\n");
}
int main() {
int server_fd, client_sock;
struct sockaddr_in address, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
printf("Server socket created (fd: %d)\n", server_fd);
// 1. 打印初始缓冲区大小
print_buffer_sizes(server_fd, "Server (initial)");
// 2. 尝试设置一个自定义的缓冲区大小
int custom_rcv_buf = 64 * 1024; // 64 KB
int custom_snd_buf = 32 * 1024; // 32 KB
printf("\nAttempting to set custom buffer sizes...\n");
printf(" Requesting SO_RCVBUF: %d bytes\n", custom_rcv_buf);
printf(" Requesting SO_SNDBUF: %d bytes\n", custom_snd_buf);
if (setsockopt(server_fd, SOL_SOCKET, SO_RCVBUF, &custom_rcv_buf, sizeof(custom_rcv_buf))) {
perror("setsockopt SO_RCVBUF failed");
}
if (setsockopt(server_fd, SOL_SOCKET, SO_SNDBUF, &custom_snd_buf, sizeof(custom_snd_buf))) {
perror("setsockopt SO_SNDBUF failed");
}
// 3. 再次打印缓冲区大小,查看内核实际设置的值
print_buffer_sizes(server_fd, "Server (after setsockopt)");
// --- 设置服务器监听 ---
int opt = 1;
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("\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);
}
printf("Client socket created (fd: %d)\n", client_sock);
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");
print_buffer_sizes(client_sock, "Client (after connect)");
// --- 服务器 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 (fd: %d).\n", accepted_sock);
print_buffer_sizes(accepted_sock, "Server-Accepted (after accept)");
// 简单通信后关闭
close(client_sock);
close(accepted_sock);
close(server_fd);
printf("Sockets closed.\n");
return 0;
}
代码解释:
- 定义了一个
print_buffer_sizes
函数,它使用getsockopt
来获取并打印套接字的SO_RCVBUF
和SO_SNDBUF
的值。- 注意在两次
getsockopt
调用之间重置了len
变量。
- 注意在两次
- 在
main
函数中:- 创建服务器套接字。
- 第一次调用
print_buffer_sizes
打印初始缓冲区大小。 - 调用
setsockopt
尝试设置自定义的缓冲区大小。 - 第二次调用
print_buffer_sizes
打印设置后的缓冲区大小。注意,打印出的值可能与请求的值不同,因为内核会进行调整。 - 继续设置服务器监听。
- 创建客户端套接字并连接。
- 第三次调用
print_buffer_sizes
打印客户端套接字的缓冲区大小。 - 服务器
accept
连接,得到accepted_sock
。 - 第四次调用
print_buffer_sizes
打印accept
返回的套接字的缓冲区大小。
示例 3:获取 TCP_NODELAY
选项的值
链接到标题
这个例子演示了如何使用 getsockopt
来检查 TCP 套接字的 TCP_NODELAY
选项(Nagle 算法是否被禁用)。
// getsockopt_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 8100
void check_tcp_nodelay(int sock, const char* context) {
int nodelay_flag = -1;
socklen_t len = sizeof(nodelay_flag);
printf("--- Checking TCP_NODELAY for %s ---\n", context);
if (getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &nodelay_flag, &len) == 0) {
printf(" TCP_NODELAY value: %d ", nodelay_flag);
if (nodelay_flag == 0) {
printf("(Nagle's algorithm Enabled)\n");
} else {
printf("(Nagle's algorithm Disabled)\n");
}
} else {
perror(" getsockopt TCP_NODELAY failed");
// 如果套接字不是 TCP 类型,这会失败 (ENOPROTOOPT)
}
printf("----------------------------------\n");
}
int main() {
int server_fd, client_sock;
struct sockaddr_in address, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0); // TCP socket
if (server_fd == 0) {
perror("TCP socket failed");
exit(EXIT_FAILURE);
}
printf("TCP Server socket created (fd: %d)\n", server_fd);
// 1. 检查默认的 TCP_NODELAY 状态
check_tcp_nodelay(server_fd, "TCP Server (default)");
// 2. 启用 TCP_NODELAY
int flag = 1;
printf("\nEnabling TCP_NODELAY on server socket...\n");
if (setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) < 0) {
perror("setsockopt TCP_NODELAY failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 再次检查 TCP_NODELAY 状态
check_tcp_nodelay(server_fd, "TCP Server (after enabling)");
// --- 设置服务器监听 ---
int opt = 1;
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("\nTCP Server listening on port %d\n", PORT);
// --- 客户端连接 (TCP) ---
client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock < 0) {
perror("TCP client socket failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("TCP Client socket created (fd: %d)\n", client_sock);
// 检查客户端默认的 TCP_NODELAY 状态
check_tcp_nodelay(client_sock, "TCP Client (default)");
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("TCP client connect failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}
printf("TCP Client connected.\n");
// 检查客户端连接后的 TCP_NODELAY 状态
check_tcp_nodelay(client_sock, "TCP Client (after connect)");
// --- 服务器 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("TCP Server accepted connection (fd: %d).\n", accepted_sock);
// 检查服务器 accept 套接字的 TCP_NODELAY 状态
check_tcp_nodelay(accepted_sock, "TCP Server-Accepted (after accept)");
// --- 对比:创建一个 UDP 套接字并尝试获取 TCP_NODELAY ---
printf("\n--- Testing TCP_NODELAY on a UDP socket ---\n");
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0); // UDP socket
if (udp_sock < 0) {
perror("UDP socket failed");
} else {
printf("UDP socket created (fd: %d)\n", udp_sock);
check_tcp_nodelay(udp_sock, "UDP Socket");
// 这个 check_tcp_nodelay 调用应该会失败,因为 UDP 不支持 TCP_NODELAY
close(udp_sock);
}
// 简单通信后关闭
close(client_sock);
close(accepted_sock);
close(server_fd);
printf("Sockets closed.\n");
return 0;
}
代码解释:
- 定义了一个
check_tcp_nodelay
函数,它使用getsockopt
来获取IPPROTO_TCP
层的TCP_NODELAY
选项的值。nodelay_flag
为 0 表示 Nagle 算法启用(默认),为 1 表示禁用。
- 在
main
函数中:- 创建一个 TCP 服务器套接字。
- 第一次调用
check_tcp_nodelay
检查默认状态(通常是启用 Nagle 算法)。 - 调用
setsockopt
启用TCP_NODELAY
(flag = 1
)。 - 第二次调用
check_tcp_nodelay
验证设置成功(值应为 1)。 - 继续设置服务器监听。
- 创建 TCP 客户端套接字。
- 第三次调用
check_tcp_nodelay
检查客户端默认状态。 - 客户端连接服务器。
- 第四次调用
check_tcp_nodelay
检查客户端连接后的状态。 - 服务器
accept
连接。 - 第五次调用
check_tcp_nodelay
检查accept
返回的套接字状态。 - 额外演示: 创建一个 UDP 套接字,并在其上调用
check_tcp_nodelay
。这应该会失败(ENOPROTOOPT
),因为TCP_NODELAY
是 TCP 特有的选项。
重要提示与注意事项: 链接到标题
- 与
setsockopt
对应:getsockopt
和setsockopt
是一对操作,分别用于读取和设置套接字选项。 optlen
的输入输出: 在调用前,必须将*optlen
初始化为目标缓冲区的大小。调用后,它会被更新为实际写入optval
的数据大小。- 缓冲区大小: 确保传递给
optval
的缓冲区足够大,以容纳所查询选项的值。例如,查询SO_RCVBUF
需要一个int
大小的缓冲区,而查询某些更复杂的选项可能需要一个结构体大小的缓冲区。 - 内核调整:
setsockopt
设置的某些值(如缓冲区大小)可能被内核调整,使用getsockopt
可以获取实际生效的值。 - 错误处理: 始终检查返回值。常见的错误包括
ENOPROTOOPT
(协议不支持该选项,如在 UDP 套接字上查询TCP_NODELAY
)。 - 协议层匹配: 确保
level
和optname
正确匹配。在错误的level
上查询选项会失败。
总结:
getsockopt
是一个用于查询套接字当前配置和状态的重要函数。它与 setsockopt
一起,为程序员提供了全面控制和检查套接字行为的能力。理解其参数和使用方法对于调试、验证和优化网络应用程序至关重要。