Linux网络编程核心API速查手册

Linux网络编程核心API速查手册

文档规范:符合Linux系统标准,重点知识点加粗标注,适配学习、复习、面试速查、代码参考全场景


一、字节序与IP地址转换API

核心作用:解决主机字节序与网络字节序(固定大端序)的兼容性问题,以及IP地址「可读字符串 ↔ 网络序二进制格式」的互转,是所有网络通信的前置基础

1. 主机-网络字节序转换函数

标准头文件#include <arpa/inet.h>(兼容#include <netinet/in.h>

标准函数原型 核心功能 关键注意点
uint32_t htonl(uint32_t hostlong); 32位无符号整数 主机字节序 → 网络字节序 转换 专用于IPv4地址、32位网络标识等长整型数据,适配应用层与传输层的交互
uint32_t ntohl(uint32_t netlong); 32位无符号整数 网络字节序 → 主机字节序 转换 htonl为双向互转函数,入参为网络序32位数据
uint16_t htons(uint16_t hostshort); 16位无符号整数 主机字节序 → 网络字节序 转换 专用于TCP/UDP端口号,所有端口号入参必须经此函数转换
uint16_t ntohs(uint16_t netshort); 16位无符号整数 网络字节序 → 主机字节序 转换 htons为双向互转函数,用于从网络包中解析端口号

规范说明:采用stdint.h固定宽度整数类型,规避unsigned long在32/64位系统的宽度差异问题,符合现代Linux编程标准

使用样例

#include <iostream>
#include <cstdint>
#include <arpa/inet.h>

int main() {
    // 端口号转换(16位)
    uint16_t host_port = 8080;
    uint16_t net_port = htons(host_port);
    std::cout << "主机序端口:" << host_port << " → 网络序端口:" << net_port << std::endl;
    std::cout << "网络序端口还原:" << net_port << " → 主机序端口:" << ntohs(net_port) << std::endl;

    // IPv4地址转换(32位,模拟手动构造IP 192.168.1.100)
    uint32_t host_ip = (192 << 24) | (168 << 16) | (1 << 8) | 100;
    uint32_t net_ip = htonl(host_ip);
    std::cout << "主机序IP:0x" << std::hex << host_ip << " → 网络序IP:0x" << net_ip << std::dec << std::endl;
    std::cout << "网络序IP还原:0x" << std::hex << net_ip << " → 主机序IP:0x" << ntohl(net_ip) << std::dec << std::endl;

    return 0;
}

2. IP地址格式转换函数

标准头文件#include <arpa/inet.h>

兼容IPv4/IPv6双栈,是无缓冲区溢出风险的标准转换接口,替代老旧的inet_addr/inet_ntoa

(1)字符串IP → 网络序二进制IP

int inet_pton(int af, const char *src, void *dst);

  • 核心功能:将点分十进制IPv4/冒分十六进制IPv6可读字符串,转换为网络字节序的二进制IP地址
  • 参数详解
    • af:地址族,固定为AF_INET(IPv4)或AF_INET6(IPv6)
    • src:输入,可读的IP字符串常量
    • dst:输出,存储转换后的二进制IP地址(IPv4传入struct in_addr*,IPv6传入struct in6_addr*
  • 返回值
    • 成功:返回1,转换有效
    • 格式无效:返回0,输入字符串不是合法IP
    • 失败:返回-1,设置errno标识错误类型
  • 易错点:转换后的地址已是网络字节序,无需再经htonl二次转换

(2)网络序二进制IP → 字符串IP

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

  • 核心功能:将网络字节序的二进制IP地址,转换为人类可读的IP格式字符串
  • 参数详解
    • af:地址族,固定为AF_INET(IPv4)或AF_INET6(IPv6)
    • src:输入,二进制IP地址(IPv4传入const struct in_addr*,IPv6传入const struct in6_addr*
    • dst:输出,存储转换后的字符串缓冲区
    • size:输入,缓冲区的最大长度,IPv4建议用宏INET_ADDRSTRLEN,IPv6建议用INET6_ADDRSTRLEN
  • 返回值:成功返回指向dst的指针,失败返回NULL并设置errno
  • 易错点:必须提前分配足够的缓冲区,避免缓冲区溢出

使用样例

#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <errno.h>

int main() {
    // ===== IPv4 转换 =====
    const char* ipv4_str = "192.168.1.100";
    struct in_addr ipv4_bin;
    char ipv4_buf[INET_ADDRSTRLEN];

    // 字符串 → 二进制
    int ret = inet_pton(AF_INET, ipv4_str, &ipv4_bin);
    if (ret == 1) {
        std::cout << "IPv4字符串[" << ipv4_str << "]转换为二进制:0x" << std::hex << ipv4_bin.s_addr << std::dec << std::endl;
    } else if (ret == 0) {
        std::cerr << "IPv4格式错误:" << ipv4_str << std::endl;
        return -1;
    } else {
        std::cerr << "转换失败:" << strerror(errno) << std::endl;
        return -1;
    }

    // 二进制 → 字符串
    const char* res = inet_ntop(AF_INET, &ipv4_bin, ipv4_buf, INET_ADDRSTRLEN);
    if (res) {
        std::cout << "二进制IP还原为字符串:" << ipv4_buf << std::endl;
    } else {
        std::cerr << "还原失败:" << strerror(errno) << std::endl;
        return -1;
    }

    // ===== IPv6 转换 =====
    const char* ipv6_str = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
    struct in6_addr ipv6_bin;
    char ipv6_buf[INET6_ADDRSTRLEN];

    ret = inet_pton(AF_INET6, ipv6_str, &ipv6_bin);
    if (ret == 1) {
        std::cout << "\nIPv6字符串[" << ipv6_str << "]转换为二进制成功" << std::endl;
    } else {
        std::cerr << "IPv6转换失败:" << strerror(errno) << std::endl;
        return -1;
    }

    res = inet_ntop(AF_INET6, &ipv6_bin, ipv6_buf, INET6_ADDRSTRLEN);
    if (res) {
        std::cout << "IPv6二进制还原为字符串:" << ipv6_buf << std::endl;
    } else {
        std::cerr << "IPv6还原失败:" << strerror(errno) << std::endl;
        return -1;
    }

    return 0;
}

二、套接字地址结构体

核心作用:存储通信双方的协议族、IP地址、端口号等核心信息,是所有socket API的核心入参;Linux采用「通用抽象结构体 + 协议族专用结构体」的设计,实现多协议兼容

标准头文件#include <sys/socket.h>(通用结构体)、#include <netinet/in.h>(IP协议族结构体)、#include <sys/un.h>(Unix域结构体)

1. 通用套接字地址结构体

struct sockaddr {
    sa_family_t sa_family;  // 地址族,标识协议类型
    char        sa_data[14]; // 填充的地址数据,兼容所有协议族
};
  • 核心作用:作为所有socket API的统一入参类型,使用时需将专用结构体强制转换为此类型,实现多协议兼容

2. IPv4专用地址结构体(最常用)

// IPv4地址结构体
struct in_addr {
    uint32_t s_addr; // 存储网络字节序的二进制IPv4地址
};

// IPv4套接字完整结构体
struct sockaddr_in {
    sa_family_t    sin_family;  // 地址族,固定为AF_INET
    in_port_t      sin_port;    // 16位端口号,**必须使用网络字节序**
    struct in_addr sin_addr;    // 32位IPv4地址结构体
    unsigned char  sin_zero[8]; // 填充字节,与struct sockaddr长度对齐
};
  • 高频使用宏INADDR_ANY,代表本机所有网卡的IP地址,服务端bind时常用,无需绑定固定IP

使用样例

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    // 初始化服务端地址结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr)); // 清空结构体

    server_addr.sin_family = AF_INET; // 指定IPv4协议族
    server_addr.sin_port = htons(8080); // 端口号转换为网络序
    // 方式1:绑定本机所有网卡(服务端常用)
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    // 方式2:绑定固定IP(需先转换为网络序)
    // inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr);

    std::cout << "地址族:" << server_addr.sin_family << std::endl;
    std::cout << "网络序端口:" << server_addr.sin_port << std::endl;
    std::cout << "主机序端口:" << ntohs(server_addr.sin_port) << std::endl;

    // 转换二进制IP为字符串
    char ip_buf[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &server_addr.sin_addr, ip_buf, INET_ADDRSTRLEN);
    std::cout << "绑定的IP地址:" << ip_buf << std::endl;

    return 0;
}

3. 其他协议族专用结构体

  • IPv6专用:struct sockaddr_in6,适配128位IPv6地址
  • Unix域本地通信专用:struct sockaddr_un,用于本机进程间高速通信

三、套接字全生命周期管理API

核心作用:构成TCP/UDP网络编程的核心流程骨架,明确各API的调用时序、适用角色、适用协议

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

1. 全场景通用基础API

(1)创建套接字

int socket(int domain, int type, int protocol);

  • 核心功能:创建一个socket文件描述符,是所有网络操作的起点,在内核中分配对应的socket资源
  • 参数详解
    • domain:协议族/地址族,常用值:AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(Unix域)
    • type:套接字类型,常用值:SOCK_STREAM(流式套接字,对应TCP)、SOCK_DGRAM(数据报套接字,对应UDP)
    • protocol:协议编号,通常填0,自动匹配domaintype对应的默认协议
  • 返回值:成功返回非负的socket文件描述符,失败返回-1并设置errno

(2)关闭套接字

int close(int fd);

  • 核心功能:关闭socket文件描述符,释放内核对应的socket资源,终止连接
  • 关键注意点
    • 多进程/多线程场景下,socket有引用计数,仅当引用计数为0时才会真正释放连接
    • TCP场景下,调用后会触发四次挥手流程,若需立即终止连接,需配合套接字选项使用

(3)精细化半关闭连接

int shutdown(int sockfd, int how);

  • 核心功能:单向关闭socket的读/写通道,实现TCP半关闭,弥补close引用计数的局限
  • 参数详解how指定关闭方式,常用值:
    • SHUT_RD:关闭读通道,后续无法接收数据,缓冲区未读数据全部丢弃
    • SHUT_WR:关闭写通道,触发TCP FIN包,后续无法发送数据,缓冲区未发数据会继续发送完成
    • SHUT_RDWR:同时关闭读写通道,等价于close(无引用计数限制)
  • 返回值:成功返回0,失败返回-1并设置errno

2. TCP服务端专属流程API

(1)绑定地址与端口(命名套接字)

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

  • 核心功能:将固定的IP地址+端口号绑定到socket,TCP服务端必须调用,客户端可省略(内核自动分配临时端口)
  • 参数详解
    • sockfdsocket()创建的文件描述符
    • addr:传入填充好的协议族专用地址结构体,需强制转换为struct sockaddr*类型
    • addrlen:传入地址结构体的长度(如sizeof(struct sockaddr_in)
  • 返回值:成功返回0,失败返回-1并设置errno
  • 高频易错点:端口号必须经htons转换为网络字节序,否则会出现端口绑定异常

(2)开启监听模式

int listen(int sockfd, int backlog);

  • 核心功能:将主动套接字转为被动监听套接字,仅TCP服务端使用,内核为其维护TCP连接队列
  • 参数详解
    • sockfd:已调用bind()的socket文件描述符
    • backlog:TCP全连接队列(已完成三次握手)的最大长度上限,Linux系统默认值通常为128,实际生效值受系统net.core.somaxconn限制
  • 返回值:成功返回0,失败返回-1并设置errno
  • 关键注意点:必须在bind之后、accept之前调用,UDP套接字无需调用

(3)接受客户端连接

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

  • 核心功能:从监听套接字的全连接队列中,取出一个已完成三次握手的TCP连接,返回专属的已连接套接字(后续与该客户端的所有数据交互均使用此fd),仅TCP服务端使用
  • 参数详解
    • sockfd:已调用listen()的监听套接字文件描述符
    • addr:输出参数,存储客户端的地址信息
    • addrlen值-结果参数,传入时需初始化为addr结构体的长度,调用后返回实际写入的地址长度
  • 返回值:成功返回非负的已连接套接字fd,失败返回-1并设置errno
  • 高频易错点
    • 监听套接字仅用于接收连接,全程不会关闭;已连接套接字对应单个客户端,通信结束后需关闭
    • addrlen必须提前初始化,否则会出现地址解析异常

3. TCP客户端专属流程API

发起TCP连接

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

  • 核心功能:TCP客户端向指定服务端发起三次握手,建立TCP连接;UDP场景下可调用,用于预绑定对端地址
  • 参数详解
    • sockfdsocket()创建的未连接套接字文件描述符
    • addr:传入填充好的服务端地址结构体,需强制转换为struct sockaddr*类型
    • addrlen:传入地址结构体的长度
  • 返回值:成功返回0,失败返回-1并设置errno
  • UDP场景适配:UDP调用connect不会建立真实连接,仅会在内核中记录对端地址,后续可直接用send/recv传输数据,提升效率并限定通信对端

使用样例1

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 8080
#define BACKLOG 10
#define BUF_SIZE 1024

int main() {
    int listen_fd, conn_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buf[BUF_SIZE];
    ssize_t recv_len;

    // 1. 创建监听套接字(TCP)
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1) {
        std::cerr << "socket create failed: " << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
    }
    std::cout << "监听套接字创建成功,fd:" << listen_fd << std::endl;

    // 设置端口复用(解决TIME_WAIT占用问题)
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        std::cerr << "setsockopt failed: " << strerror(errno) << std::endl;
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // 2. 初始化服务端地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡

    // 3. 绑定地址和端口
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        std::cerr << "bind failed: " << strerror(errno) << std::endl;
        close(listen_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "绑定端口 " << PORT << " 成功" << std::endl;

    // 4. 开启监听
    if (listen(listen_fd, BACKLOG) == -1) {
        std::cerr << "listen failed: " << strerror(errno) << std::endl;
        close(listen_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "开始监听,等待客户端连接..." << std::endl;

    // 5. 接受客户端连接(阻塞)
    conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
    if (conn_fd == -1) {
        std::cerr << "accept failed: " << strerror(errno) << std::endl;
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // 打印客户端信息
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
    std::cout << "客户端[" << client_ip << ":" << ntohs(client_addr.sin_port) << "]已连接,conn_fd:" << conn_fd << std::endl;

    // 6. 接收客户端数据
    recv_len = recv(conn_fd, buf, BUF_SIZE - 1, 0);
    if (recv_len == -1) {
        std::cerr << "recv failed: " << strerror(errno) << std::endl;
        close(conn_fd);
        close(listen_fd);
        exit(EXIT_FAILURE);
    } else if (recv_len == 0) {
        std::cout << "客户端主动关闭连接" << std::endl;
        close(conn_fd);
        close(listen_fd);
        exit(EXIT_SUCCESS);
    }
    buf[recv_len] = '\0'; // 字符串结尾
    std::cout << "收到客户端数据:" << buf << std::endl;

    // 7. 回复客户端
    const char* resp = "Hello, Client! I received your message.";
    ssize_t send_len = send(conn_fd, resp, strlen(resp), 0);
    if (send_len == -1) {
        std::cerr << "send failed: " << strerror(errno) << std::endl;
    } else {
        std::cout << "发送回复成功,字节数:" << send_len << std::endl;
    }

    // 8. 关闭连接
    shutdown(conn_fd, SHUT_RDWR); // 双向关闭
    close(conn_fd);
    close(listen_fd);
    std::cout << "连接已关闭" << std::endl;

    return 0;
}

使用样例2

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUF_SIZE 1024

int main() {
    int sock_fd;
    struct sockaddr_in server_addr;
    char buf[BUF_SIZE];
    ssize_t send_len, recv_len;

    // 1. 创建客户端套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        std::cerr << "socket create failed: " << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
    }
    std::cout << "客户端套接字创建成功,fd:" << sock_fd << std::endl;

    // 2. 初始化服务端地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    // 转换服务端IP为二进制
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) != 1) {
        std::cerr << "inet_pton failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 连接服务端
    if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        std::cerr << "connect failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "成功连接到服务端 " << SERVER_IP << ":" << SERVER_PORT << std::endl;

    // 4. 发送数据到服务端
    const char* msg = "Hello, Server! This is client.";
    send_len = send(sock_fd, msg, strlen(msg), 0);
    if (send_len == -1) {
        std::cerr << "send failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "发送数据成功,字节数:" << send_len << std::endl;

    // 5. 接收服务端回复
    recv_len = recv(sock_fd, buf, BUF_SIZE - 1, 0);
    if (recv_len == -1) {
        std::cerr << "recv failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    } else if (recv_len == 0) {
        std::cout << "服务端关闭连接" << std::endl;
        close(sock_fd);
        exit(EXIT_SUCCESS);
    }
    buf[recv_len] = '\0';
    std::cout << "收到服务端回复:" << buf << std::endl;

    // 6. 关闭套接字
    close(sock_fd);
    std::cout << "客户端连接关闭" << std::endl;

    return 0;
}

四、数据传输:读写API

核心作用:根据TCP(面向连接、可靠字节流)和UDP(无连接、不可靠数据报)的传输特性,提供对应的读写接口,覆盖基础场景到高级通用场景

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

1. TCP流式传输读写API(面向连接)

仅适用于已建立连接的TCP套接字(accept返回的服务端fd、connect成功后的客户端fd)

(1)TCP数据发送

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

  • 核心功能:向已连接的TCP对端发送数据,支持精细化传输控制
  • 参数详解
    • sockfd:已连接的TCP套接字fd
    • buf:待发送数据的缓冲区地址
    • len:待发送数据的字节长度
    • flags:传输控制标志,常用值见下表,无特殊需求填0
  • 返回值:成功返回实际发送的字节数,失败返回-1并设置errno
  • 关键注意点:返回值大于0不代表对端已收到数据,仅代表数据已成功写入内核发送缓冲区

(2)TCP数据接收

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  • 核心功能:从已连接的TCP对端接收数据,支持精细化接收控制
  • 参数详解
    • sockfd:已连接的TCP套接字fd
    • buf:存储接收数据的缓冲区地址
    • len:缓冲区的最大可接收字节数
    • flags:接收控制标志,常用值见下表,无特殊需求填0
  • 返回值
    • 成功:返回实际接收的字节数
    • 对端关闭连接:返回0
    • 失败:返回-1并设置errno
  • 补充说明:TCP是流式协议,recv返回的字节数可能小于len,需循环读取直至收到完整数据

(3)send/recv 常用flags标志位

标志位 核心作用 适用函数
MSG_OOB 传输/接收TCP紧急带外数据 send/recv
MSG_PEEK 预览缓冲区数据,不从内核缓冲区移除,下次recv仍可读取 recv
MSG_WAITALL 阻塞等待,直到缓冲区填满len字节才返回 recv
MSG_NOSIGNAL 发送时对端关闭连接,不触发SIGPIPE信号,仅返回错误 send

补充:TCP套接字也可使用通用文件读写接口read()/write(),等价于flags=0recv()/send(),无额外控制能力

使用样例

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>

// 循环发送数据,确保全部发送完成
ssize_t send_all(int sockfd, const void* buf, size_t len) {
    size_t total_sent = 0;
    const char* p = static_cast<const char*>(buf);

    while (total_sent < len) {
        ssize_t sent = send(sockfd, p + total_sent, len - total_sent, 0);
        if (sent == -1) {
            return -1; // 发送失败
        }
        total_sent += sent;
    }
    return total_sent;
}

// 循环接收指定长度数据
ssize_t recv_all(int sockfd, void* buf, size_t len) {
    size_t total_recv = 0;
    char* p = static_cast<char*>(buf);

    while (total_recv < len) {
        ssize_t recv_len = recv(sockfd, p + total_recv, len - total_recv, MSG_WAITALL);
        if (recv_len == -1) {
            return -1; // 接收失败
        } else if (recv_len == 0) {
            return total_recv; // 对端关闭
        }
        total_recv += recv_len;
    }
    return total_recv;
}

int main() {
    // (需结合前面的TCP客户端/服务端框架使用)
    int sock_fd; // 已连接的TCP套接字fd
    const char* msg = "这是一段长数据,用于测试TCP流式传输的循环收发问题,确保数据完整传输";
    char buf[1024] = {0};

    // 发送完整数据
    ssize_t send_len = send_all(sock_fd, msg, strlen(msg));
    if (send_len == -1) {
        std::cerr << "send_all failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        return -1;
    }
    std::cout << "成功发送 " << send_len << " 字节" << std::endl;

    // 接收指定长度数据
    ssize_t recv_len = recv_all(sock_fd, buf, strlen(msg));
    if (recv_len == -1) {
        std::cerr << "recv_all failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        return -1;
    }
    std::cout << "成功接收 " << recv_len << " 字节:" << buf << std::endl;

    return 0;
}

2. UDP数据报传输读写API(无连接)

适用于无连接的UDP套接字,无需建立连接,每次传输需指定对端地址

(1)UDP数据发送

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

  • 核心功能:向指定的UDP对端发送数据报
  • 参数详解
    • 前4个参数与send完全一致
    • dest_addr:传入目标对端的地址结构体
    • addrlen:传入地址结构体的长度
  • 返回值:成功返回实际发送的字节数,失败返回-1并设置errno

(2)UDP数据接收

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

  • 核心功能:接收UDP数据报,同时获取发送端的地址信息
  • 参数详解
    • 前4个参数与recv完全一致
    • src_addr:输出参数,存储发送端的地址信息
    • addrlen值-结果参数,传入时需初始化为src_addr结构体的长度,调用后返回实际地址长度
  • 返回值:成功返回实际接收的字节数,失败返回-1并设置errno
  • 补充说明:UDP是数据报协议,recvfrom一次返回一个完整的数据报,不会出现半包

补充:已调用connect预绑定对端地址的UDP套接字,可直接使用send()/recv()/read()/write(),无需每次指定地址

使用样例1

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 8080
#define BUF_SIZE 1024

int main() {
    int sock_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buf[BUF_SIZE];
    ssize_t recv_len, send_len;

    // 1. 创建UDP套接字
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd == -1) {
        std::cerr << "socket create failed: " << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
    }

    // 2. 初始化服务端地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 3. 绑定端口(UDP服务端需绑定,客户端可选)
    if (bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        std::cerr << "bind failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "UDP服务端绑定端口 " << PORT << " 成功,等待数据..." << std::endl;

    // 4. 接收客户端数据
    recv_len = recvfrom(sock_fd, buf, BUF_SIZE - 1, 0, 
                        (struct sockaddr*)&client_addr, &client_len);
    if (recv_len == -1) {
        std::cerr << "recvfrom failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    buf[recv_len] = '\0';

    // 打印客户端信息和数据
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
    std::cout << "收到客户端[" << client_ip << ":" << ntohs(client_addr.sin_port) << "]数据:" << buf << std::endl;

    // 5. 回复客户端
    const char* resp = "UDP Server: I received your message!";
    send_len = sendto(sock_fd, resp, strlen(resp), 0, 
                      (struct sockaddr*)&client_addr, client_len);
    if (send_len == -1) {
        std::cerr << "sendto failed: " << strerror(errno) << std::endl;
    } else {
        std::cout << "发送回复成功,字节数:" << send_len << std::endl;
    }

    // 6. 关闭套接字
    close(sock_fd);
    return 0;
}

使用样例2

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUF_SIZE 1024

int main() {
    int sock_fd;
    struct sockaddr_in server_addr;
    socklen_t server_len = sizeof(server_addr);
    char buf[BUF_SIZE];
    ssize_t send_len, recv_len;

    // 1. 创建UDP套接字
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd == -1) {
        std::cerr << "socket create failed: " << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
    }

    // 2. 初始化服务端地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) != 1) {
        std::cerr << "inet_pton failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 发送数据到服务端
    const char* msg = "UDP Client: Hello Server!";
    send_len = sendto(sock_fd, msg, strlen(msg), 0, 
                      (struct sockaddr*)&server_addr, server_len);
    if (send_len == -1) {
        std::cerr << "sendto failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "发送数据成功,字节数:" << send_len << std::endl;

    // 4. 接收服务端回复
    recv_len = recvfrom(sock_fd, buf, BUF_SIZE - 1, 0, 
                        (struct sockaddr*)&server_addr, &server_len);
    if (recv_len == -1) {
        std::cerr << "recvfrom failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    buf[recv_len] = '\0';
    std::cout << "收到服务端回复:" << buf << std::endl;

    // 5. 关闭套接字
    close(sock_fd);
    return 0;
}

3. 通用全能型I/O API

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

  • 核心功能:Linux网络编程最通用的I/O函数,兼容TCP/UDP全场景,支持分散/聚集I/O、传输辅助数据(如进程间传递文件描述符)、携带控制信息,功能最全面
  • 使用样例
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080

int main() {
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd == -1) {
        std::cerr << "socket failed: " << strerror(errno) << std::endl;
        return -1;
    }

    // 初始化服务端地址
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);

    // 分散I/O:多个缓冲区数据合并发送
    char buf1[] = "Hello, ";
    char buf2[] = "UDP Server!";
    struct iovec iov[2];
    iov[0].iov_base = buf1;
    iov[0].iov_len = strlen(buf1);
    iov[1].iov_base = buf2;
    iov[1].iov_len = strlen(buf2);

    // 构造msghdr结构体
    struct msghdr msg;
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = &server_addr;
    msg.msg_namelen = sizeof(server_addr);
    msg.msg_iov = iov;
    msg.msg_iovlen = 2;

    // 发送数据
    ssize_t send_len = sendmsg(sock_fd, &msg, 0);
    if (send_len == -1) {
        std::cerr << "sendmsg failed: " << strerror(errno) << std::endl;
    } else {
        std::cout << "分散发送成功,总字节数:" << send_len << std::endl;
    }

    close(sock_fd);
    return 0;
}

五、精细化控制:套接字选项API

核心作用:调整内核中socket的行为,适配业务场景、优化传输性能、解决特殊场景问题,是网络编程进阶必备

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

1. 核心选项操作函数

(1)获取套接字属性

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

(2)设置套接字属性

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

  • 通用参数详解
    • sockfd:待操作的socket文件描述符
    • level:选项所属的协议层级,常用值:
      • SOL_SOCKET:通用套接字层
      • IPPROTO_TCP:TCP传输层
      • IPPROTO_IP:IPv4网络层
    • optname:具体的选项名称
    • optval:获取/设置选项值的缓冲区地址
    • optlen:选项值的长度,getsockopt中为值-结果参数
  • 返回值:成功返回0,失败返回-1并设置errno

2. 高频常用选项汇总

选项名称 所属层级 核心功能 典型适用场景
SO_REUSEADDR SOL_SOCKET 允许地址和端口复用 服务端重启快速绑定端口,解决TIME_WAIT状态端口占用问题
SO_REUSEPORT SOL_SOCKET 允许多个进程/线程绑定同一个端口 多进程负载均衡,提升并发处理能力
SO_RCVBUF SOL_SOCKET 设置/获取内核接收缓冲区大小 大文件传输、高带宽场景优化,注意受系统net.core.rmem_max限制
SO_SNDBUF SOL_SOCKET 设置/获取内核发送缓冲区大小 高延迟网络场景优化,受系统net.core.wmem_max限制
SO_RCVTIMEO SOL_SOCKET 设置接收操作超时时间 避免recv/recvfrom无限阻塞
SO_SNDTIMEO SOL_SOCKET 设置发送操作超时时间 避免send/sendto无限阻塞
SO_KEEPALIVE SOL_SOCKET 开启TCP层保活探测机制 长连接场景检测无效断开的连接
TCP_NODELAY IPPROTO_TCP 禁用Nagle算法,关闭小包合并 低延迟场景(实时通信、交互式操作)
TCP_CORK IPPROTO_TCP 开启TCP黏包控制,数据攒满后统一发送 大文件批量传输,提升带宽利用率
TCP_LINGER2 IPPROTO_TCP 设置FIN_WAIT2状态超时时间 优化服务端TIME_WAIT资源占用

使用样例

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h> // TCP_NODELAY需要此头文件
#include <errno.h>

int main() {
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        std::cerr << "socket failed: " << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
    }

    // ===== 1. 设置端口复用 =====
    int reuse = 1;
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        std::cerr << "setsockopt SO_REUSEADDR failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "设置SO_REUSEADDR成功" << std::endl;

    // ===== 2. 设置接收超时(5秒) =====
    struct timeval rcv_timeout;
    rcv_timeout.tv_sec = 5;
    rcv_timeout.tv_usec = 0;
    if (setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &rcv_timeout, sizeof(rcv_timeout)) == -1) {
        std::cerr << "setsockopt SO_RCVTIMEO failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "设置SO_RCVTIMEO(5秒)成功" << std::endl;

    // ===== 3. 禁用Nagle算法(低延迟) =====
    int nodelay = 1;
    if (setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) == -1) {
        std::cerr << "setsockopt TCP_NODELAY failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "设置TCP_NODELAY成功(禁用Nagle算法)" << std::endl;

    // ===== 4. 获取发送缓冲区大小 =====
    int snd_buf_size;
    socklen_t opt_len = sizeof(snd_buf_size);
    if (getsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, &opt_len) == -1) {
        std::cerr << "getsockopt SO_SNDBUF failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "当前发送缓冲区大小:" << snd_buf_size << " 字节" << std::endl;

    // ===== 5. 修改发送缓冲区大小 =====
    snd_buf_size = 65536; // 64KB
    if (setsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, sizeof(snd_buf_size)) == -1) {
        std::cerr << "setsockopt SO_SNDBUF failed: " << strerror(errno) << std::endl;
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    // 重新获取验证
    getsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, &opt_len);
    std::cout << "修改后发送缓冲区大小:" << snd_buf_size << " 字节" << std::endl;

    close(sock_fd);
    return 0;
}

六、常见错误与调试建议

将散落在各处的易错点集中呈现,便于快速定位问题

常见错误 可能原因 解决方法 调试样例
bind: Address already in use 端口被占用或处于TIME_WAIT状态 设置SO_REUSEADDR选项 见「套接字选项设置样例」中SO_REUSEADDR部分
connect: Connection refused 服务端未监听、防火墙拦截、网络不通 检查服务端listen状态和网络连通性 telnet 127.0.0.1 8080nc -zv 127.0.0.1 8080
accept: Invalid argument addrlen未初始化 调用前务必赋值为sizeof(struct sockaddr_in) 见TCP服务端样例中client_len = sizeof(client_addr)
send: Broken pipe 对端已关闭连接,继续发送数据 捕获SIGPIPE信号或使用MSG_NOSIGNAL标志 send(sock_fd, msg, len, MSG_NOSIGNAL);
recv: Connection reset by peer 对端异常关闭连接(如崩溃) 正确处理错误,关闭套接字 见TCP客户端样例中recv错误处理
recv返回0 对端主动关闭连接 表示EOF,应关闭套接字 见TCP服务端样例中recv_len == 0处理
send/recv返回值小于请求长度 TCP流式特性,或缓冲区不足 循环发送/接收直至完成 见「TCP循环收发样例」中send_all/recv_all函数
inet_pton返回0 传入的IP字符串格式错误 检查IP格式,如192.168.1.1::1 见IP地址转换样例中错误处理
setsockopt修改缓冲区大小无效 必须在connect/listen之前设置 在创建套接字后立即设置 见套接字选项样例中缓冲区设置部分
backlog实际生效值小于传入值 受系统net.core.somaxconn限制 检查并调整系统参数 sysctl net.core.somaxconn
sysctl -w net.core.somaxconn=1024

通用调试技巧

  1. 开启errno打印:所有socket API失败后调用strerror(errno)
  2. 编译时开启警告:g++ -Wall -Wextra -o server server.cpp
  3. 抓包分析:tcpdump -i lo port 8080(本地回环)或wireshark
  4. 查看端口占用:netstat -tulnp | grep 8080ss -tulnp | grep 8080
  5. 查看系统参数:sysctl -a | grep net.core(缓冲区/队列相关)
posted @ 2026-03-23 16:52  suiyuan129  阅读(62)  评论(0)    收藏  举报