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进行测试

原文地址:https://www.cnblogs.com/kuluo/p/12693800.html