好的,我们继续按照您的列表顺序,介绍下一个函数。在 setsockopt 之后,根据您提供的列表,下一个函数是 getsockopt


1. 函数介绍 Link to heading

getsockopt (Get Socket Options) 是一个 Linux 系统调用,与 setsockopt 相对应。它的功能是查询一个已创建的套接字的当前选项(options)或参数(parameters)的值。

你可以把 getsockopt 想象成查看收音机或电视机当前的设置

  • 你有一个设备(套接字)。
  • 你想知道它当前的某个设置(如音量、音质、电源状态)是什么。
  • getsockopt 就是让你读取这些当前设置的值。

这对于以下场景非常有用:

  1. 调试: 检查套接字是否被正确配置,例如 SO_REUSEADDR 是否已启用。
  2. 验证: 在设置某个选项后,使用 getsockopt 来验证设置是否成功(因为内核可能会调整某些值)。
  3. 动态调整: 根据当前的缓冲区大小或其他参数来动态调整程序行为。
  4. 获取信息: 获取内核为套接字分配的资源信息,如实际的缓冲区大小。

2. 函数原型 Link to heading

#include <sys/socket.h> // 必需

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

3. 功能 Link to heading

  • 获取选项: 为套接字 sockfd 获取由 leveloptname 共同指定的选项的当前值。
  • 填充缓冲区: 将获取到的选项值填充到调用者提供的 optval 指向的缓冲区中。
  • 返回长度: 通过 optlen 参数返回实际存储在 optval 中的数据的大小。

4. 参数 Link to heading

  • 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. 返回值 Link to heading

  • 成功时: 返回 0。同时,optval 指向的缓冲区被成功填充,*optlen 被更新为实际值大小。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 无效,EINVAL leveloptname 无效或 optval/optlen 指针无效,ENOPROTOOPT 协议不支持该选项等)。

6. 相似函数,或关联函数 Link to heading

  • setsockopt: 用于设置套接字选项。与 getsockopt 相对应。
  • socket: 创建套接字,是 getsockopt 操作的对象。

7. 示例代码 Link to heading

示例 1:获取 SO_REUSEADDR 选项的值 Link to heading

这个例子演示了如何使用 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;
}

代码解释:

  1. 定义了一个 check_reuseaddr 函数,它封装了 getsockopt 调用。
    • 它接受套接字文件描述符和一个上下文字符串用于打印。
    • 初始化一个 int reuse_flag 变量和 socklen_t len
    • 调用 getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse_flag, &len)
    • 检查返回值,如果成功则打印 reuse_flag 的值(0 表示禁用,非 0 表示启用)。
  2. main 函数中:
    • 创建 server_fd1,不设置任何选项,然后调用 check_reuseaddr。通常默认值是 0(禁用)。
    • 创建 server_fd2,调用 setsockopt 设置 SO_REUSEADDR 为 1(启用),然后调用 check_reuseaddr 验证设置成功。
    • 尝试将两个套接字都绑定到同一个端口,以观察 SO_REUSEADDR 的效果(虽然这个例子的重点是 getsockopt)。

示例 2:获取套接字缓冲区大小 Link to heading

这个例子演示了如何使用 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;
}

代码解释:

  1. 定义了一个 print_buffer_sizes 函数,它使用 getsockopt 来获取并打印套接字的 SO_RCVBUFSO_SNDBUF 的值。
    • 注意在两次 getsockopt 调用之间重置了 len 变量。
  2. main 函数中:
    • 创建服务器套接字。
    • 第一次调用 print_buffer_sizes 打印初始缓冲区大小。
    • 调用 setsockopt 尝试设置自定义的缓冲区大小。
    • 第二次调用 print_buffer_sizes 打印设置后的缓冲区大小。注意,打印出的值可能与请求的值不同,因为内核会进行调整。
    • 继续设置服务器监听。
    • 创建客户端套接字并连接。
    • 第三次调用 print_buffer_sizes 打印客户端套接字的缓冲区大小。
    • 服务器 accept 连接,得到 accepted_sock
    • 第四次调用 print_buffer_sizes 打印 accept 返回的套接字的缓冲区大小。

示例 3:获取 TCP_NODELAY 选项的值 Link to heading

这个例子演示了如何使用 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;
}

代码解释:

  1. 定义了一个 check_tcp_nodelay 函数,它使用 getsockopt 来获取 IPPROTO_TCP 层的 TCP_NODELAY 选项的值。
    • nodelay_flag 为 0 表示 Nagle 算法启用(默认),为 1 表示禁用。
  2. main 函数中:
    • 创建一个 TCP 服务器套接字。
    • 第一次调用 check_tcp_nodelay 检查默认状态(通常是启用 Nagle 算法)。
    • 调用 setsockopt 启用 TCP_NODELAYflag = 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 特有的选项。

重要提示与注意事项: Link to heading

  1. setsockopt 对应: getsockoptsetsockopt 是一对操作,分别用于读取和设置套接字选项。
  2. optlen 的输入输出: 在调用前,必须*optlen 初始化为目标缓冲区的大小。调用后,它会被更新为实际写入 optval 的数据大小。
  3. 缓冲区大小: 确保传递给 optval 的缓冲区足够大,以容纳所查询选项的值。例如,查询 SO_RCVBUF 需要一个 int 大小的缓冲区,而查询某些更复杂的选项可能需要一个结构体大小的缓冲区。
  4. 内核调整: setsockopt 设置的某些值(如缓冲区大小)可能被内核调整,使用 getsockopt 可以获取实际生效的值。
  5. 错误处理: 始终检查返回值。常见的错误包括 ENOPROTOOPT(协议不支持该选项,如在 UDP 套接字上查询 TCP_NODELAY)。
  6. 协议层匹配: 确保 leveloptname 正确匹配。在错误的 level 上查询选项会失败。

总结:

getsockopt 是一个用于查询套接字当前配置和状态的重要函数。它与 setsockopt 一起,为程序员提供了全面控制和检查套接字行为的能力。理解其参数和使用方法对于调试、验证和优化网络应用程序至关重要。