组播的时候到底该如何绑定网卡

http://blog.hongquan.me/?p=114

以前在代码中,遇到组播都是一直绑定0.0.0.0然后去把自己加入到组播组里面去。但是最近的一次改动却让我发现了一个问题。

因为在显示的环境中机器上会有很多网卡(>3)。 而我们指向接收其中一个子网中的组播中的数据,那么很明显的想法就是接收端绑定相应子网的IP。在windows环境中,我们绑定了机器上该子网的IP(例如:172.16.20.40)。实际测试的发现一切良好。然后在Linux环境中沿用了这个code。结果却发现无论如何都收不到组播消息。于是一阵瞎倒腾,发现在linux上绑定在组播组的地址上就可以收到消息。晕,系统不一样表现就是不一样啊。但是事情并没有结束,我并没有弄清楚这是咋回事。查看了公司的包装的代码发现在mutilcast client的实现上是按照Windows的设定来的。

  1. 创建UDP socket,绑定在一个网卡地址上
  2. 指定multiast group address,并且取出socket的本地地址,填入ip_mreq.imr_multiaddr和ip_mreq.imr_interface。

而google了一番发现了一个问题,在linux上是不能绑定在物理网卡地址上的,这样做会什么信息都收不到,要么绑定0.0.0.0以收取所有的组播消息,或者绑定一个具体的组播地址以收取发到这个组播地址的消息。而具体要收取哪一个子网的组播信息的时候用ip_mreq.imr_interface来指定。详见(http://stackoverflow.com/questions/11234671/multiple-multicast-on-multiple-interfaces 和http://www.kohala.com/start/mcast.api.txt

  看起来一样的东西在不同的系统下面可能是不一样的,谨记,谨记.




关于在双网卡多IP环境下的UDP服务器组件编程问题及解决方案

现行双通机房很多
而双通机房大多是使用多IP,即电信一个网通一个. (也有只使用单IP的双通机房,不过据说效果不好,未验证)
在双IP下,在使用常规方法创建UDP服务(即用一个Socket绑定0.0.0.0,即绑定本机所有IP) 时会产生一个由双IP造成的问题:
假设 本机有电信IP 1 网通IP 2
当一个电信用户A发一个UDP包到电信IP 2,而服务器处理返回时,有可能是用IP 1 发送出去的.
这就造成可能用户A会收不到该UDP返回包,原因是对用户A的防火墙而言IP 1是一个陌生IP,有可能被拒绝了.

解决这个问题的方案比较简单. 只要在创建UDP服务时,为每一个IP创建一个UDP Socket,绑定于不同IP相同端口. 在处理用户UDP时,哪个SOcket接收到的,就用哪个Socket返回.

现有UDP另一个高级问题: 转发问题
假定有用户A,用户B已登录到UDP服务器.
而当有一个业务,是A发请求包,而UDP服务器处理后需发UDP包给B,以通知B.(例,P2P打通,IM上线通知等.)
这个时候,UDP在处理 A请求,问题是当转发B时,该用哪个IP ,即哪个Socket发给B.
如果A和B是通过不同的IP登录上去的. 服务器用了非B通信的Socket发的时候,就会产生可能B接收不到包的问题.

解决这个问题的方案有两个:
1.在用户在线表中记录用户通信的服务器Socket.
2.在UDP层记录用户IP与服务器Socket的通信关系. 在发包时从这关系映射中查找该用哪个Socket发送.

现来分析两种方案的实现方法,及优缺点
1方案,实现简单,在用户在线表中增加一些记录字段即可,优点是性能好. 缺点是在业务层上实现的,会导致业务复杂度增加和应用限制(必须记录用户的在线信息.)
2方案,实现方法是每一个服务Socket建立一个映射表,把通信过的用户IP映射过来.每次须要转发时就从映射表中查找该Socket是否存在该IP.有则使用,无则查询下一个Socket. 都查询不到则使用默认Socket.
该方法的优点的是在UDP组件层就实现了功能,不会限制应用.不影响业务逻辑.
缺点是实现复杂 要采用一种映射算法来存储IP与Socket之间的关系.而实现映射又须要额外的存储空间来实现.
下面讲一下2方案里怎么存储用户IP和服务器Socket之间的映射
具体的实现映射的方法有两种比较实用的,一种是使用HashTable.
优点是精确,缺点是须要的存储空间大.
另一种是使用路由和搜索引擎爬虫中比较常用的BloomFilter.算法
优点是内存占用空间少,缺点是CPU消耗比HashTable高些. 而且不够精确.

现在来比较一下两种算法的存储消耗,CPU现在够强,这部分的CPU消耗基本可以忽略.
以预计映射100W个用户IP与Socket之间的映射为目标.
如果用HashTable,以桶+链表解决冲突的方法,那每一节点须 4(桶)+4链表)+4(IP) 共 12字节
100W即约11M.
如果使用BloomFilter 以8种Hash算法实现 即8位Byte型的BloomFilter. 使用400W节点.约需4M.而冲突率可保证在10W分之一以下(已验证)
两种算法各有优缺点,就看个人喜爱哪种了.
还有一个问题就是什么时候清空这些映射重建.因为UDP的IP映射长久保留是没有意义的.
我的方案是为每一个Socket建两个映射,两个映射建立时间错开3分钟,但每一个映射的存活时间都是6分钟.
即在任何一个时刻,都有一个映射至少记录了3分钟内的IP映射情况.
这部分看大家各自需求是什么样的,一天清一次也可以.
如果哪位朋友有更好的方法欢迎讨论.

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,可以使用setsockopt函数来指定组播数据发送和接收的网络接口。以下是一个简单的示例,展示如何使用setsockopt函数指定网卡。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MCAST_PORT 8888 #define MCAST_ADDR "239.1.2.3" #define BIND_ADDR "192.168.0.2" #define IF_NAME "eth0" int main() { int sockfd, ret; struct sockaddr_in addr; struct ip_mreq mreq; struct ifreq ifr; // 创建socket sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); exit(1); } // 设置socket选项,允许多个进程绑定到同一个端口 int reuse = 1; ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); if (ret < 0) { perror("setsockopt"); exit(1); } // 绑定到指定地址和端口上 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(MCAST_PORT); addr.sin_addr.s_addr = INADDR_ANY; ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); if (ret < 0) { perror("bind"); exit(1); } // 设置组播组地址和网卡 memset(&mreq, 0, sizeof(mreq)); mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR); mreq.imr_interface.s_addr = inet_addr(BIND_ADDR); ret = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); if (ret < 0) { perror("setsockopt"); exit(1); } // 设置发送数据的网卡 memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, IF_NAME, sizeof(ifr.ifr_name)-1); ret = setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); if (ret < 0) { perror("setsockopt"); exit(1); } // 接收组播数据 char buf[1024]; struct sockaddr_in peer_addr; socklen_t addrlen; while (1) { memset(buf, 0, sizeof(buf)); addrlen = sizeof(peer_addr); ret = recvfrom(sockfd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&peer_addr, &addrlen); if (ret < 0) { perror("recvfrom"); continue; } printf("Received %d bytes from %s:%d: %s\n", ret, inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), buf); } return 0; } ``` 在上面的代码中,我们使用setsockopt函数设置了IP_ADD_MEMBERSHIP选项,将socket加入到指定的组播组中,并设置了组播数据接收的网卡。同时,我们也使用setsockopt函数设置了SO_BINDTODEVICE选项,指定组播数据发送的网卡
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值