前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty 系列九(支持UDP协议).

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

作者头像
JMCui
发布2018-09-27 10:05:46
1.9K0
发布2018-09-27 10:05:46
举报
文章被收录于专栏:JMCuiJMCui

一、基础知识

    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。    

代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
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));
    }
}

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

代码语言:javascript
复制
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 写的监听类,如下:

代码语言:javascript
复制
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();
        }
    }
}

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

参考资料:《Netty IN ACTION》

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-09-13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、基础知识
  • 二、功能说明
  • 三、实现
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档