112-封装 recvFromFlags

在上一篇博文中,我们使用了 recvmsg 函数来获取标志位,但是每次填充 struct msg 结构体都相当费事,因此我们希望将这个过程封装成一个函数 recvFromFlags,一劳永逸。

除了获取标志位之外,我们还希望得到数据包是从哪个接口进来的,以及数据包的目的地址。这个任务需要使用辅助数据结构来完成,如果你不记得辅助数据结构,请参考《辅助数据》

本文所用到的代码托管在 gitos 上:http://git.oschina.net/ivan_allen/unp

本文使用的程序路径为 unp/program/advcudp/recvfromflags

1. recvFromFlags 函数声明

int recvFromFlags(int sockfd, char* buf, int len, int *flags,
  struct sockaddr *addr, socklen_t *addrlen, struct in_pktinfo *pkt);

先不论此函数实现,先解释下参数的含义。前 3 个就不解释了,大家都明白。从 flags 开始。

  • flags: 这是一个传出参数,用来接收 recv 可能产生的错误或其它信息,常见值如下:
    • MSG_EOR
    • MSG_TRUNC
    • MSG_CTRUNC
    • MSG_OOB
    • MSG_ERRQUEUE
    • MSG_BCAST (使用时需先检查有没有定义)
    • MSG_MCAST(使用时需先检查有没有定义)
  • addr 和 addrlen 和 recvfrom 函数的一样,没有区别
  • pkt:这是一个传出参数,定义在后面给出。

recvFromFlags 的返回值表示接收的字节数。返回 -1 失败。

1.1 struct in_pktinfo 结构体(man 7 ip)

struct in_pktinfo {
  unsigned int   ipi_ifindex;  /* 接口索引号 */
  struct in_addr ipi_spec_dst; /* 路由地址 */
  struct in_addr ipi_addr;     /* 目标地址*/
};

该数据结构,通过 struct msghdr 的成员 msg_control 辅助数据来传递。如果想要接收此数据,接收方需要打开 socket 选项 IP_PKTINFO,具体如下:

int on = 1;
setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));

2. 示例

udp server 中,我们通过 recvFromFlags 来演示它的用法,下面是部分程序(已删除无关部分)。

  • 部分代码
void doServer(int sockfd) {    
  char buf[4096];
  char ifname[IF_NAMESIZE];    
  int nr, nw, flags;           
  struct sockaddr_in cliaddr;  
  socklen_t len;
  struct in_pktinfo pkt;       

  while(1) {                   
    len = sizeof(cliaddr);     
    flags = 0;                 
    bzero(&pkt, sizeof(pkt));
    // 接收数据,同时接收标志位,struct in_pktinfo 信息
    recvFromFlags(sockfd, buf, 20, &flags, (struct sockaddr*)&cliaddr, &len, &pkt);                              

    printf("%d byte datagram from %s", nr, inet_ntoa(cliaddr.sin_addr));                                              
    if (pkt.ipi_addr.s_addr != 0) {   
      printf(", to %s", inet_ntoa(pkt.ipi_addr));
    }
    if (pkt.ipi_spec_dst.s_addr != 0) {
      printf("(local ip %s)", inet_ntoa(pkt.ipi_spec_dst));
    }
    if (pkt.ipi_ifindex > 0) {
      printf(", recv i/f = %s", if_indextoname(pkt.ipi_ifindex, ifname));
    }

    if (flags & MSG_TRUNC) {
      printf(" (datagram truncated)");
    }
    if (flags & MSG_CTRUNC) {
      printf(" (control info truncated)");
    }
#ifdef MSG_BCAST
    if (flags & MSG_BCAST) {
      printf(" (broadcast)");
    }
#endif
#ifdef MSG_MCAST
    if (flags & MSG_MCAST) {
      printf(" (multicast)");
    }
#endif

    printf("\n");

    sendto(sockfd, buf, nr, 0, (struct sockaddr*)&cliaddr, len);
  }
}
  • 运行结果


这里写图片描述
图1 使用 recvFromFlags 的 udp 服务器

3. recvFromFlags 函数实现

因为要接收标志位和辅助数据,因此内部实现只能使用 recvmsg 函数来完成。标志位的接收在上一篇博文里已经演示过,非常简单。这里重点和难点在于 struct in_pktinfo 数据的接收。实际上,如果你已经熟练掌握了辅助数据,这也不是什么难事。

下面我只贴上接收辅助数据的关键代码。recvFromFlags 函数的具体实现定义在 unp/program/util/common.cc 中。

int recvFromFlags(int sockfd, char* buf, int len, int *flags, 
    struct sockaddr *addr, socklen_t *addrlen, struct in_pktinfo *pkt) {
  int nr;
  struct msghdr msg;  
  // 其它局部变量定义...         

  // 填充 msg
  // ...      
  msg.msg_control = control_un.control;
  msg.msg_controllen = sizeof(control_un.control);


  nr = recvmsg(sockfd, &msg, *flags);
  if (nr < 0) {
    return nr;
  }

  // 返回的标志位
  *flags = msg.msg_flags;

  if (msg.msg_controllen < sizeof(struct cmsghdr) // 辅助数据长度不够
      || (msg.msg_flags & MSG_CTRUNC) // 辅助数据被截断 
      || pkt == NULL) { // 用户并不想知道接口信息
    return nr;
  }

  // 遍历辅助数据
  // Linux 采用 IP_PKTINFO 而不是 IP_RECVDSTADDR 和 IP_RECVIF 
  // 该套接字选项对应结构体 struct in_pktinfo
  // 成员 ipi_ifindex 表示数据包从哪个接口进来的
  // 成员 ipi_spec_dst 表示数据包的本地地址(路由地址)
  // 成员 ipi_addr 表示数据包的目的地址
  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
      memcpy(pkt, CMSG_DATA(cmsg), sizeof(struct in_pktinfo));
      break;
    }

    ERR_QUIT("unknown ancillary data, len = %d, level = %d, type = %d",
        cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type);
  }
  return nr;
}

4. 总结

  • 掌握 recvFromFlags 函数的封装
  • 掌握辅助数据
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值