linu 提供如下 IO 接口:
- read 和 write -- 最简单的读写函数
- readn 和 writen -- 原子性读写操作
- recvfrom 和 sendto -- 增加了目标地址和地址结构长度的参数
- recv 和 send -- 允许从进程到内核传递标志
- readv 和 writev -- 允许指定往其中输入数据或从其中输出数据的缓冲区
- recvmsg 和 sendmsg -- 结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力
他们之间的差异如下:
read write | • | • | |||||
readv writev | • | • | |||||
recv send | • | • | • | ||||
recvfrom sendto | • | • | • | • | |||
recvmsg sendmsg | • | • | • | • | • |
recv 和send
函数原型
调用成功返回输入或输出字节数,出错返回 -1
这两个函数与标准 read 和 write 函数的唯一差别在于增加了一个 flags 参数,当 flags 参数为 0 时,他们与标准的 read 和 write 函数
flag 参数
flags 参数可以取下列表中的一个或多个常值的逻辑或,或取 0 值
参看“recvmsg 和 sendmsg 函数”介绍中的 输入输出函数标志总结表
设定了 MSG_WAITALL 的 recv 函数与 readn 函数完全等价,只有当下列情况之一发生时才会返回比所请求的字节数少的数据:
- 被信号中断
- 连接被终止
- 套接字错误
readv 和 writev
函数原型
定义于 sys/uio.h 中
调用成功返回读入或写出的字节数,否则返回-1
函数说明
这两个函数十分类似 read 和 write 函数,但是 readv 和 writev 允许单个系统调用读入或写出自一个或多个缓冲区,分别称作“分散度”和“集中写”,来自读操作的输入数据被分散到多个应用缓冲区中,同时多个应用缓冲区中的输出数据则被集中提供给单个写操作
当然,readv 和 writev 函数可以用于任何描述符,而不仅限于套接字描述符
同时,writev 是一个原子操作,这意味着对于记录的协议(如 UDP),每次 writev 操作只会生成一个数据报
参数说明
- iovec
函数第二个参数是一个保存 iovec 结构数组的首地址的指针,iovec 结构定义在 sys/uio.h 中:
1 struct iovec 2 { 3 void *iov_base; 4 size_t iov_len; 5 }
sys/uio.h 中定义了 IOV_MAX 常量,闲置了 iovec 结构数组中元素数目的最大值
iovec 就是缓冲区结构,iov_base 保存了缓冲区首地址,而 iov_len 保存了缓冲区长度
recvmsg 和 sendmsg
函数原型
定义在 sys/socket.h 中
调用成功返回输入或输出字节数,否则返回 -1
函数说明
这两个函数是最通用的 IO 函数,实际上我们可以把上述所有的 IO 操作都用这两个函数实现
参数说明
- msg
这两个函数把大部分参数封装到了一个 msghdr 结构中:
struct msghdr { void *msg_name; // 套接字地址结构 socklen_t msg_namelen; struct iovec *msg_iov; // 缓存结构数组 int msg_iovlen; void *msg_control; // 附加数据数组 socklen_t msg_controllen; int msg_flags; // 仅供 recvmsg 函数使用,作为值-结果参数返回 }
msg_name | 用于套接字未连接场合(如 UDP),指向一个套接字地址结构的指针,保存接收者(sendmsg)或发送者(recvmsg)的协议地址,如果无需指定协议地址(如 TCP 或已连接 UDP 套接字)则置为空指针 |
msg_namelen | msg_name 所指向地址结构的大小 |
msg_iov | 指向 iovec 结构数组的首地址,该数组即缓冲区数组,在 readv 和 writev 两个函数的介绍中已经介绍了该参数 |
msg_iovlen | 指定 msg_iov 数组中元素个数 |
msg_control | 可选,辅助数据数组的首地址 |
msg_controllen | msg_control 数组中元素个数(对于 recvmsg 函数是一个值-结果参数) |
msg_flags | 标志变量 |
- msg 参数的 msg_flags 和 flags 参数
我们必须区分 msghdr 结构的 msg_flags 成员和 flags 参数:
- 只有 recvmsg 使用 msg_flags 成员,在 recvmsg 被调用时,flags 参数被复制到 msg_flags 成员,并由内核使用其值驱动接收处理过程,并依据结果更新 msg_flags 成员
- sendmsg 则忽略 msg_flags 成员,因为他直接使用 flags 参数驱动发送过程
MSG_DONTROUTE | 绕过路由表查找(目的主机在某个直连的本地网络上) | • | ||
MSG_DONTWAIT | 仅本操作非阻塞 | • | • | |
MSG_PEEK | 窥看外来数据 | • | ||
MSG_WAITALL | 等待所有数据(内核不在尚未读入所有请求数目字节之前让它返回) | • | ||
MSG_EOR | 终止记录(这通常对于SOCK_SEQPACKET套接口类型十分有用) | • | • | |
MSG_OOB | 发送或接收带外数据 | • | • | • |
MSG_BCAST | 本数据报作为链路层广播接收或者其目的地址是一个广播地址 | • | ||
MSG_MCAST | 本数据报作为链路层多播收取 | • | ||
MSG_TRUNC | 本数据报被截断(内核预备返回的数据超过进程缓存空间) | • | ||
MSG_CTRUNC | 本数据报的辅助数据被截断(内核预备返回的辅助数据超过 msg_control 所能存储的大小) | • | ||
MSG_NOTIFICATION | 接收到的SCTP带外数据是一个事件通知而不是数据消息 | • |
下图展示了 recvmsg 返回时值的例子:
辅助控制信息(msg_control、msg_controllen 成员)
辅助数据可以通过调用 sendmsg 和 recvmsg 这两个函数使用 msghdr 结构中的 msg_control 和 msg_controllen 成员发送和接收
msg_control 成员是下面介绍的 cmsghdr 结构的数组
1 struct cmsghdr 2 { 3 socklen_t cmsg_len; 4 int cmsg_level; 5 int cmsg_type; 6 unsigned char cmsg_data[]; 7 }
- 辅助数据结构用法:
IPv4 | IPPROTO_IP | IP_REVDSTADDR | 随 UDP 数据报接收目的地址 |
• | • | IP_RECVIF | 随 UDP 数据报接收接口索引 |
IPv6 | IPPROTO_IPV6 | IPV6_DSTOPTS | 指定/接收目的地选项 |
• | • | IPV6_HOPLIMIT | 指定/接收跳限 |
• | • | IPV6_HOPOPTS | 指定/接收步跳选项 |
• | • | IPV6_NEXTHOP | 指定下一跳的地址 |
• | • | IPV6_PKTINFO | 指定/接收分组信息 |
• | • | IPV6_RTHDR | 指定/接收路由首部 |
• | • | IPV6_TCLASS | 指定/接收分组流通类别 |
UNIX域 | SOL_SOCKET | SCM_RIGHTS | 发送/接收描述符 |
• | • | SCM_CREDS | 发送/接收用户凭证 |
- 辅助数据处理函数
下面列出了 5 个宏用来简化对辅助数据的处理
1 struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mhdrptr); 2 // 返回指向第一个 cmsghdr 结构的指针或为 NULL 3 struct cmsghdr *CMSG_NXTHDR(struct msghdr *mhdrptr, struct cmsghdr *cmsgptr); 4 // 返回指向 cmhdrptr 下一个辅助对象指针或为 NULL 5 unsigned char *CMSG_DATA(struct cmsghdr *cmsgptr); 6 // 返回指向 cmsghdr 结构关联的数据的第一个字节的指针 7 unsigned int CMSG_LEN(unsigned int length); 8 // 返回给定数据量下存放到 cmsg_len 中的值 9 unsigned int CMSG_SPACE(unsigned int length); 10 // 返回给定数据量下一个辅助数据对象总的大小