Linux网络编程基础

API详情

一、核心 Socket API 详解

1.1 socket() – 创建套接字

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

参数

  • domain:协议族 / 地址族,常用:
    • AF_INET:IPv4
    • AF_INET6:IPv6
    • AF_UNIX/AF_LOCAL:本地 Unix 域套接字
  • type:套接字类型,常用:
    • SOCK_STREAM:TCP 流式套接字
    • SOCK_DGRAM:UDP 数据报套接字
  • protocol:通常设为 0,表示自动选择 type 对应的默认协议(TCP/UDP)。
    • 也可以显式指定 IPPROTO_TCPIPPROTO_UDP 等。

返回值

  • 成功:返回套接字文件描述符(非负整数)。
  • 失败:返回 -1,并设置 errno

典型用法

int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
    perror("socket");
    exit(EXIT_FAILURE);
}

1.2 bind() – 绑定地址和端口

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数

  • sockfdsocket() 返回的套接字描述符。
  • addr:指向地址结构的指针,通常使用 struct sockaddr_in(IPv4)或 struct sockaddr_in6(IPv6),强制转换为 struct sockaddr*
  • addrlen:地址结构长度,通常用 sizeof(struct sockaddr_in) 等。

典型用法(IPv4 服务器)

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0,本机所有 IP
servaddr.sin_port = htons(8080);
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
    perror("bind");
    close(listenfd);
    exit(EXIT_FAILURE);
}

说明

  • 服务器通常需要 bind() 固定端口;客户端一般不显式 bind,由系统自动分配临时端口。
  • INADDR_ANY 表示绑定本机所有 IP,多网卡场景常用。

1.3 listen() – 将套接字设为监听状态(TCP 服务器)

int listen(int sockfd, int backlog);

参数

  • sockfd:套接字描述符,一般先 socket() + bind()
  • backlog:内核为此套接字维护的两个队列之和的建议最大值:
    • 未完成连接队列(SYN_RCVD 状态)
    • 已完成连接队列(ESTABLISHED 状态)

返回值

  • 成功:0。
  • 失败:-1,设置 errno

说明

  • listen() 将套接字从「主动套接字」变为「被动套接字」,用于接受连接。
  • 实际可排队连接数受内核参数 /proc/sys/net/core/somaxconn 限制。

1.4 accept() – 接受新连接(TCP 服务器)

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数

  • sockfd:监听套接字(listen() 之后的那个)。
  • addr:用来返回客户端地址的结构体指针,可为 NULL
  • addrlen:值-结果参数,调用前是 addr 缓冲区长度,返回时为实际地址长度。

返回值

  • 成功:返回一个新的已连接套接字描述符,用于与该客户端通信。
  • 失败:返回 -1,设置 errno

典型用法

struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
if (connfd == -1) {
    perror("accept");
    // 视情况决定是否退出
}

要点

  • listenfd 是监听套接字,生命周期伴随整个服务器;connfd 是已连接套接字,每个客户端一个。
  • accept() 默认阻塞,直到有新连接到达。

1.5 connect() – 客户端建立连接(TCP)

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数

  • sockfd:客户端套接字(一般 socket() 得到,未 bind)。
  • addr:服务器地址结构。
  • addrlen:地址长度。

返回值

  • 成功:0(连接建立或超时/被拒绝)。
  • 失败:-1,设置 errno

典型用法

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("192.168.1.100");
servaddr.sin_port = htons(8080);
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
    perror("connect");
    close(sockfd);
    exit(EXIT_FAILURE);
}

1.6 close() – 关闭套接字

int close(int fd);

参数

  • fd:要关闭的文件描述符(包括套接字)。

返回值

  • 成功:0。
  • 失败:-1。

说明

  • 对于 TCP,close() 会触发四次挥手;但实际发送 FIN 等行为还受 SO_LINGER 等选项影响。
  • 套接字也是文件描述符,用完后必须关闭,避免 fd 耗尽。

1.7 shutdown() – 优雅关闭连接

int shutdown(int sockfd, int how);

参数

  • sockfd:套接字描述符。
  • how
    • SHUT_RD:关闭读端,不再接收数据。
    • SHUT_WR:关闭写端,不再发送数据,发送 FIN。
    • SHUT_RDWR:同时关闭读写。

返回值

  • 成功:0。
  • 失败:-1。

说明

  • shutdown() 可以只关闭一个方向,实现「半关闭」。
  • 即使引用计数不为 0(多进程/多线程共享),shutdown() 也会直接影响连接状态。

二、I/O 相关 API:read/write/recv/send/recvfrom/sendto

2.1 read() / write() – 通用文件 I/O

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

参数

  • fd:文件描述符(套接字也用这两个函数)。
  • buf:缓冲区。
  • count:要读写的字节数。

返回值

  • 成功:
    • read:返回实际读到的字节数,0 表示 EOF(对端关闭)。
    • write:返回实际写入的字节数。
  • 失败:返回 -1,设置 errno

说明

  • 对套接字而言,read/write 是最通用的 I/O 方式。
  • 对端正常关闭时,read 返回 0;异常则返回 -1。

2.2 recv() / send() – 套接字专用 I/O

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数

  • sockfd:套接字描述符。
  • buf:数据缓冲区。
  • len:缓冲区长度。
  • flags:标志位,常用:
    • MSG_DONTWAIT:非阻塞操作(相当于临时非阻塞)。
    • MSG_OOB:发送/接收带外数据。
    • MSG_PEEK:查看数据但不从缓冲区移除。
    • MSG_WAITALL:请求读取完整数据报或指定长度(阻塞直到满足或出错)。

返回值

  • 成功:返回实际读/写的字节数。
  • 失败:返回 -1,设置 errno
  • 对端正常关闭:recv 返回 0。

说明

  • send/recvwrite/read 多了一个 flags 参数,可以控制行为。
  • send 只是把数据从用户态拷贝到内核发送缓冲区,真正发送由协议栈完成。

2.3 recvfrom() / sendto() – 支持 UDP 的读写

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

参数

  • 前 4 个参数与 send/recv 相同。
  • dest_addr / src_addr:对端地址结构,用于 UDP 指定/获取对端地址。
  • addrlen:地址长度。

说明

  • 对于 UDP,通常使用 sendto/recvfrom 来指定或获取对方地址。
  • TCP 中也可以使用,但一般直接用 send/recv 即可。

2.4 recvmsg() / sendmsg() – 更复杂的 I/O

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

参数

  • msgstruct msghdr 结构,包含:
    • 多个缓冲区(msg_iov / msg_iovlen)。
    • 可选的控制信息(msg_control / msg_controllen)。
    • 对端地址信息。

说明

  • 适合需要「多缓冲区」或「辅助数据」的场景,例如:
    • 传递文件描述符(Unix 域套接字 + SCM_RIGHTS)。
    • 获取/设置 IP 层信息(如 IP_TTL、IP_TOS)。

三、I/O 多路复用:select / poll / epoll

3.1 select() – 传统多路复用

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

参数

  • nfds:集合中最大文件描述符 + 1。
  • readfds:监视可读的 fd 集合。
  • writefds:监视可写的 fd 集合。
  • exceptfds:监视异常的 fd 集合。
  • timeout:超时时间,NULL 表示永久阻塞。

辅助宏

FD_ZERO(fd_set *set);          // 清空集合
FD_SET(int fd, fd_set *set);   // 将 fd 加入集合
FD_CLR(int fd, fd_set *set);   // 将 fd 从集合中移除
FD_ISSET(int fd, fd_set *set); // 判断 fd 是否在集合中

返回值

  • 成功:返回就绪 fd 的总数(包括读写异常)。
  • 超时:返回 0。
  • 失败:返回 -1,设置 errno

缺点

  • 每次调用都要线性扫描整个 fd 集合。
  • 单个进程能够监视的 fd 数量受 FD_SETSIZE 限制(一般 1024)。

3.2 poll() – 更灵活的多路复用

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数

  • fdspollfd 数组:
    struct pollfd {
        int   fd;       // 文件描述符
        short events;   // 监视的事件(如 POLLIN | POLLOUT)
        short revents;  // 返回的事件(由内核填充)
    };
    
  • nfds:数组元素个数。
  • timeout:超时时间(毫秒),-1 表示永久阻塞。

返回值

  • 成功:就绪 fd 的数量。
  • 超时:0。
  • 失败:-1。

优点

  • 没有 FD_SETSIZE 的硬编码限制。
  • 接口比 select 更直观,易于扩展。

3.3 epoll – Linux 高性能多路复用

3.3.1 epoll_create() – 创建 epoll 实例
int epoll_create(int size);

参数

  • size:只是建议值,Linux 2.6.8 之后被忽略,只需大于 0 即可。

返回值

  • 成功:返回 epoll 实例的文件描述符。
  • 失败:-1。
3.3.2 epoll_ctl() – 注册/修改/删除事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数

  • epfdepoll_create 返回的 epoll fd。
  • op:操作类型:
    • EPOLL_CTL_ADD:添加 fd 到 epoll 实例。
    • EPOLL_CTL_MOD:修改已注册 fd 的事件。
    • EPOLL_CTL_DEL:删除 fd。
  • fd:要操作的文件描述符。
  • eventstruct epoll_event 指针:
    struct epoll_event {
        uint32_t     events;  /* Epoll events (EPOLLIN, EPOLLOUT, EPOLLET, ...) */
        epoll_data_t data;    /* User data variable */
    };
    typedef union epoll_data {
        void    *ptr;
        int      fd;
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
    

返回值

  • 成功:0。
  • 失败:-1。
3.3.3 epoll_wait() – 等待事件发生
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

参数

  • epfd:epoll 实例 fd。
  • events:数组,用来返回就绪事件。
  • maxevents:数组大小。
  • timeout:超时(毫秒),-1 表示阻塞。

返回值

  • 成功:就绪 fd 的数量。
  • 超时:0。
  • 失败:-1。

说明

  • epoll 使用事件驱动模型,只返回活跃的 fd,适合高并发场景。
  • 支持 LT(水平触发)和 ET(边缘触发)两种模式,ET 需配合非阻塞套接字。

四、地址转换与解析 API

4.1 inet_pton() / inet_ntop() – IP 地址二进制与文本转换

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数

  • af:地址族,AF_INETAF_INET6
  • src
    • inet_pton:点分十进制 IPv4 字符串(如 “192.168.1.1”)或 IPv6 文本形式。
    • inet_ntop:网络字节序的二进制 IP 地址。
  • dst
    • inet_pton:存放转换后的网络字节序地址(struct in_addr / struct in6_addr)。
    • inet_ntop:用于存放结果字符串的缓冲区。
  • size:缓冲区大小(inet_ntop)。

返回值

  • inet_pton
    • 成功:1。
    • 格式无效:0。
    • 失败:-1。
  • inet_ntop
    • 成功:返回 dst 指针。
    • 失败:返回 NULL

说明

  • 推荐用这两个函数代替旧的 inet_addrinet_atoninet_ntoa,它们支持 IPv4 和 IPv6。

4.2 getaddrinfo() / freeaddrinfo() – 现代地址解析

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);

参数

  • node:主机名或 IP 字符串(如 “www.example.com” 或 “192.168.1.1”)。
  • service:服务名或端口号字符串(如 “http” 或 “80”)。
  • hintsaddrinfo 结构,指定过滤条件:
    struct addrinfo {
        int              ai_flags;     // AI_PASSIVE, AI_CANONNAME 等
        int              ai_family;    // AF_INET, AF_INET6, AF_UNSPEC
        int              ai_socktype;  // SOCK_STREAM, SOCK_DGRAM
        int              ai_protocol;  // 0 或 IPPROTO_TCP/UDP
        size_t           ai_addrlen;   // 地址长度
        struct sockaddr *ai_addr;      // 地址结构
        char            *ai_canonname; // 正式主机名
        struct addrinfo *ai_next;      // 链表下一节点
    };
    
  • res:返回的链表头指针,每个节点包含一个可用地址。

返回值

  • 成功:0。
  • 失败:返回非零错误码,可用 gai_strerror() 转为错误信息。

说明

  • getaddrinfo 同时处理:
    • 主机名到 IP 地址的解析(DNS)。
    • 服务名到端口的映射。
    • 返回可直接用于 socket/bind/connect 的地址结构。
  • 支持 IPv4/IPv6 双栈编程,是现代网络编程推荐方式。

4.3 getnameinfo() – 地址到主机名/服务名的逆向解析

int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
                char *host, socklen_t hostlen,
                char *serv, socklen_t servlen,
                int flags);

参数

  • addr / addrlen:地址结构及长度。
  • host / hostlen:存放主机名缓冲区。
  • serv / servlen:存放服务名(端口号)缓冲区。
  • flags:如 NI_NUMERICHOSTNI_NUMERICSERV 等,控制是否返回数字形式。

说明

  • 相当于「反向 DNS + 端口解析」,是 gethostbyaddr / getservbyport 的现代替代。

五、套接字选项与辅助 API

5.1 setsockopt() / getsockopt() – 设置/获取套接字选项

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

参数

  • sockfd:套接字描述符。
  • level:选项级别:
    • SOL_SOCKET:套接字层选项。
    • IPPROTO_TCP:TCP 选项。
    • IPPROTO_IP:IP 选项。
    • IPPROTO_IPV6:IPv6 选项。
  • optname:选项名,如:
    • SO_REUSEADDR:地址重用。
    • SO_KEEPALIVE:保活探测。
    • SO_BROADCAST:广播。
    • SO_RCVBUF / SO_SNDBUF:收发缓冲区大小。
    • SO_RCVTIMEO / SO_SNDTIMEO:收发超时。
    • TCP_NODELAY:禁用 Nagle 算法。
  • optval:选项值缓冲区。
  • optlen:缓冲区长度。

典型用法

int reuse = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
int keepalive = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));

5.2 fcntl() – 设置非阻塞/文件描述符标志

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

常用命令

  • F_GETFL:获取文件状态标志。
  • F_SETFL:设置文件状态标志,常用:
    • O_NONBLOCK:非阻塞 I/O。

示例:设置套接字为非阻塞

int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

5.3 getsockname() / getpeername() – 获取本地/对端地址

int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数

  • sockfd:套接字描述符。
  • addr / addrlen:返回地址结构。
    说明
  • getsockname:返回本地协议地址(IP + 端口)。
  • getpeername:返回对端协议地址。

六、分门别类归纳表

下列部分是对上述内容的总结归纳

6.1 套接字创建与连接

类别API作用简述典型使用场景
创建套接字socket()创建套接字,返回文件描述符TCP/UDP 服务器/客户端第一步
绑定地址bind()绑定 IP 地址和端口到套接字服务器固定端口;客户端一般不调用
监听连接listen()将套接字设为监听状态,维护连接队列TCP 服务器在 bind 之后调用
接受连接accept()从监听队列取出一个已完成连接,返回新套接字TCP 服务器循环调用,为每个客户端服务
发起连接connect()客户端向服务器发起连接(TCP)TCP 客户端建立连接
关闭套接字close()关闭套接字,释放资源所有不再使用的套接字都需要关闭
优雅关闭shutdown()关闭读/写端,支持半关闭需要单独关闭读或写方向时使用

6.2 I/O 读写

类别API作用简述典型使用场景
通用 I/Oread()从文件描述符读取数据对套接字也适用,简单读取
通用 I/Owrite()向文件描述符写入数据对套接字也适用,简单发送
套接字recv()接收数据,支持 flagsTCP 常用,可带 MSG_PEEK 等
套接字send()发送数据,支持 flagsTCP 常用,可带 MSG_DONTWAIT 等
UDPrecvfrom()接收数据,同时获取对端地址UDP 服务器/客户端常用
UDPsendto()发送数据,指定目标地址UDP 发送数据到指定目标
高级 I/Orecvmsg()支持 msghdr 的复杂接收多缓冲区、控制信息、传递 fd 等
高级 I/Osendmsg()支持 msghdr 的复杂发送多缓冲区、控制信息、传递 fd 等

6.3 I/O 多路复用

类别API作用简述典型使用场景
传统select()多路复用,监视多个 fd 的读写异常简单服务器,兼容性要求高
传统poll()类似 select,但无 FD_SETSIZE 限制中等规模并发服务器
Linux 高性能epoll_create()创建 epoll 实例高并发服务器(如 Web、游戏服务器)
Linux 高性能epoll_ctl()添加/修改/删除 epoll 事件在主循环中维护关注的 fd
Linux 高性能epoll_wait()等待就绪事件事件驱动主循环

6.4 地址转换与解析

类别API作用简述典型使用场景
IP 转换inet_pton()文本 IP → 网络字节序二进制解析命令行或配置文件中的 IP 地址
IP 转换inet_ntop()网络字节序二进制 → 文本 IP打印日志、调试、显示客户端 IP
地址解析getaddrinfo()解析主机名 + 服务名为 addrinfo 链表现代 TCP/UDP 客户端/服务器初始化
地址解析freeaddrinfo()释放 getaddrinfo 返回的链表与 getaddrinfo 配对使用
逆向解析getnameinfo()地址结构 → 主机名 + 服务名日志中记录主机名而不仅是 IP

6.5 套接字选项与辅助 API

类别API作用简述典型使用场景
选项设置setsockopt()设置套接字选项(缓冲区、重用、保活等)端口重用、开启 KEEPALIVE、设置超时等
选项获取getsockopt()获取套接字选项当前值调试、查看默认缓冲区大小等
非阻塞fcntl()设置/获取文件描述符标志,常用 O_NONBLOCK将套接字设置为非阻塞模式,配合 epoll/ET
本地地址getsockname()获取套接字本地地址(IP + 端口)查看系统自动分配的临时端口等
对端地址getpeername()获取套接字对端地址已连接套接字需要记录客户端 IP/端口时

相关结构体详情

一、地址相关的结构体

1.1 通用地址结构 sockaddr

struct sockaddr {
    sa_family_t sa_family;  // 地址族:AF_INET、AF_INET6、AF_UNIX 等
    char        sa_data[14]; // 协议地址(具体含义由协议决定)
};
  • 作用:bind / connect / sendto / recvfrom 等函数的「通用地址参数」,实际使用时会根据 sa_family 转成具体的 sockaddr_in / sockaddr_in6 / sockaddr_un 等类型。

1.2 IPv4 套接字地址 sockaddr_in / in_addr

struct sockaddr_in {
    sa_family_t    sin_family;  // AF_INET
    in_port_t      sin_port;    // 端口,网络字节序
    struct in_addr sin_addr;    // IPv4 地址
    unsigned char  sin_zero[8]; // 填充,使大小与 sockaddr 一致
};
struct in_addr {
    in_addr_t s_addr; // 32bit IPv4 地址,网络字节序
};
  • 使用场景:IPv4 TCP/UDP 服务器/客户端 bind / connect 时设置本地/对端地址。
  • 注意:
    • sin_port 必须用 htons() 转为网络字节序。
    • sin_addr.s_addr 可设为 INADDR_ANY(0.0.0.0)表示本机任意地址。

1.3 IPv6 套接字地址 sockaddr_in6 / in6_addr

struct sockaddr_in6 {
    sa_family_t     sin6_family;   // AF_INET6
    in_port_t       sin6_port;     // 端口号,网络字节序
    uint32_t        sin6_flowinfo; // IPv6 流信息
    struct in6_addr sin6_addr;     // IPv6 地址
    uint32_t        sin6_scope_id; // 作用域 ID(link-local 时为接口索引)
};
struct in6_addr {
    uint8_t s6_addr[16]; // 128bit IPv6 地址
};
  • 使用场景:IPv6 套接字绑定地址、连接对端。

1.4 Unix 域套接字地址 sockaddr_un

struct sockaddr_un {
    sa_family_t sun_family;  // AF_UNIX / AF_LOCAL
    char        sun_path[108]; // 路径名(或抽象地址)
};
  • 使用场景:本地进程间通信(本地套接字)。sun_path 可以是:
    • 文件系统中的路径(bind 后会创建一个套接字文件)。
    • 抽象地址(Linux 特有,sun_path[0] == '\0')。

二、I/O 多路复用相关结构体

2.1 pollfd – poll 专用

struct pollfd {
    int   fd;       // 监视的文件描述符
    short events;   // 用户关心的事件(POLLIN、POLLOUT 等)
    short revents;  // 实际发生的事件,由内核填充
};
  • 使用场景:poll() 的参数,每个 fd 对应一个 pollfd

2.2 epoll_event / epoll_data – epoll 专用

struct epoll_event {
    uint32_t     events;  // 事件掩码:EPOLLIN、EPOLLOUT、EPOLLET 等
    epoll_data_t data;    // 用户数据,可存 fd 或指针
};
union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
};
typedef union epoll_data epoll_data_t;
  • 使用场景:epoll_ctl / epoll_waitdata 常用来保存「连接上下文」指针或 fd。

三、高级 I/O 相关结构体

3.1 iovec – 分散/聚集 I/O 向量

struct iovec {
    void  *iov_base; // 缓冲区起始地址
    size_t iov_len;  // 缓冲区长度
};
  • 使用场景:
    • readv / writev 一次操作多个缓冲区。
    • sendmsg / recvmsg 中通过 msg_iov 数组指定多段缓冲区。

3.2 msghdr / cmsghdr – sendmsg/recvmsg 消息头

struct msghdr {
    void         *msg_name;       // 可选的对端地址(未连接套接字)
    socklen_t     msg_namelen;    // 地址长度
    struct iovec *msg_iov;        // scatter/gather 缓冲区数组
    size_t        msg_iovlen;     // msg_iov 中元素个数
    void         *msg_control;    // 辅助数据(控制信息)缓冲区
    size_t        msg_controllen; // 辅助数据缓冲区长度
    int           msg_flags;      // 接收消息的标志(recvmsg 返回)
};
struct cmsghdr {
    socklen_t cmsg_len;   // 总长度(包含头部)
    int       cmsg_level; // 协议层(SOL_SOCKET / IPPROTO_IP / IPPROTO_IPV6)
    int       cmsg_type;  // 控制消息类型
    /* 后面是实际数据 unsigned char cmsg_data[]; */
};
  • 使用场景:
    • sendmsg / recvmsg 实现带外数据、传递文件描述符(Unix 域套接字)、IP 选项等高级功能。
    • 通过 CMSG_FIRSTHDR / CMSG_NXTHDR / CMSG_DATA 等宏访问控制信息。

四、名字解析相关结构体

4.1 addrinfo – getaddrinfo 返回的地址链表

struct addrinfo {
    int             ai_flags;      // AI_PASSIVE、AI_CANONNAME 等
    int             ai_family;     // AF_INET、AF_INET6、AF_UNSPEC
    int             ai_socktype;   // SOCK_STREAM、SOCK_DGRAM
    int             ai_protocol;   // IPPROTO_TCP、IPPROTO_UDP 或 0
    size_t          ai_addrlen;    // ai_addr 的长度
    struct sockaddr *ai_addr;      // 指向地址结构(sockaddr_in / sockaddr_in6)
    char            *ai_canonname; // 正式主机名(若 AI_CANONNAME)
    struct addrinfo *ai_next;      // 链表下一个节点
};
  • 使用场景:getaddrinfo() 的结果,用于替代 gethostbyname,实现协议无关的地址解析。

4.2 hostent – 传统主机地址信息(已废弃)

struct hostent {
    char  *h_name;       // 主机正式名称
    char **h_aliases;    // 别名列表(NULL 结尾)
    int    h_addrtype;   // 地址类型:AF_INET 或 AF_INET6
    int    h_length;     // 地址长度(字节)
    char **h_addr_list;  // 网络地址列表(网络字节序),NULL 结尾
};
#define h_addr h_addr_list[0] // 向后兼容宏
  • 使用场景:gethostbyname / gethostbyaddr 的返回值;现代代码建议用 getaddrinfo / getnameinfo

4.3 servent – 服务数据库信息

struct servent {
    char  *s_name;      // 服务的正式名称
    char **s_aliases;   // 别名列表(NULL 结尾)
    int    s_port;      // 端口号,网络字节序
    char  *s_proto;     // 协议名,例如 "tcp" / "udp"
};
  • 使用场景:getservbyname / getservbyport 查询 /etc/services 中的服务信息。

五、选项与时间相关结构体

5.1 linger – SO_LINGER 选项

struct linger {
    int l_onoff;  // 是否启用 LINGER:0 关闭,非 0 启用
    int l_linger; // 启用时,close() 等待的秒数
};
  • 使用场景:setsockopt 设置 SO_LINGER,控制 close 行为:
    • l_onoff=0:默认,close 立即返回,内核尽量优雅关闭。
    • l_onoff!=0 && l_linger=0close 立即返回,丢弃发送缓冲区中数据,发 RST 强制关闭。
    • l_onoff!=0 && l_linger>0close 最多等待 l_linger 秒,未发完数据则返回错误。

5.2 timeval – 时间间隔/时间戳

struct timeval {
    time_t      tv_sec;  // 秒
    suseconds_t tv_usec; // 微秒
};
  • 使用场景:
    • gettimeofday 获取当前时间。
    • setsockopt 设置 SO_RCVTIMEO / SNDTIMEO 作为超时参数。

六、网络接口相关结构体(ioctl 操作网卡)

6.1 ifreq – 单个网络接口配置

struct ifreq {
    char ifr_name[IFNAMSIZ]; // 接口名,例如 "eth0"
    union {
        struct sockaddr ifru_addr;      // 地址
        struct sockaddr ifru_dstaddr;   // 点对点目的地址
        struct sockaddr ifru_broadaddr; // 广播地址
        short           ifru_flags;     // 标志(IFF_UP 等)
        int             ifru_metric;    // 路由度量
        caddr_t         ifru_data;      // 其他数据
    } ifr_ifru;
};
// 常用宏
#define ifr_addr     ifr_ifru.ifru_addr
#define ifr_dstaddr  ifr_ifru.ifru_dstaddr
#define ifr_broadaddr ifr_ifru.ifru_broadaddr
#define ifr_flags    ifr_ifru.ifru_flags
#define ifr_metric   ifr_ifru.ifru_metric
#define ifr_data     ifr_ifru.ifru_data
  • 使用场景:ioctl(SIOCGIFADDR) / SIOCGIFFLAGS 等获取/设置 IP、掩码、标志、MTU 等。

6.2 ifconf – 所有网络接口列表

struct ifconf {
    int  ifc_len;  // 缓冲区长度
    union {
        char          *ifcu_buf;  // 缓冲区地址
        struct ifreq  *ifcu_req;  // ifreq 数组
    } ifc_ifcu;
};
#define ifc_buf  ifc_ifcu.ifcu_buf
#define ifc_req  ifc_ifcu.ifcu_req
  • 使用场景:ioctl(SIOCGIFCONF) 一次性获取所有网卡信息,结果是一个 ifreq 数组。

七、分类归纳表

下面按用途把结构体分成几类:

类别结构体主要用途简述典型使用场景
通用地址sockaddr通用地址结构,传给很多 socket APIbind/connect/sendto 等的地址参数
IPv4 地址sockaddr_inIPv4 套接字地址(IP + 端口)服务器/客户端 IPv4 地址设置
IPv4 地址(内嵌)in_addr32bit IPv4 地址sockaddr_in.sin_addr 的类型
IPv6 地址sockaddr_in6IPv6 套接字地址(IP + 端口 + flow/scope)IPv6 服务器/客户端地址绑定
IPv6 地址(内嵌)in6_addr128bit IPv6 地址sockaddr_in6.sin6_addr 的类型
Unix 域地址sockaddr_unUnix 域套接字地址(路径名或抽象地址)本地进程间通信(本地套接字)
多路复用pollfdpoll 监视的文件描述符和事件poll() 调用
多路复用epoll_eventepoll 事件结构和用户数据epoll_ctl/epoll_wait
分散/聚集 I/Oiovec一段缓冲区的地址和长度readv/writevsendmsg/recvmsg
高级消息 I/Omsghdrsendmsg/recvmsg 的消息头(地址、向量、控制信息)带外数据、传递文件描述符等
控制消息(辅助数据)cmsghdr控制信息头部,用于 msg_control传递 fd、IP_TTL 等控制信息
名字解析(现代)addrinfogetaddrinfo 返回的地址链表协议无关的 DNS 和服务查询
名字解析(旧)hostent主机名/地址信息(gethostbyname 等)已废弃,建议用 addrinfo
服务查询servent服务名/端口映射(getservbyname 等)查询 /etc/services
套接字选项lingerSO_LINGER 选项,控制 close 行为设置优雅/强制关闭连接
时间/超时timeval秒 + 微秒时间表示setsockopt 超时、gettimeofday
网卡接口ifreq单个网卡配置/状态(IP、掩码、标志等)ioctl 获取/设置网卡参数
网卡接口ifconf所有网卡接口信息列表ioctl(SIOCGIFCONF) 获取所有网卡

补充

典型的代码流程

TCP服务端

socket() → bind() → listen() → accept() → recv()/send() → close()

TCP客户端

socket() → connect() → send()/recv() → close()

UDP服务端

socket() → bind() → recvfrom()/sendto() → close()

UDP客户端

socket() → sendto()/recvfrom() → close()

相关头文件

一、核心 socket 头文件

头文件主要用途 / 说明代表性函数 / 类型
<sys/socket.h>核心 socket API:创建/连接/收发等socket(), bind(), listen(), accept(), connect(), recv(), send(), recvmsg(), sendmsg(), getsockopt(), setsockopt(), socklen_t
<sys/types.h>各种基本类型(很多历史代码会包含它)size_t, ssize_t, socklen_t 等类型定义;老式 man 里常见 #include <sys/types.h>
<netinet/in.h>IPv4/IPv6 地址结构、常量struct sockaddr_in, struct sockaddr_in6, struct in_addr, INADDR_ANY, INADDR_LOOPBACK, IPPROTO_TCP, IPPROTO_UDP
<arpa/inet.h>网络/主机字节序转换、IP 文本与二进制转换htonl(), htons(), ntohl(), ntohs(), inet_pton(), inet_ntop(), inet_addr(), inet_ntoa()
<sys/un.h>Unix 域套接字地址结构struct sockaddr_un,用于本地进程间通信

二、I/O 多路复用相关头文件

头文件主要用途 / 说明代表性函数 / 类型
<sys/select.h>select() / pselect()fd_set 操作select(), pselect(), fd_set, FD_ZERO, FD_SET, FD_CLR, FD_ISSET
<poll.h>poll() / ppoll()struct pollfd, poll()
<sys/epoll.h>Linux 特有的 epoll 系列epoll_create(), epoll_create1(), epoll_ctl(), epoll_wait(), struct epoll_event

三、名字解析 / 服务查询头文件

头文件主要用途 / 说明代表性函数 / 类型
<netdb.h>主机名、服务名解析(现代/老式都有)getaddrinfo(), freeaddrinfo(), gai_strerror(), getnameinfo();老式:gethostbyname(), gethostbyaddr(), getservbyname(), struct hostent, struct servent
<sys/socket.h>getaddrinfo() 一起使用(地址结构、socklen_t 等)配合 <netdb.h> 一起使用
<sys/types.h>配合 <netdb.h> 的老式代码老式写法常见组合:<sys/types.h> + <sys/socket.h> + <netdb.h>

四、通用 I/O、错误处理与信号头文件

这些虽然不是“网络专用”,但在网络编程里几乎必用。

头文件主要用途 / 说明代表性函数 / 类型
<unistd.h>POSIX 标准 I/O:文件描述符读写、关闭、管道等read(), write(), close(), pipe(), dup2()
<fcntl.h>文件控制:设置非阻塞、文件锁等fcntl(), open(),常用来设置 O_NONBLOCK
<errno.h>错误码 errno 及各种 E* 常量errno, EINTR, EAGAIN, EWOULDBLOCK, ECONNRESET, EPIPE
<signal.h>信号处理(尤其是 SIGPIPESIGCHLD 等)signal(), sigaction(), SIGPIPE, SIG_IGN

五、其他常用但易忽略的头文件

头文件主要用途 / 说明代表性函数 / 类型
<string.h>内存 / 字符串操作memset(), memcpy(), strlen(), strcmp() 等;经常用来初始化 sockaddr_in 等结构体
<stdio.h>标准 I/O / 错误输出perror(), fprintf(stderr, ...);网络层通常用 unistd.hread/write,但调试日志经常用 stdio
<stdlib.h>通用工具:内存分配、进程控制malloc(), free(), exit(), atoi()
<time.h>时间相关(超时、日志时间戳)time(), localtime(), strftime()struct timeval<sys/time.h><sys/select.h> 中定义
<sys/time.h>struct timeval 等时间结构常用于 select() 超时、gettimeofday()(有些实现也放在 <sys/select.h> 中)
<sys/ioctl.h>设备控制,包括网卡配置等ioctl(),配合 SIOCGIFADDRSIOCGIFFLAGS 等操作网卡
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值