linu 提供如下 IO 接口:
他们之间的差异如下:
函数 | 可用任何描述符 | 仅套接字描述符 | 单个读写缓冲区 | 分散读写缓冲区 | 可选标志 | 可选对端地址 | 可选控制信息 |
read write | • | • | |||||
readv writev | • | • | |||||
recv send | • | • | • | ||||
recvfrom sendto | • | • | • | • | |||
recvmsg sendmsg | • | • | • | • | • |
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
调用成功返回输入或输出字节数,出错返回 -1
这两个函数与标准 read 和 write 函数的唯一差别在于增加了一个 flags 参数,当 flags 参数为 0 时,他们与标准的 read 和 write 函数
flags 参数可以取下列表中的一个或多个常值的逻辑或,或取 0 值
参看“recvmsg 和 sendmsg 函数”介绍中的 输入输出函数标志总结表
设定了 MSG_WAITALL 的 recv 函数与 readn 函数完全等价,只有当下列情况之一发生时才会返回比所请求的字节数少的数据:
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
定义于 sys/uio.h 中
调用成功返回读入或写出的字节数,否则返回-1
这两个函数十分类似 read 和 write 函数,但是 readv 和 writev 允许单个系统调用读入或写出自一个或多个缓冲区,分别称作“分散度”和“集中写”,来自读操作的输入数据被分散到多个应用缓冲区中,同时多个应用缓冲区中的输出数据则被集中提供给单个写操作
当然,readv 和 writev 函数可以用于任何描述符,而不仅限于套接字描述符
同时,writev 是一个原子操作,这意味着对于记录的协议(如 UDP),每次 writev 操作只会生成一个数据报
函数第二个参数是一个保存 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 保存了缓冲区长度
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
定义在 sys/socket.h 中
调用成功返回输入或输出字节数,否则返回 -1
这两个函数是最通用的 IO 函数,实际上我们可以把上述所有的 IO 操作都用这两个函数实现
这两个函数把大部分参数封装到了一个 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 | 标志变量 |
我们必须区分 msghdr 结构的 msg_flags 成员和 flags 参数:
标志 | 说明 | 发送函数 flags | 接收函数 flags | msg_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 返回时值的例子:
辅助数据可以通过调用 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 }
协议 | cmsg_level | cmsg_type | 说明 |
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 // 返回给定数据量下一个辅助数据对象总的大小
文章内容来源于:https://techlog.cn/article/list/10182663
原文:https://www.cnblogs.com/WenLee/p/12159041.html