• Netty 系列九(支持UDP协议).


    一、基础知识

        UDP 协议相较于 TCP 协议的特点:

    1、无连接协议,没有持久化连接;
    2、每个 UDP 数据报都是一个单独的传输单元;
    3、一定的数据报丢失;
    4、没有重传机制,也不管数据报是否可达;
    5、速度比TCP快很多,可用来高效处理大量数据 —— 牺牲了握手以及消息管理机制。
    6、常用于音频、视频场景,可以忍受一定的数据包丢失,追求速度上的提升。

        TCP 协议采用的是一种叫做单播的传输形式,UDP 协议提供了向多个接收者发送消息的额外传输形式(多播、广播):

    单播(TCP 和 UDP):发送消息给一个由唯一的地址所标识的单一的网络目的地。
    多播(UDP):传输给一个预定义的主机组。
    广播(UDP):传输到网络(或者子网)上的所有主机。

    二、功能说明

        广播方:打开一个文件,通过 UDP 使用特殊的受限广播地址或者零网络地址 255.255.255.255,把每一行作为一个消息广播到一个指定的端口。

        接收方:通过 UDP 广播,只需简单地通过在指定的端口上启动一个监听程序,便可以创建一个事件监视器来接收日志消息。所有的在该 UDP 端口上监听的事件监听器都将会接收到广播信息。

    三、实现

        下图展示了怎么将我们的 文件数据 广播为 UDP消息:所有的将要被传输的数据都被封装在了 LogEvent 消息中。 LogEventBroadcaster 将把这些写入到 Channel 中,并通过 ChannelPipeline 发送它们,在那里它们将会被转换(编码)为 DatagramPacket 消息。最后,他们都将通过 UDP 被广播,并由远程节点(监视器)所捕获。

        Netty 中支持 UDP 协议主要通过以下相关类:

    DatagramPacket:使用 ByteBuf 作为数据源,是 UDP 协议传输的消息容器。

    DatagramChannel:扩展了 Netty 的 Channel 抽象以支持 UDP 的多播组管理,它的实现类 NioDatagramChannnel 用来和远程节点通信。

    Bootstrap:UDP 协议的引导类,使用 bind() 方法绑定 Channel。    

    public class LogEvent {
        public static final byte SEPARATOR = ':';
        /**
         * IP套接字地址(IP地址+端口号)
         */
        private final InetSocketAddress inetSocketAddress;
        /**
         * 文件名
         */
        private final String logfile;
        /**
         * 消息内容
         */
        private final String msg;
        
        private final long received;
    
        /**
         * 用于传入消息的构造函数
         *
         * @param inetSocketAddress
         * @param logfile
         * @param msg
         * @param received
         */
        public LogEvent(InetSocketAddress inetSocketAddress, String logfile, String msg, long received) {
            this.inetSocketAddress = inetSocketAddress;
            this.logfile = logfile;
            this.msg = msg;
            this.received = received;
        }
    
        /**
         * 用于传出消息的构造函数
         *
         * @param logfile
         * @param msg
         */
        public LogEvent(String logfile, String msg) {
            this(null, logfile, msg, -1);
        }
    
        public InetSocketAddress getInetSocketAddress() {
            return inetSocketAddress;
        }
    
        public String getLogfile() {
            return logfile;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public long getReceived() {
            return received;
        }
    }
    文件实体类 LogEvent.java
    public class LogEventEncoder extends MessageToMessageEncoder<LogEvent> {
        private final InetSocketAddress remoteAddress;
    
        public LogEventEncoder(InetSocketAddress remoteAddress) {
            this.remoteAddress = remoteAddress;
        }
    
    
        @Override
        protected void encode(ChannelHandlerContext ctx, LogEvent msg, List<Object> out) throws Exception {
            byte[] file = msg.getLogfile().getBytes(CharsetUtil.UTF_8);
            byte[] content = msg.getMsg().getBytes(CharsetUtil.UTF_8);
            ByteBuf byteBuf = ctx.alloc().buffer(file.length + content.length + 1);
            byteBuf.writeBytes(file);
            byteBuf.writeByte(LogEvent.SEPARATOR);
            byteBuf.writeBytes(content);
            out.add(new DatagramPacket(byteBuf, remoteAddress));
        }
    }
    编码器 LogEventEncoder.java

        该编码器实现了将 LogEvent 实体类内容转换为 DatagramPacket UDP数据报。

    public class LogEventBroadcaster {
        private final EventLoopGroup group;
        private final Bootstrap bootstrap;
        private final File file;
    
        public LogEventBroadcaster(InetSocketAddress address, File file) {
            group = new NioEventLoopGroup();
            bootstrap = new Bootstrap();
            bootstrap.group(group)
                    //引导该 NioDatagramChannel(无连接的)
                    .channel(NioDatagramChannel.class)
                    // 设置 SO_BROADCAST 套接字选项
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new LogEventEncoder(address));
            this.file = file;
        }
        
        public void run() throws InterruptedException, IOException {
            //绑定 Channel,UDP 协议的连接用 bind() 方法
            Channel channel = bootstrap.bind(0).sync().channel();
            long pointer = 0;
            //长轮询 监听是否有新的日志文件生成
            while (true) {
                long length = file.length();
                if (length < pointer) {
                    // 如果有必要,将文件指针设置到该文件的最后一个字节
                    pointer = length;
                } else {
                    RandomAccessFile raf = new RandomAccessFile(file, "r");
                    // 确保当前的文件指针,以确保没有任何的旧数据被发送
                    raf.seek(pointer);
                    String line;
                    while ((line = raf.readLine()) != null) {
                        //对于每个日志条目,写入一个 LogEvent 到 Channel 中,最后加入一个换行符号
                        channel.writeAndFlush(new LogEvent(file.getAbsolutePath(), line + System.getProperty("line.separator")));
                    }
                    pointer = raf.getFilePointer();
                    raf.close();
                }
                try {
                    // 休眠一秒,如果被中断,则退出循环,否则重新处理它
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    while (!Thread.interrupted()) {
                        break;
                    }
                }
            }
        }
    
        public void stop() {
            group.shutdownGracefully();
        }
    
        public static void main(String[] args) throws IOException, InterruptedException {
            InetSocketAddress socketAddress = new InetSocketAddress("255.255.255.255", 8888);
            File file = new File("E:\2018-09-12.log");
            LogEventBroadcaster logEventBroadcaster = new LogEventBroadcaster(socketAddress, file);
            try {
                logEventBroadcaster.run();
            } finally {
                logEventBroadcaster.stop();
            }
        }
    }

        现在,我们来测试一下这个 UDP 广播类,首先我们需要一个工具 nmap ,用它来监听 UDP 的 8888 端口,以接收我们广播的日志文件。下载地址: https://nmap.org/dist/nmap-7.70-win32.zip

        下载完成后,命令行进入安装目录,执行命令:ncat.exe -l -u -p 8888 ,监听 UDP 端口。

     

         当然,也可以自己写个测试类监听 UDP 端口,打印日志查看。这里我没有用 Netty 写监听类,直接用了 java 原生的 DatagramSocket 和 DatagramPacket 写的监听类,如下:

    public class UDPServer {
    
        public static void main(String[] args) {
            DatagramSocket server = null;
            try {
                server = new DatagramSocket(8888);
                byte[] datas = new byte[1024];
                //用一个字节数组接收UDP包,字节数组在传递给构造函数时是空的
                while (true) {
                    DatagramPacket datagramPacket = new DatagramPacket(datas, datas.length);
                    server.receive(datagramPacket);
                    System.out.println(new String(datas));
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                server.close();
            }
        }
    }
    UDPServer.java

        基于 Netty 的监听类实现可以参考我上传 GitHub 上的源代码。

    参考资料:《Netty IN ACTION》

    演示源代码:https://github.com/JMCuixy/NettyDemo/tree/master/src/main/java/org/netty/demo/udp

  • 相关阅读:
    StringBuffer与Stringbuilder
    String与其他类型的转换
    创建多进程的方法
    8.进程
    克隆虚拟机
    初探12306售票算法(二)-java代码实践
    初探12306售票算法(一)- 理论
    手机上mcd程序耗电异常问题相关
    vue版app上下拉加载
    css基础
  • 原文地址:https://www.cnblogs.com/jmcui/p/9636505.html
Copyright © 2020-2023  润新知