• Netty 学习 一、初识Netty【原创】


      在过去几年的工作和学习中,比较关注高层次的应用开发,对底层探究较少。实现Web应用的开发,主要依赖Tomcat、Apache等应用服务器,程序员无需了解底层协议,但同样限制了应用的性能和效率。现在开始探究网络编程,Netty是一个非常重要的技术。会持续更新有关Netty学习的相关文章,共勉。

         Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

         'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.

    一、实现Discard协议。顾名思义,Discard协议是指接受并放弃所有请求,不做任何回应。

     实现Handler

    **
     * 服务端处理通道.这里只是打印一下请求的内容,并不对请求进行任何的响应 DiscardServerHandler 继承自
     * ChannelHandlerAdapter, 这个类实现了ChannelHandler接口, ChannelHandler提供了许多事件处理的接口方法,
     * 然后你可以覆盖这些方法。 现在仅仅只需要继承ChannelHandlerAdapter类而不是你自己去实现接口方法。
     *
     */
    public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
        /**
         * 这里我们覆盖了chanelRead()事件处理方法。 每当从客户端收到新的数据时, 这个方法会在收到消息时被调用,
         * 这个例子中,收到的消息的类型是ByteBuf
         * 
         * @param ctx
         *            通道处理的上下文信息
         * @param msg
         *            接收的消息
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
    
            try {
                
                ByteBuf in = (ByteBuf) msg;
                // 打印客户端输入,传输过来的的字符
                System.out.print(in.toString(CharsetUtil.UTF_8));
                
            } finally {
                /**
                 * ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。
                 * 请记住处理器的职责是释放所有传递到处理器的引用计数对象。
                 */
                // 抛弃收到的数据
                ReferenceCountUtil.release(msg);
            }
    
        }
    
        /***
         * 这个方法会在发生异常时触发
         * 
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            /**
             * exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用,即当 Netty 由于 IO
             * 错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来 并且把关联的 channel
             * 给关闭掉。然而这个方法的处理方式会在遇到不同异常的情况下有不 同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。
             */
            // 出现异常就关闭
            cause.printStackTrace();
            ctx.close();
        }
    
    }

    实现DsicardServer

    /**
     * 丢弃任何进入的数据 启动服务端的DiscardServerHandler
     */
    public class DiscardServer {
        private int port;
    
        public DiscardServer(int port) {
            super();
            this.port = port;
        }
    
        public void run() throws Exception {
    
            /***
             * NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,
             * Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。 在这个例子中我们实现了一个服务端的应用,
             * 因此会有2个NioEventLoopGroup会被使用。 第一个经常被叫做‘boss’,用来接收进来的连接。
             * 第二个经常被叫做‘worker’,用来处理已经被接收的连接, 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。
             * 如何知道多少个线程已经被使用,如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的实现,
             * 并且可以通过构造函数来配置他们的关系。
             * 
             */
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            System.out.println("准备运行端口:" + port);
            try {
                /**
                 * ServerBootstrap 是一个启动NIO服务的辅助启动类 你可以在这个服务中直接使用Channel
                 */
                ServerBootstrap b = new ServerBootstrap();
                /**
                 * 这一步是必须的,如果没有设置group将会报java.lang.IllegalStateException: group not
                 * set异常
                 */
                b = b.group(bossGroup, workerGroup);
                /***
                 * ServerSocketChannel以NIO的selector为基础进行实现的,用来接收新的连接
                 * 这里告诉Channel如何获取新的连接.
                 */
                b = b.channel(NioServerSocketChannel.class);
                /***
                 * 这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。 ChannelInitializer是一个特殊的处理类,
                 * 他的目的是帮助使用者配置一个新的Channel。
                 * 也许你想通过增加一些处理类比如NettyServerHandler来配置一个新的Channel
                 * 或者其对应的ChannelPipeline来实现你的网络程序。 当你的程序变的复杂时,可能你会增加更多的处理类到pipline上,
                 * 然后提取这些匿名类到最顶层的类上。
                 */
                b = b.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new DiscardServerHandler());// demo1.discard
                        // ch.pipeline().addLast(new
                        // ResponseServerHandler());//demo2.echo
                        // ch.pipeline().addLast(new
                        // TimeServerHandler());//demo3.time
                    }
                });
                /***
                 * 你可以设置这里指定的通道实现的配置参数。 我们正在写一个TCP/IP的服务端,
                 * 因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。
                 * 请参考ChannelOption和详细的ChannelConfig实现的接口文档以此可以对ChannelOptions的有一个大概的认识。
                 */
                b = b.option(ChannelOption.SO_BACKLOG, 128);
                /***
                 * option()是提供给NioServerSocketChannel用来接收进来的连接。
                 * childOption()是提供给由父管道ServerChannel接收到的连接,
                 * 在这个例子中也是NioServerSocketChannel。
                 */
                b = b.childOption(ChannelOption.SO_KEEPALIVE, true);
                /***
                 * 绑定端口并启动去接收进来的连接
                 */
                ChannelFuture f = b.bind(port).sync();
                /**
                 * 这里会一直等待,直到socket被关闭
                 */
                f.channel().closeFuture().sync();
            } finally {
                /***
                 * 关闭
                 */
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            }
        }
    
        //将规则跑起来
        public static void main(String[] args) throws Exception {
            int port;
            if (args.length > 0) {
                port = Integer.parseInt(args[0]);
            } else {
                port = 8080;
            }
            new DiscardServer(port).run();
            System.out.println("server:run()");
        }
    }

    测试方法:

          telnet 127.0.0.1 8080     Windows启动Telnet Client,参考 https://jingyan.baidu.com/article/54b6b9c0813a362d593b4775.html

          测试发现,只要在telnet命令行,输入内容,即可在服务器收到并打印。有时单个字母输出,有时多个字母一起输出,这是为什么呢?

    二、实现Echo,以输入内容作为应答。

           其实,我们只需修改DiscardServerHandler 的channelRead函数即可,如下所示。

     @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
    
            try {
                
                ByteBuf in = (ByteBuf) msg;
                // 打印客户端输入,传输过来的的字符
                System.out.print(in.toString(CharsetUtil.UTF_8));
                ctx.write(msg);
                ctx.flush();
            } finally {
                /**
                 * ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。
                 * 请记住处理器的职责是释放所有传递到处理器的引用计数对象。
                 */
            }
    
        }

    总结:通过DiscardServerHandler和DiscardServer两个类,我们搭建起了简单的TCP Server。
         

  • 相关阅读:
    采集智能电表
    未能写入输出文件“c:\WINDOWS\Microsoft.NET\Framework\.....dll”“拒绝访问。
    随笔写写jquery
    随便写写,,
    写写Ajaxpro
    C# 给程序加日志功能。
    Oracle_Database_11g_标准版_企业版__下载地址_详细列表
    通过C#发送自定义的html格式邮件
    C# 加密解密链接字符串
    获取本地 有线 正在使用的网卡信息
  • 原文地址:https://www.cnblogs.com/make-it-easy/p/8504764.html
Copyright © 2020-2023  润新知