71-recvmsg 和 sendmsg 函数

这两个函数只适用于套接字描述符。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 flags);

这两个函数,把大多数的参数都放进了一个 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 */ };


这里写图片描述
图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 即可。

程序路径:

git clone https://git.oschina.net/ivan_allen/unp.git

如果你已经 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 运行结果

  • 客户端


这里写图片描述
图2 客户端运行结果

  • 服务器


这里写图片描述
图3 服务器运行结果

4. 总结

  • 掌握 recvmsg 和 sendmsg 的基本用法

说明:本文转自blog.csdn.net,用于学习交流分享,仅代表原文作者观点。如有疑问,请联系我们删除~