Socket Opt

Tecy 发布于 15 天前 46 次阅读


Linux 网络编程中用于配置套接字的选项,它允许开发者精细控制套接字的行为。

头文件 #include <sys/socket.h>

setsockopt()

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

一般在 bind()/listen()/connect() 前设置选项。

sockfd

套接字描述符。

level

选项协议层。

协议层说明适用场景
SOL_SOCKET通用套接字选项所有套接字类型
IPPROTO_TCPTCP 协议选项TCP 套接字
IPPROTO_IPIPv4 协议选项IPv4 套接字
IPPROTO_IPV6IPv6 协议选项IPv6 套接字

optname

具体选项名称。

SOL_SOCKET

选项数据类型用途示例代码
SO_REUSEADDRint允许重用本地地址(解决 "Address already in use" 问题)int reuse = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
SO_REUSEPORTint允许多进程绑定相同端口(Linux 3.9+)int reuse = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
SO_KEEPALIVEint启用 TCP 心跳检测(防连接超时)int keepalive = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
SO_RCVBUFint设置接收缓冲区大小(单位:字节)int size = 1024 * 1024;
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
SO_SNDBUFint设置发送缓冲区大小int size = 2048 * 1024;
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
SO_RCVTIMEOstruct timeval设置接收超时时间struct timeval tv = {5, 0}; // 5秒
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
SO_LINGERstruct linger控制 close() 行为(强制发送剩余数据)struct linger ling = {1, 10}; // 等待10秒
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));

IPPROTO_TCP

选项数据类型用途示例代码
TCP_NODELAYint禁用 Nagle 算法(减少延迟,适合实时应用)int nodelay = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
TCP_KEEPIDLEint首次发送心跳包前的空闲时间(秒)int idle = 60;
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
TCP_KEEPINTVLint心跳包发送间隔(秒)int interval = 10;
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
TCP_KEEPCNTint断开连接前的最大心跳失败次数int cnt = 5;
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));

IPPROTO_IP

选项数据类型用途示例代码
IP_TTLint设置数据包生存时间(TTL)int ttl = 64;
setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
IP_MULTICAST_IFstruct in_addr设置多播输出接口struct in_addr addr;
inet_pton(AF_INET, "192.168.1.2", &addr);
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr));

opval

指向选项值的指针(类型取决于选项)。

oplen

optval 缓冲区的长度。

return

返回值: 成功返回 0, 失败返回 -1 并设置 errno

错误码原因
EBADFsockfd 不是有效的文件描述符
ENOTSOCKsockfd 是文件描述符,但不是套接字类型
EINVAL以下情况之一:
1. 无效的 level 或 optname
2. optval 值非法
3. 选项在连接后设置(如 TCP 连接后设置 SO_REUSEADDR
ENOPROTOOPT协议层不支持该选项(如 UDP 套接字设置 TCP_NODELAY
EFAULToptval 指针指向不可访问的内存区域
ENOBUFS系统资源不足(罕见)

getsockopt()

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

获取套接字选项当前值,与 setsockopt() 相对应。它允许开发者查询套接字的当前配置状态。

sockfd

套接字描述符。

level

选项协议层。

optname

具体选项名称。

opval

输出参数:指向存储选项值的缓冲区。

oplen

输入输出参数:输入时为缓冲区大小,输出时为实际值长度。

return

返回值: 成功返回 0, 失败返回 -1 并设置 errno

特殊选项

SO_ERROR

获取待处理错误。

使用场景

  • 在非阻塞 connect 后检查连接状态。
  • 在 I/O 多路复用中检测套接字错误。
int sock_error;
socklen_t len = sizeof(sock_error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sock_error, &len) == 0) {
    if (sock_error != 0) {
        printf("Socket error: %s\n", strerror(sock_error));
    } else {
        printf("No pending socket errors\n");
    }
}

SO_TYPE

获取套接字类型。

int sock_type;
socklen_t len = sizeof(sock_type);
if (getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &sock_type, &len) == 0) {
    switch (sock_type) {
        case SOCK_STREAM: 
            printf("TCP socket\n"); break;
        case SOCK_DGRAM: 
            printf("UDP socket\n"); break;
        case SOCK_RAW: 
            printf("Raw socket\n"); break;
        default:
            printf("Unknown socket type: %d\n", sock_type);
    }
}

SO_ACCEPTCONN

检查是否处于监听状态。

int is_listening;
socklen_t len = sizeof(is_listening);
if (getsockopt(sockfd, SOL_SOCKET, SO_ACCEPTCONN, &is_listening, &len) == 0) {
    printf("Socket is %s\n", is_listening ? "listening" : "not listening");
}

TCP_INFO

获取 TCP 连接信息。

#include <netinet/tcp.h>

struct tcp_info info;
socklen_t len = sizeof(info);
if (getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &info, &len) == 0) {
    printf("TCP state: %s\n", tcp_state_name(info.tcpi_state));
    printf("Retransmits: %u\n", info.tcpi_retransmits);
    printf("RTT: %u us\n", info.tcpi_rtt);
    printf("RTO: %u us\n", info.tcpi_rto);
}