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 的基本用法
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值