Netty的UDP广播发送与接收

想要的逻辑是这样的:
A向局域网内发送广播消息messageA;
B收到了messageA并直接使用既有的session或channel把需要回复的消息write回来就行了。

自己尝试了一下,记载一下使用中较为便利的写法。

客户端一般是这样写:

        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
		try {
     
            Bootstrap bootstrap = new Bootstrap()
					.group(eventLoopGroup)
					.channel(NioDatagramChannel.class)
					.option(ChannelOption.SO_BROADCAST, true)
					.handler(new NormalUDPClientHandler());

            bootstrap.bind(9999).sync().channel().closeFuture().await();
		} catch (InterruptedException e) {
     
            e.printStackTrace();
		} finally {
     
            eventLoopGroup.shutdownGracefully();
		}

就像创建一个普通的服务端一样,不过channel传入的通道类型为NioDatagramChannel,以及option传入的网络选项为ChannelOption.SO_BROADCAST。

上面这几行代码在《Netty权威指南》第十二章有类似出现,但原文是bind(0),这里是bind(9999),毕竟真实场景中还是很少拿0作为接收端口。或者说,原文想表达的意思就是这里的bootstrap只作为发送方存在,不接收回复。

初次看这段代码时被这个bind(0)误导了,以为是发送UDP必须如此…后面发现并不是,bind(0)代表着发送方会随机选择一个端口去发送UDP,类似于”万能端口“的概念,如果绑定一个合理值,那么发送端口就会被固定下来。

但其实发送UDP的重点除了上述所说的通道类型与网络选项外,还在于发送的数据包的特殊,与bind(0)关系不大。这个数据包也就是DatagramPacket,这里在handler中体现:

public class NormalUDPClientHandler extends ChannelInboundHandlerAdapter {
     

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
     
        L.d(ctx.channel().remoteAddress() + "");
        ctx.executor().parent().execute(new Runnable() {
     
			@Override
			public void run() {
     
				for (int i = 0; i < 10; i++) {
     
                    ctx.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("我在广播" + i, Charset.forName("utf-8")), new InetSocketAddress("255.255.255.255", 10000)));
					try {
     
                        TimeUnit.SECONDS.sleep(2);
					} catch (InterruptedException e) {
     
                        e.printStackTrace();
					}
				}
			}
		});

	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
     
        L.d(ctx.channel().remoteAddress() + "");
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
     
        DatagramPacket packet = (DatagramPacket) msg;
        ByteBuf byteBuf = packet.copy().content();
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        String content = new String(bytes);
        L.d(packet.sender()+","+content);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
     
        cause.printStackTrace();
	}
}

这里需要小小注意一下,如果在Handler中有延时发送UDP的需求,请务必使用子线程
看过netty源码就会知道,这里handler中回调的几个方法,都在同一个线程池的同一条线程中,会按照调用顺序依次调用。所以如果有延时,就使用本身自带的线程池别起一条线程去延时吧,或者不要使用sleep这种方法。

再来看这个DatagramPacket,这是netty自建的一个数据包,传入内容和目标地址即可,当然这里因为是广播,地址传入"255.255.255.255"就可以了,这里的10000端口就表示只有监听10000端口才能收到这条广播消息。

public final class DatagramPacket extends DefaultAddressedEnvelope<ByteBuf, InetSocketAddress> implements ByteBufHolder {
     
    public DatagramPacket(ByteBuf data, InetSocketAddress recipient) {
     
        super(data, recipient);
    }
}

另外这里发送方也在9999端口监听着广播是否有人回复,接下来看UDP接收端。

        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
		try {
     
            Bootstrap bootstrap = new Bootstrap()
					.group(eventLoopGroup)
					.channel(NioDatagramChannel.class)
					.option(ChannelOption.SO_BROADCAST, true)
					.handler(new NormalUDPServerHandler());

            bootstrap.bind(10000).sync().channel().closeFuture().await();
		} catch (InterruptedException e) {
     
            e.printStackTrace();
		} finally {
     
            eventLoopGroup.shutdownGracefully();
		}

接收端与发送端写法一模一样,只是监听的端口与handler不同。

public class NormalUDPServerHandler extends ChannelInboundHandlerAdapter {
     

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
     
        DatagramPacket packet = (DatagramPacket) msg;
        ByteBuf byteBuf = packet.copy().content();
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        String content = new String(bytes);
		
        L.d(packet.sender().toString() + "," + content);

        ByteBuf byteBuf1 = new UnpooledByteBufAllocator(false).buffer();
        byteBuf1.writeCharSequence(content, Charset.forName("utf-8"));
        ctx.writeAndFlush(new DatagramPacket(byteBuf1, packet.sender()));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
     
        cause.printStackTrace();
	}
}

这里的逻辑是把接收到的广播消息,原路返回给发送方,发送的地址信息可以使用packet.sender()直接获得。

这样一来,接收端的输出为:

20:01:43.043 (NormalUDPServerHandler.java:26)#channelRead-->:/192.168.0.104:9999,我在广播0
20:01:45.045 (NormalUDPServerHandler.java:26)#channelRead-->:/192.168.0.104:9999,我在广播1
20:01:47.047 (NormalUDPServerHandler.java:26)#channelRead-->:/192.168.0.104:9999,我在广播2

发送端的输出为:

20:01:43.043 (NormalUDPClientHandler.java:19)#channelActive-->:null
20:01:43.043 (NormalUDPClientHandler.java:48)#channelRead-->:/192.168.0.104:10000,我在广播0
20:01:45.045 (NormalUDPClientHandler.java:48)#channelRead-->:/192.168.0.104:10000,我在广播1
20:01:47.047 (NormalUDPClientHandler.java:48)#channelRead-->:/192.168.0.104:10000,我在广播2

你可能感兴趣的:(Java,netty,UDP,广播)