使用Netty+SpringBoot方式可以快速地开发一套基于UDP协议的服务端程序,同样的也可以开发客户端,一般使用UDP都是使用原生的方式,发送消息后就不管不问,也就是不需要确定消息是否收到,这里使用Netty创建的客户端和服务端倒是能够类似http协议那样请求数据,得到返回数据,实际上得到的就是服务端原路返回的数据。
1、这里也使用SpringBoot+Netty创建,pom.xml文件导入依赖包
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <dependencies> <!--web模块的启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- netty依赖 springboot2.x自动导入版本 --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> </dependencies>
2、Netty客户端的类,包含main方法,这里就没有使用SpringBoot方式的启动
package boot.netty.udp.client; import java.net.InetSocketAddress; import boot.netty.udp.client.adapter.BootNettyUdpClientSimpleChannelInboundHandler; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.util.CharsetUtil; public class BootNettyUdpClient { public void bind(String address, int port, String data) { EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try { Bootstrap clientBootstrap = new Bootstrap(); clientBootstrap = clientBootstrap.group(eventLoopGroup); clientBootstrap = clientBootstrap.channel(NioDatagramChannel.class); clientBootstrap = clientBootstrap.option(ChannelOption.SO_BROADCAST, true); clientBootstrap = clientBootstrap.handler(new BootNettyUdpClientSimpleChannelInboundHandler()); Channel channel = clientBootstrap.bind(0).sync().channel(); channel.writeAndFlush(new DatagramPacket( Unpooled.copiedBuffer(data, CharsetUtil.UTF_8), new InetSocketAddress(address,port))).sync(); // 与BootNettyUdpClientSimpleChannelInboundHandler中的ctx.channel().id().toString()是一样的值 System.out.println("channnel id = "+channel.id().toString()); // 方式一:查询等待超时 单位s 等待服务端原路返回的消息, // 在channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)方法中 // 收到消息后可主动关闭channel,此处等待自然释放 channel.closeFuture().await(10000); // 方式二:直接等待服务端原路返回后在channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)方法中 // 收到消息后可主动关闭channe // 若服务端没有原路返回消息或者消息未收到将会一直等待,浪费资源 //channel.closeFuture().sync(); } catch (Exception e) { // TODO: handle exception } finally { System.out.println("netty client udp close!"); eventLoopGroup.shutdownGracefully(); } } public static void main(String[] args) { // 向网段内的所有机器广播UDP消息,这个没试过是不是这个原理 // new BootNettyUdpClient().bind("255.255.255.255",9999,"I am client"); // 指定某个套接字地址和发送的内容可以发送消息 // 该方式也可以封装成一个udp的客户端的send类 new BootNettyUdpClient().bind("127.0.0.1",9999,"I am client"); } }
3、服务端I/O数据读写处理类
package boot.netty.udp.client.adapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; import io.netty.util.CharsetUtil; public class BootNettyUdpClientSimpleChannelInboundHandler extends SimpleChannelInboundHandler<DatagramPacket> { @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { try { String strdata = msg.content().toString(CharsetUtil.UTF_8); //打印收到的消息 System.out.println("msg---"+strdata); // 与BootNettyUdpClient中的channel.id().toString()是一样的值 System.out.println(ctx.channel().id().toString()); // 收到服务端原路返回的消息后,不需要再次向服务端发送消息, 可以在这里暴力关闭,也可以在 channelReadComplete(ChannelHandlerContext ctx)内 // ctx.close(); } catch (Exception e) { } } /** * 重写方法 * 结构: * 1.public class BootNettyUdpClientSimpleChannelInboundHandler extends SimpleChannelInboundHandler<DatagramPacket> * * 2.public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter * * 3.public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler * * ChannelInboundHandlerAdapter类有诸多方法可以重写,可以根据具体需求来写 * */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { super.channelReadComplete(ctx); System.out.println("关闭channel"); ctx.close(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
4、我们可以启动一个服务端(SpringBoot搭建基于UDP协议的服务端)然后调用main方法,可以看到服务端可以收到消息,客户端也可以收到服务端原路返回的时间戳消息
public static void main(String[] args) { // 向网段内的所有机器广播UDP消息,这个没试过是不是这个原理 // new BootNettyUdpClient().bind("255.255.255.255",9999,"I am client"); // 指定某个套接字地址和发送的内容可以发送消息 // 该方式也可以封装成一个udp的客户端的send类 new BootNettyUdpClient().bind("127.0.0.1",9999,"I am client"); }