Linux组播设置以及netty设置

Java 7增加NIO multicast的支持, netty 4中也增加了对multicast的支持: Issue #216
实际在使用中需要对环境和Netty的代码进行相应的配置。

检查Linux环境是否支持multicast

运行ifconfig -a, 如果在输出中有如下的信息,则当前的内核支持multicast。

1
UP BROADCAST RUNNING MULTICAST

如果没有这样的信息, 尝试下面的命令

1
$ifconfig eth0 multicast

再运行ifconfig -a, 如果的确没有UP BROADCAST RUNNING MULTICAST, 需要重新编译内核。
设置内核配置如下:

  • CONFIG_IP_MULTICAST=y
  • CONFIG_IP_ROUTER=y
  • CONFIG_IP_MROUTE=y
  • CONFIG_NET_IPIP=y
    默认Red Hat / Fedora/ Cent OS内核都支持multicast。

配置多播路由

命令route -n可以查看配置的路由信息。

1
2
3
4
5
6
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.204.0 0.0.0.0 255.255.252.0 U 1 0 0 eth0
224.0.0.0 0.0.0.0 224.0.0.0 U 0 0 0 lo
0.0.0.0 192.168.204.1 0.0.0.0 UG 0 0 0 eth0

或者/sbin/ip route show

1
2
3
4
# /sbin/ip route show
192.168.204.0/22 dev eth0 proto kernel scope link src 192.168.204.49 metric 1
224.0.0.0/3 dev lo scope link
default via 192.168.204.1 dev eth0 proto stati

如果你的网卡或者lo没有配置224.0.0.0这样的行, 需要配置:

1
route add -net 224.0.0.0 netmask 240.0.0.0 dev lo

我在本机测试, 这里使用的是lo本地回环接口 (local loopback interface)。

移除路由

1
route del -net 224.0.0.0 netmask 240.0.0.0 dev eth0

你可以在/etc/rc.d/rc.local中加上下面的一行, 这样服务器每次启动会自动配置路由。

1
route add -net 224.0.0.0 netmask 240.0.0.0 lo

IPv4 多播地址由1110开始, 这类地址为D类地址。 CIDR为 224.0.0.0/4. 包含的地址范围为 224.0.0.0 到 239.255.255.255. RFC 5771 和IETF BCP 51对地址有相应的定义.
240.0.0.0 二进制形式为11110000 00000000 00000000 00000000

检查多播地址
netstat -gn
输出

1
2
3
4
5
6
7
8
9
[root@colobu smallnest]# netstat -gn
IPv6/IPv4 Group Memberships
Interface RefCnt Group
--------------- ------ ---------------------
lo 2 224.0.0.1
eth0 1 224.0.0.1
lo 1 ff02::1
eth0 1 ff02::1:ff0c:4cf9
eth0 1 ff02::1

可以看到224.0.0.1这个IP地址。

Netty的多播设置

Netty下UDP多播的设置和UDP单播基本类似,只是增加了额外的参数。
Netty本身提供了多播的单元测试, 可以参考应用,非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public void testMulticast(Bootstrap sb, Bootstrap cb) throws Throwable {
MulticastTestHandler mhandler = new MulticastTestHandler();
sb.handler(new SimpleChannelInboundHandler<Object>() {
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
// Nothing will be sent.
}
});
cb.handler(mhandler);
sb.option(ChannelOption.IP_MULTICAST_IF, NetUtil.LOOPBACK_IF);
sb.option(ChannelOption.SO_REUSEADDR, true);
cb.option(ChannelOption.IP_MULTICAST_IF, NetUtil.LOOPBACK_IF);
cb.option(ChannelOption.SO_REUSEADDR, true);
cb.localAddress(addr.getPort());
Channel sc = sb.bind().sync().channel();
if (sc instanceof OioDatagramChannel) {
// skip the test for OIO, as it fails because of
// No route to host which makes no sense.
// Maybe a JDK bug ?
sc.close().awaitUninterruptibly();
return;
}
DatagramChannel cc = (DatagramChannel) cb.bind().sync().channel();
String group = "230.0.0.1";
InetSocketAddress groupAddress = new InetSocketAddress(group, addr.getPort());
cc.joinGroup(groupAddress, NetUtil.LOOPBACK_IF).sync();
sc.writeAndFlush(new DatagramPacket(Unpooled.copyInt(1), groupAddress)).sync();
assertTrue(mhandler.await());
// leave the group
cc.leaveGroup(groupAddress, NetUtil.LOOPBACK_IF).sync();
// sleep a second to make sure we left the group
Thread.sleep(1000);
// we should not receive a message anymore as we left the group before
sc.writeAndFlush(new DatagramPacket(Unpooled.copyInt(1), groupAddress)).sync();
mhandler.await();
sc.close().awaitUninterruptibly();
cc.close().awaitUninterruptibly();
}

如果你的多播地址绑定的网卡不是lo回环接口, 你需要用你绑定的网卡设置sb.option(ChannelOption.IP_MULTICAST_IF, NetUtil.LOOPBACK_IF);

参考

  1. http://www.hiperworks.com/pirdoc/cglx-doc/setup/network.html
  2. http://www.yolinux.com/TUTORIALS/LinuxTutorialNetworking.html#MULTICAST
  3. Linux下多播的配置
  4. http://en.wikipedia.org/wiki/Multicast_address