这种功能的实现首先考虑到的是广/多播,然后通过所受到的广播,获取到发送某种广播的ip地址,即实现“发现设备”功能。得到IP,即完成组网功能。
多播与广播
在这里选择的是多播。
选项 |
单播 |
多播(组播) |
广播 |
描述 |
主机之间一对一的通讯模式,网络中的交换机和路由器对数据只进行转发不进行复制。 |
主机之间一对一组的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据,网络中的交换机和路由器只向有需求者复制并转发其所需数据。 |
主机之间一对所有的通讯模式,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都可以接收到所有信息(不管你是否需要) |
优点 |
1)服务器及时响应客户机的请求 2)服务器针对每个客户不通的请求发送不通的数据,容易实现个性化服务。 |
1)需要相同数据流的客户端加入相同的组共享一条数据流,节省了服务器的负载。具备广播所具备的优点。 2)由于组播协议是根据接受者的需要对数据流进行复制转发,所以服务端的服务总带宽不受客户接入端带宽的限制。IP协议允许有2亿6千多万个组播,所以其提供的服务可以非常丰富。 3)此协议和单播协议一样允许在Internet宽带网上传输。 |
1)网络设备简单,维护简单,布网成本低廉 2)由于服务器不用向每个客户机单独发送数据,所以服务器流量负载极低 |
缺点 |
1)服务器针对每个客户机发送数据流,服务器流量=客户机数量×客户机流量;在客户数量大、每个客户机流量大的流媒体应用中服务器不堪重负。 2)现有的网络带宽是金字塔结构,城际省际主干带宽仅仅相当于其所有用户带宽之和的5%。如果全部使用单播协议,将造成网络主干不堪重负。现在的P2P应用就已经使主干经常阻塞。而将主干扩展20倍几乎是不可能。 |
1)与单播协议相比没有纠错机制,发生丢包错包后难以弥补,但可以通过一定的容错机制和QOS加以弥补。 2)现行网络虽然都支持组播的传输,但在客户认证、QOS等方面还需要完善,这些缺点在理论上都有成熟的解决方案,只是需要逐步推广应用到现存网络当中。 |
1)无法针对每个客户的要求和时间及时提供个性化服务。 2)网络允许服务器提供数据的带宽有限,客户端的最大带宽=服务总带宽。例如有线电视的客户端的线路支持100个频道(如果采用数字压缩技术,理论上可以提供500个频道),即使服务商有更大的财力配置更多的发送设备、改成光纤主干,也无法超过此极限。也就是说无法向众多客户提供更多样化、更加个性化的服务。 3)广播禁止允许在Internet宽带网上传输。 |
组网流程
服务端与客户端同时加入一个多播,然后客户端不断发送寻找主机的报文,知道得到服务端的响应。获取到服务端的响应后,即可得到主机的IP,从而停止发送寻找主机的报文,并开始着手进行连接主机,即完成自动组网。

实践代码
客户端:不断地向目标组内发送UDP报文,直到得到主机的回应或被关闭。
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
| EventLoopGroup group = new OioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channelFactory(new ChannelFactory<Channel>() { public Channel newChannel() { return new OioDatagramChannel(); } }) .option(ChannelOption.SO_REUSEADDR, true) .handler(new ChannelInitializer<DatagramChannel>() { @Override public void initChannel(DatagramChannel ch) throws Exception { ch.pipeline().addLast(new UdpPacketDecoder()); ch.pipeline().addLast(new UdpPacketEncoder()); ch.pipeline().addLast(new ClientMulticastHandler()); } }); ch = (DatagramChannel) b.bind(port).sync().channel(); ch.joinGroup(groupAddress); startSearch();
ch.closeFuture().sync(); Log.d("MulticastClient","MulticastClient.run stop"); } catch (InterruptedException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } setCallback(null);
|
发送UDP报文
1 2 3 4 5 6 7 8 9 10 11
| public void startSearch() { if (search != null) { search.cancel(true); } search = ch.eventLoop().scheduleAtFixedRate(new Runnable() { public void run() { ch.writeAndFlush(UdpPacket.newSearchRequest(serverAddress)); } }, 0, 1, TimeUnit.SECONDS); }
|
此处再贴上客户端收到服务端的回应之后的处理逻辑,也就是ClientMulticastHandler
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class ClientMulticastHandler extends SimpleChannelInboundHandler<UdpPacket> { @Override protected void channelRead0(ChannelHandlerContext ctx, UdpPacket msg) throws Exception { if (msg.isSearchResponse()) { MulticastClient.Callback callBack = MulticastClientCallback.INSTANCE.getCallback(); if (callBack != null) { callBack.onSearch(msg.getDstAddress().getHostString(), msg.getPort()); } } }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
|
服务端:监听
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
| EventLoopGroup group = new OioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channelFactory(new io.netty.channel.ChannelFactory<Channel>() { public Channel newChannel() { return new OioDatagramChannel(); } }) .option(ChannelOption.SO_REUSEADDR, true) .handler(new ChannelInitializer<DatagramChannel>() { @Override public void initChannel(DatagramChannel ch) throws Exception { ch.pipeline().addLast(new UdpPacketDecoder()); ch.pipeline().addLast(new UdpPacketEncoder()); ch.pipeline().addLast(new ServerMulticastHandler()); } });
ch = (DatagramChannel)b.bind(port).sync().channel();
ch.joinGroup(groupAddress).sync();
ch.closeFuture().sync(); System.out.println("MulticastServer stop"); } catch (InterruptedException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); }
|
对“寻找主机请求”的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class ServerMulticastHandler extends SimpleChannelInboundHandler<UdpPacket> { @Override protected void channelRead0(ChannelHandlerContext ctx, UdpPacket msg) throws Exception { if (msg.isSearchRequset()) { ctx.channel().writeAndFlush( UdpPacket.newSearchResponse(Ip.getLocalIp(ctx.channel()), Ip.SERVER_TCP_PORT, msg.getDstAddress())); } }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
|
至此,通过多播组网的这一部分基本完成。
参考: