这两个函数只适用于套接字描述符。read、readv、recv 和 recvfrom 能用的地方,recvmsg 都能使用,而且,recvmsg 能提供更多的功能。同样的,各种 output 类型的函数都可以替换成 sendmsg 函数。

所以,recvmsg 和 sendmsg 是之前我们学过的读写类函数的究极形态。这么强大的函数,使用起来也会相当的复杂,在本文,我们只讨论它的一部分功能,剩下的功能,以后再说。

1. 函数原型

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

这两个函数,把大多数的参数都放进了一个 struct msghdr 类型的结构体。接下来,我们就来看看这个结构体的样子。

2. msghdr{}

struct msghdr {
void *msg_name; /* 套接字地址 */
socklen_t msg_namelen; /* 地址长度 */
struct iovec *msg_iov; /* 散布/聚焦数组 */
size_t msg_iovlen; /* msg_iov 的元素个数 */
void *msg_control; /* 辅助数据 */
size_t msg_controllen; /* 辅助数据大小 */
int msg_flags; /* 接收数据的标志,仅用于 recvmsg */


71-recvmsg 和 sendmsg 函数_linux


图1 msghdr{} 结构体


  • msg_name 和 msg_namelen

这两个成员仅用于无连接的场合(比如无连接的 UDP 套接字)。它相当于 recvfrom 和 sendto 的最后两个参数。

msg_name 指向一个套接字地址结构,msg_namelen 则是该套接字地址结构的大小。对于 sendto 来说,msg_name 存入接收者的地址。对于 recv_msg 来说,它用来接收返回值(值-结果参数)。

  • msg_iov 和 msg_iovlen

这两个成员和 writev 和 readv 函数没什么两样。

  • msg_control 和 msg_controllen

这两个成员表示辅助数据的位置和大小。对于 recvmsg 来说,这个参数也是一个值-结果参数。关于辅助数据的含义,我们先不讨论它,以后我们遇到它的时候,再进行详细学习。因为就目前来说,我们还用不上它。

  • msg_flags

这个成员只针对 recvmsg 函数有效。

recvmsg 被调用时,recvmsg 的最后一个参数 flags 的值会被复制到 msg_flags 成员并交由内核使用。当 recvmsg 返回时,msg_flags 会保存返回结果,因此 msg_flags 也是一个值结果参数。msg_flags 的返回结果可能是下面这些值:MSG_EORMSG_OOBMSG_BCASTMSG_MCASTMSG_TRUNCMSG_CTRUNCMSG_NOTIFCATION. 千万别晕菜,这些东西你目前来说不需要去理解它是干嘛的……所以我们目前忽略掉它。

sendmsg 要想使用 flags,只能使用 sendmsg 函数的最后一个参数,而不应该使用 msg_flags 成员。

3. 实验

根据第 2 节中的情况,我们掌握前 4 个 msghdr{} 的前 4 个成员:msg_name, msg_namelen, msg_iov, msg_iovlen 即可。实验中,我们将之前写的 udp 回射程序进行改写,将 sendto 和 recvfrom 改成 sendmsg 和 recvmsg 即可。

如果你已经 clone 过这个代码了,请使用 ​​git pull​​​ 更新一下。本节程序所使用的程序路径是 ​​unp/program/advcio/rwmsg​

udp 回射程序主要修改了两个地方:

  • 将客户端的 sendto 修改成了 sendmsg
  • 将服务器的 recvfrom 修改成了 recvmsg

3.1 程序代码

  • 服务器端关键代码
void doServer(int sockfd) {
char buf[4096];
int nr, nw;
struct sockaddr_in cliaddr;
struct msghdr msg;
struct iovec iov;
socklen_t len;

while(1) {
// 填充 msghdr{} 结构体
iov.iov_base = buf;
iov.iov_len = 4096;
msg.msg_name = &cliaddr;
msg.msg_namelen = sizeof(cliaddr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 20;
msg.msg_flags = 0;
// 接收数据
nr = recvmsg(sockfd, &msg, 0);
if (nr < 0) {
if (errno == EINTR) continue;
ERR_EXIT("recvfrom");
}
// 后面的部分是将数据处理后发送给客户端
// ...
  • 客户端代码
void doClient(int sockfd) {
int ret, len, nr, nw;
struct sockaddr_in servaddr;
char prompt[64];
char buf[4096];
struct msghdr msg;
struct iovec iov[2];
strcpy(prompt, "send: ");

ret = resolve(g_option.hostname, g_option.port, &servaddr);
if (ret < 0) ERR_EXIT("resolve");

while(1) {
nr = iread(STDIN_FILENO, buf, 4096);
if (nr < 0) {
ERR_EXIT("iread");
}
else if (nr == 0) break;

// 填充 msghdr{} 结构体
iov[0].iov_base = prompt;
iov[0].iov_len = strlen(prompt);
iov[1].iov_base = buf;
iov[1].iov_len = nr;
msg.msg_name = &servaddr;
msg.msg_namelen = sizeof(servaddr);
msg.msg_iov = iov;
msg.msg_iovlen = 2;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;

nw = sendmsg(sockfd, &msg, 0);

// 从服务器接收数据
// ...

3.2 运行结果

  • 客户端


71-recvmsg 和 sendmsg 函数_网络编程_02


图2 客户端运行结果


  • 服务器


71-recvmsg 和 sendmsg 函数_网络编程_03


图3 服务器运行结果


4. 总结

  • 掌握 recvmsg 和 sendmsg 的基本用法