• Netty学习摘记 —— UDP广播事件


    本文参考

    本篇文章是对《Netty In Action》一书第十三章"使用UDP广播事件"的学习摘记,主要内容为广播应用程序的开发

    消息POJO

    我们将日志信息封装成名为LogEvent的POJO

    public final class LogEvent {
      public static final byte SEPARATOR = (byte) ':';
      private final InetSocketAddress source;
      private final String logfile;
      private final String msg;
      private final long received;

      //
    用于传出消息的构造函数
      public LogEvent(String logfile, String msg) {
        this(null, -1, logfile, msg);
      }

      //
    用于传入消息的构造函数
      public LogEvent(InetSocketAddress source, long received, String logfile, String msg) {
        this.source = source;
        this.logfile = logfile;
        this.msg = msg;
        this.received = received;
      }

      //
    返回发送 LogEvent 的源的 InetSocketAddress
      public
    InetSocketAddress getSource() {
        return source;
      }

      //
    返回所发送的 LogEvent 的日志文件的名称
      public String getLogfile() {
        return logfile;
      }

      //
    返回消息内容
      public String getMsg() {
        return msg;
      }

      //
    返回接收 LogEvent 的时间
      public long getReceivedTimestamp() {
        return received;
      }
    }

     

    编写广播者

    Netty 提供了大量的类来支持 UDP 应用程序的编写

    我们使用较多的是DatagramPacket和NioDatagramChannel

    Netty 的DatagramPacket用于和远端的UDP通信,是一个简单的消息容器,可以包装消息,接收方地址和发送方地址,我们可以将LogEvent消息转换为DatagramPacket后进行发送(需要扩展 Netty 的 MessageToMessageEncoder)

    The message container that is used for DatagramChannel to communicate with the remote peer.

    Netty的NioDatagramChannel用来实现发送和接收UDP消息

    An NIO datagram Channel that sends and receives an AddressedEnvelope .

    下图展示了广播方向的LogEvent消息流向

    下面是编码器的实现代码

    public class LogEventEncoder extends MessageToMessageEncoder<LogEvent> {
      private final InetSocketAddress remoteAddress;

      //LogEventEncoder
    创建了即将被发送到指定的 InetSocketAddress DatagramPacket 消息
      public LogEventEncoder(InetSocketAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
      }

      @Override
      protected void encode(ChannelHandlerContext channelHandlerContext,
      LogEvent logEvent, List<Object> out) throws Exception {
        byte[] file = logEvent.getLogfile().getBytes(CharsetUtil.UTF_8);
        byte[] msg = (logEvent.getMsg() + " ").getBytes(CharsetUtil.UTF_8);
        ByteBuf buf = channelHandlerContext.alloc()
          .buffer(file.length + msg.length + 1);
        //
    将文件名写入到 ByteBuf
        buf.writeBytes(file);
        //
    添加一个 SEPARATOR
        buf.writeByte(LogEvent.SEPARATOR);
        //
    将日志消息写入 ByteBuf
        buf.writeBytes(msg);
        //
    将一个拥有数据和目的地地址的新 DatagramPacket 添加到出站的消息列表中
        out.add(new DatagramPacket(buf, remoteAddress));
      }
    }

    最后对它进行引导,每秒从文件中读取一行数据,并且读完整个文件后又会从文件头开始重新读取数据

    命令行参数的第一个参数是远程主机接收消息的端口号,第二个参数是广播者本地文件路径

    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();
        //
    引导该 NioDatagramChannel(无连接的)
        bootstrap.group(group).channel(NioDatagramChannel.class)
          //
    设置 SO_BROADCAST 套接字选项
          .option(ChannelOption.SO_BROADCAST, true)
          .handler(new LogEventEncoder(address));
        this.file = file;
      }

      public void run() throws Exception {
        /
    /绑定 Channel
        Channel
    ch = bootstrap.bind(0).sync().channel();
        long pointer = 0;
        long len = file.length();
        //
    启动主处理循环
        while (true){
          if (len <= pointer) {
            // file was reset
            pointer = 0;
          } else {
            // Content was added
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            //
    设置当前的文件指针,以确保没有任何的旧日志被发送
            raf.seek(pointer);
            String line;
            while ((line = raf.readLine()) != null) {
              //
    对于每个日志条目,写入一个 LogEvent Channel
              ch.writeAndFlush(new LogEvent(null, -1, file.getAbsolutePath(), line));
              try {
                //
    休眠 1 秒,如果被中断,则退出循环;否则重新处理它
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                Thread.interrupted();
                break;
              }
            }
            //
    存储其在文件中的当前位置
            pointer = raf.getFilePointer();
            raf.close();
          }
        }
      }

      public void stop() {
        group.shutdownGracefully();
      }

      public static void main(String[] args) throws Exception {
        if (args.length != 2) {
          throw new IllegalArgumentException();
        }
        //
    创建并启动一个新的 LogEventBroadcaster 的实例
        LogEventBroadcaster broadcaster = new LogEventBroadcaster(
          new InetSocketAddress("255.255.255.255",
          Integer.parseInt(args[0])), new File(args[1]));
        try {
          broadcaster.run();
        }
        finally {
          broadcaster.stop();
        }
      }
    }

    我们注意到广播者绑定到了一个为0的端口号,因为UDP是无连接的协议,所以只需要为广播者分配一个临时的端口来发送消息,参数0代表绑定一个临时端口

    A port number of zero will let the system pick up an ephemeral port in a bind operation.

     

    使用netcat进行测试

  • 相关阅读:
    JavaScript 正则表达式(RegExp)
    impala 时间格式转换
    Informatica TO_BIGINT,TO_DECIMAL 转 字符串
    Hive metastore三种配置方式
    Windows10 从零搭建 Hadoop/Hive 环境及 Hive 入门
    Window 安装Hive
    使用dynamic-datasource-spring-boot-starter配置多数据源
    Sqoop 安装与简单测试
    使用dynamic-datasource-spring-boot-starter做多数据源及源码分析
    真正了解sqoop的一切
  • 原文地址:https://www.cnblogs.com/kuluo/p/12693800.html
Copyright © 2020-2023  润新知