Netty实战之UDP广播实现

UDP 广播服务器

UDP 不需要连接,直接向服务器发送连接请求,而不会考虑是否连接成功

首先项目目的是读取本地日志文件,然后一行一行打印出消息

1. 设置 LogEvent

首先设置消息对象 LogEvent

package UDP;

import java.net.InetSocketAddress;

public final class LogEvent {
    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;
    }

    public InetSocketAddress getSource() { //返回地址
        return source;
    }

    public String getLogfile() { //文件名称
        return logfile;
    }

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

    public long getReceivedTimestamp() {  //返回时间,是一个长整型数据
        return received;
    }
}

2. 编码器

LogEvent消息类型需要进行转换,才能够被Netty框架处理

因此如何转换消息类型时关键,这里采用DatagramPacket 进行消息的接受,并在Channel中流动

package UDP;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil;

import java.net.InetSocketAddress;
import java.util.List;

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

    LogEventEncoder(InetSocketAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, LogEvent msg, List<Object> out) throws Exception {
        // 将消息内容转为byte类型存储,以待传入byteBuf
        byte[] file = msg.getLogfile().getBytes(CharsetUtil.UTF_8);
        byte[] message = msg.getMsg().getBytes(CharsetUtil.UTF_8);
        
        ByteBuf byteBuf = ctx.alloc().buffer(file.length+message.length+1);// 分配byteBuf的内存
        
        byteBuf.writeBytes(file);// 将file内容写入byteBuf
        byteBuf.writeByte(LogEvent.SEPARATOR);// 写入指定的分隔符
        byteBuf.writeBytes(message);
        out.add(new DatagramPacket(byteBuf, remoteAddress));// 将datagrampacket写入out列,进行处理
    }
}

DatagramPacket 是一个指明消息地址和消息内容的信息包裹,因此表现为

new DatagramPacket(ByteBuf buf, InetSocketAddress remoteAddress)

3. 广播器

广播器从文件中读取日志,并解码后传入Channel进行发送

广播器要有地址和传输的文件,才能进行读取并转为LogEvent类型,然后发送给地址内的各个接收点

package UDP;

import io.netty.bootstrap.Bootstrap;
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.nio.NioDatagramChannel;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;

public class LogEventBroadcaster {
    private File file;// 日志文件地址
    private InetSocketAddress address;// 目标地址

    // 构造函数指定传输地址和文件地址,才能进行下一步操作
    public LogEventBroadcaster(InetSocketAddress address, File file){
        this.address = address;
        this.file = file;
    }

    public static void main(String[] args) throws Exception {
        File file = new File("C:\\Users\\root\\IdeaProjects\\SimpleNetty\\src\\main\\java\\Demo\\file.txt");
        InetSocketAddress address = new InetSocketAddress("255.255.255.255", 9999);
        new LogEventBroadcaster(address, file).run();
    }

    public void run() throws Exception{
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)
                .channel(NioDatagramChannel.class) // 使用非阻塞DatagramChannel
                .option(ChannelOption.SO_BROADCAST, true)// 设置BROADCAST广播
                .handler(new LogEventEncoder(address));// 添加处理器
        // 获取引导bootstrap对应的Channel
        Channel ch = b.bind(0).syncUninterruptibly().channel();

        System.out.println("LogEventBroadcaster running!");
        long pointer = 0;// 文件指针

        for (;;){
            long len = file.length();
            if (len < pointer){
                pointer = len;
            } else if (len > pointer) {// 从0开始读取文件
                RandomAccessFile raf = new RandomAccessFile(this.file, "r");
                raf.seek(pointer);// 跳到指针的位置
                String line;
                while ((line = raf.readLine())!=null){// 逐行读取文件内容,指针随之移动,直到文件末尾
                    // 将logevent消息传输给Channel
                    ch.writeAndFlush(new LogEvent(null, -1, file.getAbsolutePath(), line));
                }
                pointer = raf.getFilePointer();// 获取当前指针位置,如果循环被中断,则重启时不会读取旧文件
                raf.close();// 关闭文件
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException  e){
                Thread.interrupted();
                break;
            }
        }
        group.shutdownGracefully();
    }
}

4. 解码器

接收者将收到的DatagramPacket进行解码为String类型,转换为byte内容进行处理

package UDP;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.util.CharsetUtil;

import java.nio.charset.StandardCharsets;
import java.util.List;

public class LogEventDecoder extends MessageToMessageDecoder<DatagramPacket> {

    @Override
    protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
        ByteBuf byteBuf = packet.content();// 消息包的内容存入byteBuf中
        // 获取分隔符的索引,便于分割bytebuf为两部分
        int idx = byteBuf.indexOf(0, byteBuf.readableBytes(), LogEvent.SEPARATOR);
        // 将bytebuf转化为两部分的String类型
        String logfile = byteBuf.slice(0, idx).toString(StandardCharsets.UTF_8);// 提取文件名
        String message = byteBuf.slice(idx+1, byteBuf.readableBytes()).toString(CharsetUtil.UTF_8);
        // 转化为LogEvent类型
        LogEvent event = new LogEvent(packet.sender(), System.currentTimeMillis(), logfile, message);
        out.add(event);
    }
}

5. 处理器

对LogEvent进行处理,将其转化为能够阅读的格式,打印到屏幕

package UDP;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class LogEventHandler extends SimpleChannelInboundHandler<LogEvent> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, LogEvent event) throws Exception {

        String builder = event.getReceivedTimestamp() +
                " [" +
                event.getSource().toString() +
                "] [" +
                event.getLogfile() +
                "] : " +
                event.getMsg();//3
        System.out.println(builder); //4
    }

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

6. 监听器

package UDP;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;

import java.net.InetSocketAddress;

public class LogEventMonitor {

    private final Bootstrap bootstrap;
    private final EventLoopGroup group;
    public LogEventMonitor(InetSocketAddress address) {
        group = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.group(group)  //1
                .channel(NioDatagramChannel.class)
                .option(ChannelOption.SO_BROADCAST, true)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        pipeline.addLast(new LogEventDecoder());  // 先将packet数据解码为LogEvent
                        pipeline.addLast(new LogEventHandler()); // 添加处理LogEvent的Handler
                    }
                }).localAddress(address);
    }

    public Channel bind() {
        return bootstrap.bind().syncUninterruptibly().channel();  //3
    }

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

    public static void main(String[] args) throws Exception {

        LogEventMonitor monitor = new LogEventMonitor(new InetSocketAddress(9999));  //4
        try {
            Channel channel = monitor.bind();
            System.out.println("LogEventMonitor running");

            channel.closeFuture().await();
        } finally {
            monitor.stop();
        }
    }
}

先运行监听器,再运行广播器,否则广播器会直接发送消息,监听器就接受不到了

你可能感兴趣的:(netty)