1. 函数介绍
setsockopt (Set Socket Options) 是一个 Linux 系统调用,用于在已创建的套接字上设置各种选项(options)或参数(parameters)。这些选项控制着套接字的行为和底层协议的特定方面。
你可以把 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. 函数原型
#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来指示具体的错误原因(例如EBADFsockfd无效,EINVALlevel或optname无效或optval/optlen不匹配,ENOPROTOOPT协议不支持该选项等)。 
6. 相似函数,或关联函数
getsockopt: 用于获取套接字的当前选项值。与setsockopt相对应。socket: 创建套接字,是setsockopt操作的对象。bind/listen/connect: 其他套接字操作函数,通常与setsockopt结合使用来配置套接字。
7. 示例代码
示例 1:设置 SO_REUSEADDR 选项
这个例子演示了如何在服务器套接字上设置 SO_REUSEADDR 选项,这是服务器编程中的一个常见且重要的实践。
// 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 选项
这个例子演示了如何调整套接字的接收和发送缓冲区大小。
// 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 算法。
// 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 的组合)以及各种常用选项的作用,对于编写高效、健壮的网络应用程序至关重要。它是网络编程中不可或缺的工具之一。