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 函数的封装
  • 掌握辅助数据
说明:本文转自--Allen--,用于学习交流分享,仅代表原文作者观点。如有侵权,请联系我们删除~