Netty从入门到客户端与服务端通信——UDP协议

前言

不想多说什么,深刻的体会到学习方法的重要性,还有心态的问题,是否你能够沉得住气!耐的下心~这个过程感觉自己也是蛮拼的了~ 不过还好!终于让本姑娘有所收获了!!!


Netty宏观

如果最开始你去了解Netty你会发现真尼玛难!进程,线程,并发,NIo,IO,这么多鬼玩意。。。。首先我们先从宏观去来接他


What是Netty?

Netty是客户端于服务端进行通信的一个网络应用程序框架,他快速开发并且可以实现高性能协议和异步事件驱动。这些话是不是特别的高大尚啊~ 让我用一句大白话解决:他就是小明和小红两个人通过电话进行交流,并且这个过程中小红还可以让雪芬打电话。就是这么个玩意。。。


Netty来自哪?

Netty为什么现在这么好用并且这么流行,其实就是历史发展不断的更新不断的让我们程序员好干活~
Netty是jboss提供的一个jar包。这个jar包主要是针对的socket进行编程的。
1、IO:由于系统的扩大这个时候我们进行分模块话进行管理,然后使用RPC进行不同服务器之间远程通信,最开始的是时候我们使用的IO进行编程,但这种方式的编程也就是一个线程对应一个socket,并且是阻塞模式下的,阻塞的意思就是干这件事情的不能去做另外一件事情,所以非常的消耗资源。由于这些原因我们开发了另一种叫做NIO
2、NIO:NIO的重点突出就是在非阻塞下进行通信,他再设计上进行了优化,一个线程可以select选择多个socket,所以这个时候就方便了很多,但是出现了另一种问题,就是NIO从实质上并没有解决高并发的问题,所以这个时候Netty就出现了
3、Netty:Netty是基于NIO,他可以自己扩展自己的协议,并且是一个高并发的客户端和服务端的一个框架

NIO是面向缓冲区的,IO是面向的数据流

IO形式

这里写图片描述

NIO形式

这里写图片描述


同类技术?

同类技术处了是NIO,还有一个比Netty出现早的框架Mina框架
这里写图片描述


使用Netty的好处?

他简化网络应用的编程过程开发,例如UDP和TCP的socket服务开发,他功能比较强大,并且提供了编码好解码的能力,并且很多著名的框架都是底层采用Netty,比如Dubbo,服务提供者和消费者之间的通信。淘宝的消息中间件 RocketMQ 的消息生产者和消息消费者之间

  • 易用性好
  • 性能好
  • 健壮性好
  • 安全性好

UDP协议实战

这个Demo我大概研究了3天的时间,中途的时候有坚持不住的感觉,实在是没有接触过!但是通过我不懈努力,让我给搞定了!!!!!
我们项目主要是针对的是服务端,但是由于设备还没有下来得原因我只能自己模拟客户端,然后让他们直接进行通信,首先看服务端。


服务端

首先说一下大体的过程
1有一个boostrap服务者
2有两个干活的,也就是处理IO的,一个是boss用于监听端口,一个work干活的
3配置UDP管道,UDP管道主要使用的是NioDatagramChannel
4然后使用pipeline进行配置,可以实现编码和解码等功能,然后寻找到我们的执行的处理的Handler类
5配置我们的服务,例如选择是广播和单播,读和写缓存
6绑定端口,获取通道
7优雅的关闭服务器

扫描二维码关注公众号,回复: 1919543 查看本文章
/**
 * 服务监听、初始化类
 * */
public class UdpServer {
    //打印日志
    public static final Logger log = Logger.getLogger(UdpServer.class); 

    //给管道抽象出接口,给Channel更多的能力和配置,例如Channel的状态,参数,IO操作
    //使用ChannelPipeline实现自定义IO
    public static Channel channel;

    public static void run() throws Exception {  
         //启动服务
         EventLoopGroup workerGroup = new NioEventLoopGroup();
         //优化使用的线程
        final EventExecutorGroup group = new DefaultEventExecutorGroup(16);

        try {  
            Bootstrap b = new Bootstrap();//udp不能使用ServerBootstrap  
            b.group(workerGroup).channel(NioDatagramChannel.class)//设置UDP通道
                    //设置udp的管道工厂
                    .handler(new ChannelInitializer<NioDatagramChannel>(){
                       //NioDatagramChannel标志着是UDP格式的
                        @Override
                        protected void initChannel(NioDatagramChannel ch)
                                throws Exception {
                            // TODO Auto-generated method stub
                            //创建一个执行Handler的容器
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            //执行具体的处理器
                            pipeline.addLast(group,"handler",new UdpServerHandler());//消息处理器  
                        }

                    })//初始化处理器
                    //true / false 多播模式(UDP适用),可以向多个主机发送消息
                    .option(ChannelOption.SO_BROADCAST, true)// 支持广播  
                    .option(ChannelOption.SO_RCVBUF, 2048 * 1024)// 设置UDP读缓冲区为2M  
                    .option(ChannelOption.SO_SNDBUF, 1024 * 1024);// 设置UDP写缓冲区为1M  

            // 绑定端口,开始接收进来的连接  ,绑定的端口9999
            ChannelFuture f = b.bind(Constants.PORT).sync();  
            //获取channel通道
            channel=f.channel();
            System.out.println("UDP Server 启动!");
            // 等待服务器 socket 关闭 。  
            // 这不会发生,可以优雅地关闭服务器。  
            f.channel().closeFuture().sync();
        }finally {
            // 优雅退出 释放线程池资源
            group.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

服务端Handler编写

编写的Handler主要是实现SimpleChannelInboundHandler,重写他里面的方法,主要做的事情就是连接,获取消息,发送消息,主要介绍下面几个方法
channelActive:表示连接成功
channelRead0:等消息触发的时候走这个方法,主要是获取客户端发来的消息
exceptionCaught()捕获异常消息
writeAndFlush()回馈给客户端的消息
DatagramPacket:表示消息容器,里面包含了发送的消息和接受的消息

//1通过content()来获取消息
//2通过sender()来获取发送者的消息
//3recipient()来获取接受者的消息


public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket>{
    //打印日志
    public static final Logger log = Logger.getLogger(UdpServerHandler.class);
    //在事件循环中执行
    private EventExecutor executor ;
    //客户端与服务器端连接成功的时候触发
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("UDP通道已经连接");
        System.out.println("UDP通道已经连接");
        super.channelActive(ctx);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
            throws Exception
    {
        // 读取收到的数据
        ByteBuf buf = (ByteBuf) packet.copy().content();
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, CharsetUtil.UTF_8);
        System.out.println("【NOTE】>>>>>> 收到客户端的数据:"+body);

        // 回复一条信息给客户端
        ctx.writeAndFlush(new DatagramPacket(
                Unpooled.copiedBuffer("Hello,我是Server,我的时间戳是"+System.currentTimeMillis()
                        , CharsetUtil.UTF_8)
                , packet.sender())).sync();
    }
    //消息没有结束的时候触发
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    //捕获异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {
        //logger.log(Level.INFO, "AuthServerInitHandler exceptionCaught");
        log.error("UdpServerHandler exceptionCaught"+cause.getMessage());
        System.out.println("UdpServerHandler exceptionCaught"+cause.getMessage());
        cause.printStackTrace();
        ctx.close();
    }

客户端

客户端和服务端非常的相似,但是客户端要比服务端少一个工作者,并且还需要监听服务端的ip和端口,然后客户端自己也是要一个端口的,哈哈

  //创建于服务端就是连接操作,创建线程
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //开始客户端的服务,和管道的设置
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST,true)
                    .option(ChannelOption.SO_RCVBUF, 2048 * 1024)// 设置UDP读缓冲区为2M
                    .option(ChannelOption.SO_SNDBUF, 1024 * 1024)// 设置UDP写缓冲区为1M
                    .handler(new UdpServerHandler());
             //服务端绑定的管道的端口
            Channel ch = b.bind(8888).sync().channel();
            // 向网段类所有机器广播发UDP,这是想客户端发送内容
            ch.writeAndFlush(new DatagramPacket(
                            Unpooled.copiedBuffer("我是在客户端写的!!!", CharsetUtil.UTF_8),
                           //地址
                            new InetSocketAddress(
                                    "localhost",port
                            ))).sync();
            //如果超过长时间则表示超时
            if(!ch.closeFuture().await(15000)){
                System.out.println("查询超时!!!");
            }
        }
        finally {
            //优雅的关闭释放内存
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception{
        int port = 9999;
        if(args.length>0){
            try{
                port = Integer.parseInt(args[0]);
            }
            catch (NumberFormatException e){
                e.printStackTrace();
            }
        }
        new UdpClient().run(port);
    }

Handler

public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    //接受服务端发送的内容
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
            String  body =  msg.content().toString(CharsetUtil.UTF_8);
            System.out.println(body+"这是服务端发送的内容");
            //这里接收到服务端发送的1内容
        }

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

运行效果

客户端接受到服务端的消息
这里写图片描述
服务端介绍到客户端的消息
这里写图片描述


项目总体架构

这里写图片描述


总结

其实我感觉一定要耐心去看,把自己扎到代码堆里,浸泡几天,就OK了。不错的经历哦,后续还要接着完善,加油!

猜你喜欢

转载自blog.csdn.net/dtttyc/article/details/80894819
今日推荐