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编程标准
使用样例
cpp
#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 - 易错点:必须提前分配足够的缓冲区,避免缓冲区溢出
使用样例
cpp
#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. 通用套接字地址结构体
cpp
struct sockaddr {
sa_family_t sa_family; // 地址族,标识协议类型
char sa_data[14]; // 填充的地址数据,兼容所有协议族
};
- 核心作用:作为所有socket API的统一入参类型,使用时需将专用结构体强制转换为此类型,实现多协议兼容
2. IPv4专用地址结构体(最常用)
cpp
// 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
使用样例
cpp
#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,自动匹配domain和type对应的默认协议
- 返回值 :成功返回非负的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服务端必须调用,客户端可省略(内核自动分配临时端口)
- 参数详解 :
sockfd:socket()创建的文件描述符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场景下可调用,用于预绑定对端地址
- 参数详解 :
sockfd:socket()创建的未连接套接字文件描述符addr:传入填充好的服务端地址结构体,需强制转换为struct sockaddr*类型addrlen:传入地址结构体的长度
- 返回值 :成功返回
0,失败返回-1并设置errno - UDP场景适配 :UDP调用
connect不会建立真实连接,仅会在内核中记录对端地址,后续可直接用send/recv传输数据,提升效率并限定通信对端
使用样例1
cpp
#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
cpp
#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套接字fdbuf:待发送数据的缓冲区地址len:待发送数据的字节长度flags:传输控制标志,常用值见下表,无特殊需求填0
- 返回值 :成功返回实际发送的字节数,失败返回
-1并设置errno - 关键注意点:返回值大于0不代表对端已收到数据,仅代表数据已成功写入内核发送缓冲区
(2)TCP数据接收
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 核心功能:从已连接的TCP对端接收数据,支持精细化接收控制
- 参数详解 :
sockfd:已连接的TCP套接字fdbuf:存储接收数据的缓冲区地址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=0的recv()/send(),无额外控制能力
使用样例
cpp
#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:传入地址结构体的长度
- 前4个参数与
- 返回值 :成功返回实际发送的字节数,失败返回
-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结构体的长度,调用后返回实际地址长度
- 前4个参数与
- 返回值 :成功返回实际接收的字节数,失败返回
-1并设置errno - 补充说明 :UDP是数据报协议,
recvfrom一次返回一个完整的数据报,不会出现半包
补充:已调用
connect预绑定对端地址的UDP套接字,可直接使用send()/recv()/read()/write(),无需每次指定地址
使用样例1
cpp
#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
cpp
#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、传输辅助数据(如进程间传递文件描述符)、携带控制信息,功能最全面
- 使用样例:
cpp
#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资源占用 |
使用样例
cpp
#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 8080 或 nc -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 |
通用调试技巧
- 开启
errno打印:所有socket API失败后调用strerror(errno) - 编译时开启警告:
g++ -Wall -Wextra -o server server.cpp - 抓包分析:
tcpdump -i lo port 8080(本地回环)或wireshark - 查看端口占用:
netstat -tulnp | grep 8080或ss -tulnp | grep 8080 - 查看系统参数:
sysctl -a | grep net.core(缓冲区/队列相关)