• 沉淀再出发:关于netty的一些理解和使用


    沉淀再出发:关于netty的一些理解和使用

    一、前言

        Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。netty在底层的数据通信和封装之中有着重要的作用,下面我们就来看看netty的简单使用过程,以及背后的原理。

    二、netty的简单使用

      2.1、netty的环境部署和使用

       在这里我们使用myeclipse平台,maven管理工具进行开发,其实使用eclipse或者其他软件也可以。首先我们新建一个maven项目,项目名和包名自定:

        之后我们修改pom.xml文件,增加netty依赖:

        保存之后,系统就会自动为我们下载和安装了,非常的方便,这样,我们的环境就部署完毕了。

       2.2、一个简单的案例

        下面我们看一个简单地案例:

        我们新建一个包,然后写入两个文件:

        首先我们编写一个处理连接的类 HelloServerHandler :

     1 package com.coder.server;
     2 
     3 import io.netty.buffer.ByteBuf;
     4 import io.netty.channel.ChannelHandlerContext;
     5 import io.netty.channel.ChannelInboundHandlerAdapter;
     6 import io.netty.util.CharsetUtil;
     7 import io.netty.util.ReferenceCountUtil;
     8 
     9 
    10 public class HelloServerHandler extends ChannelInboundHandlerAdapter {
    11     /**
    12      * 收到数据时调用
    13      */
    14     @Override
    15     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    16         try {
    17             ByteBuf in = (ByteBuf)msg;
    18             System.out.print(in.toString(CharsetUtil.UTF_8));
    19         } finally {
    20             // 抛弃收到的数据
    21             ReferenceCountUtil.release(msg);
    22         }
    23     }
    24     
    25     /**
    26      * 当Netty由于IO错误或者处理器在处理事件时抛出异常时调用
    27      */
    28     @Override
    29     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    30         // 当出现异常就关闭连接
    31         cause.printStackTrace();
    32         ctx.close();
    33     }
    34 }

       其次,我们编写接收连接,并且派发和处理的类 HelloServer :

     1 package com.coder.server;
     2 
     3 import io.netty.bootstrap.ServerBootstrap;
     4 import io.netty.channel.ChannelFuture;
     5 import io.netty.channel.ChannelInitializer;
     6 import io.netty.channel.ChannelOption;
     7 import io.netty.channel.EventLoopGroup;
     8 import io.netty.channel.nio.NioEventLoopGroup;
     9 import io.netty.channel.socket.SocketChannel;
    10 import io.netty.channel.socket.nio.NioServerSocketChannel;
    11 
    12 public class HelloServer {
    13     private int port;
    14     
    15     public HelloServer(int port) {
    16         this.port = port;
    17     }
    18     
    19     public void run() throws Exception {
    20         EventLoopGroup bossGroup = new NioEventLoopGroup();        // 用来接收进来的连接
    21         EventLoopGroup workerGroup = new NioEventLoopGroup();    // 用来处理已经被接收的连接
    22         System.out.println("准备运行端口:" + port);
    23         
    24         try {
    25             ServerBootstrap b = new ServerBootstrap();
    26             b.group(bossGroup, workerGroup)
    27             .channel(NioServerSocketChannel.class)            // 这里告诉Channel如何接收新的连接
    28             .childHandler( new ChannelInitializer<SocketChannel>() {
    29                 @Override
    30                 protected void initChannel(SocketChannel ch) throws Exception {
    31                     // 自定义处理类
    32                     ch.pipeline().addLast(new HelloServerHandler());
    33                 }
    34             })
    35             .option(ChannelOption.SO_BACKLOG, 128)
    36             .childOption(ChannelOption.SO_KEEPALIVE, true);
    37             
    38             // 绑定端口,开始接收进来的连接
    39             ChannelFuture f = b.bind(port).sync();
    40             
    41             // 等待服务器socket关闭
    42             f.channel().closeFuture().sync();
    43         } catch (Exception e) {
    44             workerGroup.shutdownGracefully();
    45             bossGroup.shutdownGracefully();
    46         }
    47     }
    48     
    49     public static void main(String[] args) throws Exception {
    50         int port = 12345;
    51         new HelloServer(port).run();
    52     }
    53 }

         然后运行,等待连接就好了,那么问题来了,使用什么进行连接呢?在windows中,我们可以使用Telnet,这个比较方便和简单,但是我们需要打开控制面板的程序和功能模块,并且启动服务,之后最好重启一下电脑:

     

        下面我们运行程序,并使用Telnet客户端测试一下:

       在telnet中‘ctrl+]’可以显示输入的文字,否则将看不到输入。

    三、使用netty自定义时间服务器

          本例中我们试图在服务器和客户端连接被创立时发送一个消息,然后在客户端解析收到的消息并输出。并且,在这个项目中使用 POJO 代替 ByteBuf 来作为传输对象。

     3.1、pojo对象创建

       Time 类:

     1 package com.coder.pojo;
     2 
     3 import java.util.Date;
     4 
     5 /**
     6  * 自定义时间数据类
     7  *
     8  */
     9 public class Time {
    10     private final long value;
    11 
    12     public Time() {
    13         // 除以1000是为了使时间精确到秒
            //注意这里的this,其实就是调用了 public Time(long value) ,并且更加的方便和快捷。
    14 this(System.currentTimeMillis() / 1000L); 15 } 16 17 public Time(long value) { 18 this.value = value; 19 } 20 21 public long value() { 22 return value; 23 } 24 25 @Override 26 public String toString() { 27 return new Date((value()) * 1000L).toString(); 28 } 29 }

     3.2、服务器程序

       TimeEncoderPOJO类:

     1 package com.coder.server;
     2 
     3 import com.coder.pojo.Time;
     4 
     5 import io.netty.buffer.ByteBuf;
     6 import io.netty.channel.ChannelHandlerContext;
     7 import io.netty.handler.codec.MessageToByteEncoder;
     8 
     9 /**
    10  * 服务器数据编码类
    11  *
    12  */
    13 public class TimeEncoderPOJO extends MessageToByteEncoder<Time> {
    14 
    15     // 发送数据时调用
    16     @Override
    17     protected void encode(ChannelHandlerContext ctx, Time msg, ByteBuf out) throws Exception {
    18         // 只传输当前时间,精确到秒
    19         out.writeInt((int)msg.value());
    20     }
    21 
    22 }

       TimeServerHandlerPOJO类:连接建立并且准备通信的时候进行处理,发送当前时间,并增加监听。

     1 package com.coder.server;
     2 
     3 import com.coder.pojo.Time;
     4 
     5 import io.netty.channel.ChannelFuture;
     6 import io.netty.channel.ChannelFutureListener;
     7 import io.netty.channel.ChannelHandlerContext;
     8 import io.netty.channel.ChannelInboundHandlerAdapter;
     9 
    10 /**
    11  * 服务器解码器
    12  * 连接建立时发送当前时间
    13  *
    14  */
    15 public class TimeServerHandlerPOJO extends ChannelInboundHandlerAdapter {
    16     /**
    17      * 连接建立的时候并且准备进行通信时被调用
    18      */
    19     @Override
    20     public void channelActive(final ChannelHandlerContext ctx) throws Exception {
    21         // 发送当前时间信息
    22         ChannelFuture f = ctx.writeAndFlush(new Time());
    23         // 发送完毕之后关闭 Channel
    24         f.addListener(ChannelFutureListener.CLOSE);
    25     }
    26     
    27     @Override
    28     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    29         cause.printStackTrace();
    30         ctx.close();
    31     }
    32 }

       TimeServerPOJO类:服务器的主程序

     1 package com.coder.server;
     2 
     3 import io.netty.bootstrap.ServerBootstrap;
     4 import io.netty.channel.ChannelFuture;
     5 import io.netty.channel.ChannelInitializer;
     6 import io.netty.channel.ChannelOption;
     7 import io.netty.channel.EventLoopGroup;
     8 import io.netty.channel.nio.NioEventLoopGroup;
     9 import io.netty.channel.socket.SocketChannel;
    10 import io.netty.channel.socket.nio.NioServerSocketChannel;
    11 
    12 public class TimeServerPOJO {
    13 private int port;
    14     
    15     public TimeServerPOJO(int port) {
    16         this.port = port;
    17     }
    18     
    19     public void run() throws Exception {
    20         EventLoopGroup bossGroup = new NioEventLoopGroup();        // 用来接收进来的连接
    21         EventLoopGroup workerGroup = new NioEventLoopGroup();    // 用来处理已经被接收的连接
    22         System.out.println("准备运行端口:" + port);
    23         
    24         try {
    25             ServerBootstrap b = new ServerBootstrap();        // 启动NIO服务的辅助启动类
    26             b.group(bossGroup, workerGroup)
    27             .channel(NioServerSocketChannel.class)            // 这里告诉Channel如何接收新的连接
    28             .childHandler( new ChannelInitializer<SocketChannel>() {
    29                 @Override
    30                 protected void initChannel(SocketChannel ch) throws Exception {
    31                     // 自定义处理类
    32                     // 注意添加顺序
    33                     ch.pipeline().addLast(new TimeEncoderPOJO(),new TimeServerHandlerPOJO());
    34                 }
    35             })
    36             .option(ChannelOption.SO_BACKLOG, 128)
    37             .childOption(ChannelOption.SO_KEEPALIVE, true);
    38             
    39             // 绑定端口,开始接收进来的连接
    40             ChannelFuture f = b.bind(port).sync();
    41             
    42             // 等待服务器socket关闭
    43             f.channel().closeFuture().sync();
    44         } catch (Exception e) {
    45             workerGroup.shutdownGracefully();
    46             bossGroup.shutdownGracefully();
    47         }
    48     }
    49     
    50     public static void main(String[] args) throws Exception {
    51         int port = 12345;
    52         new TimeServerPOJO(port).run();
    53     }
    54 }

       其中ch.pipeline().addLast(new TimeEncoderPOJO(),new TimeServerHandlerPOJO());方法的含义为:Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its ChannelPipeline.也就是说当我们添加一些处理的时候会按照管道的方式,一步步的处理,因此先后顺序非常重要。

     3.3、客户端程序

       先来看看解码器(服务器端发送了编码后的时间信息,因此,这里客户端收到之后需要解码):

       TimeDecoderPOJO 类:

     1 package com.coder.client;
     2 
     3 import java.util.List;
     4 
     5 import com.coder.pojo.Time;
     6 
     7 import io.netty.buffer.ByteBuf;
     8 import io.netty.channel.ChannelHandlerContext;
     9 import io.netty.handler.codec.ByteToMessageDecoder;
    10 
    11 public class TimeDecoderPOJO extends ByteToMessageDecoder {
    12     /**
    13      * 有新数据接收时调用
    14      * 为防止分包现象,先将数据存入内部缓存,到达满足条件之后再进行解码
    15      */
    16     @Override
    17     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    18         if(in.readableBytes() < 4) {
    19             return;
    20         }
    21         
    22         // out添加对象则表示解码成功
    23         out.add(new Time(in.readUnsignedInt()));
    24     }
    25 }

      再看看客户端数据处理类:

      TimeClientHandlerPOJO类:

     1 package com.coder.client;
     2 
     3 import com.coder.pojo.Time;
     4 
     5 import io.netty.channel.ChannelHandlerContext;
     6 import io.netty.channel.ChannelInboundHandlerAdapter;
     7 
     8 /**
     9  * 客户端数据处理类
    10  *
    11  */
    12 public class TimeClientHandlerPOJO extends ChannelInboundHandlerAdapter {
    13     @Override
    14     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    15         // 直接将信息转换成Time类型输出即可
    16         Time time = (Time)msg;
    17         System.out.println(time);
    18         ctx.close();
    19     }
    20     
    21     @Override
    22     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    23         cause.printStackTrace();
    24         ctx.close();
    25     }
    26 }

      最后是客户端的主程序:

       TimeClientPOJO类:

     1 package com.coder.client;
     2 
     3 import io.netty.bootstrap.Bootstrap;
     4 import io.netty.channel.ChannelFuture;
     5 import io.netty.channel.ChannelInitializer;
     6 import io.netty.channel.ChannelOption;
     7 import io.netty.channel.EventLoopGroup;
     8 import io.netty.channel.nio.NioEventLoopGroup;
     9 import io.netty.channel.socket.SocketChannel;
    10 import io.netty.channel.socket.nio.NioSocketChannel;
    11 
    12 public class TimeClientPOJO {
    13     public static void main(String[] args) throws Exception{
    14         String host = "127.0.0.1";            // ip
    15         int port = 12345;                    // 端口
    16         EventLoopGroup workerGroup = new NioEventLoopGroup();
    17         
    18         try {
    19             Bootstrap b = new Bootstrap();            // 与ServerBootstrap类似
    20             b.group(workerGroup);                    // 客户端不需要boss worker
    21             b.channel(NioSocketChannel.class);
    22             b.option(ChannelOption.SO_KEEPALIVE, true);    // 客户端的socketChannel没有父亲
    23             b.handler(new ChannelInitializer<SocketChannel>() {
    24                 @Override
    25                 protected void initChannel(SocketChannel ch) throws Exception {
    26                     // POJO
    27                     ch.pipeline().addLast(new TimeDecoderPOJO() ,new TimeClientHandlerPOJO());
    28                 }
    29             });
    30             
    31             // 启动客户端,客户端用connect连接
    32             ChannelFuture f = b.connect(host, port).sync();
    33             
    34             // 等待连接关闭
    35             f.channel().closeFuture().sync();
    36         } finally {
    37             workerGroup.shutdownGracefully();
    38         }
    39     }
    40 }

        至此程序编写完毕,先运行服务器,再运行客户端程序,然后测试即可,我们会发现服务器一直等待着请求,当客户端连接上之后,服务器就会发出带着格式的时间,客户端接收到之后进行解码,然后显示出来并且退出。在同一个myeclipse之中可以运行多个程序,使用下图中的按钮可以进行切换。

    四、netty的基本组成部分

     4.1、Channel  

        Channel 是 Java NIO 的一个基本构造。它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作。目前,可以把 Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。

     4.2、Callback(回调)  

       Netty 在内部使用了回调来处理事件;当一个回调被触发时,相关的事件可以被一个 interfaceChannelHandler 的实现处理。

     4.3、Future

      Future 提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。JDK 预置了 interface java.util.concurrent.Future,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以 Netty 提供了它自己的实现ChannelFuture,用于在执行异步操作的时候使用。

     4.4、Event 和 Handler

         Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。这些动作可能是:记录日志、数据转换、流控制、应用程序逻辑。Netty 是一个网络编程框架,所以事件是按照它们与入站或出站数据流的相关性进行分类的。可能由入站数据或者相关的状态更改而触发的事件包括:连接已被激活或者连接失活、数据读取、用户事件、错误事件。出站事件是未来将会触发的某个动作的操作结果,这些动作包括:打开或者关闭到远程节点的连接、将数据写到或者冲刷到套接字。
        Netty 的 ChannelHandler 为处理器提供了基本的抽象,目前可以认为每个 ChannelHandler 的实例都类似于一种为了响应特定事件而被执行的回调。Netty 提供了大量预定义的可以开箱即用的 ChannelHandler 实现,包括用于各种协议(如 HTTP 和 SSL/TLS)的 ChannelHandler。在内部 ChannelHandler 自己也使用了事件和 Future。

    五、netty聊天程序

     5.1、服务器端

       SimpleChatServerInitializer类:

     1 package com.coder.server;
     2 
     3 import io.netty.channel.ChannelInitializer;
     4 import io.netty.channel.ChannelPipeline;
     5 import io.netty.channel.socket.SocketChannel;
     6 import io.netty.handler.codec.DelimiterBasedFrameDecoder;
     7 import io.netty.handler.codec.Delimiters;
     8 import io.netty.handler.codec.string.StringDecoder;
     9 import io.netty.handler.codec.string.StringEncoder;
    10 
    11 /**
    12  * 服务器配置初始化
    13  * 添加多个处理器
    14  */
    15 public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> {
    16 
    17     @Override
    18     protected void initChannel(SocketChannel ch) throws Exception {
    19         ChannelPipeline pipeline = ch.pipeline();
    20         // 添加处理类
    21         // 使用'
    ''
    '分割帧
    22         pipeline.addLast("framer", 
    23                 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    24         // 解码、编码器
    25         pipeline.addLast("decoder", new StringDecoder());
    26         pipeline.addLast("encoder", new StringEncoder());
    27         // 处理器
    28         pipeline.addLast("handler", new SimpleChatServerHandler());
    29         
    30         System.out.println("SimpleChatClient: " + ch.remoteAddress() + "连接上");
    31     }
    32 
    33 }

      SimpleChatServerHandler类:

     1 package com.coder.server;
     2 
     3 
     4 import io.netty.channel.*;
     5 import io.netty.channel.group.ChannelGroup;
     6 import io.netty.channel.group.DefaultChannelGroup;
     7 import io.netty.util.concurrent.GlobalEventExecutor;
     8 
     9 /**
    10  * 服务端处理器
    11  */
    12 public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> {
    13  
    14     public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    15     
    16     /**
    17      * 收到新的客户端连接时调用
    18      * 将客户端channel存入列表,并广播消息
    19      */
    20     @Override
    21     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    22         Channel incoming = ctx.channel();
    23         // 广播加入消息
    24         channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入
    ");
    25         channels.add(incoming);        // 存入列表    
    26     }
    27     
    28     /**
    29      * 客户端连接断开时调用
    30      * 广播消息
    31      */
    32     @Override
    33     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    34         Channel incoming = ctx.channel();
    35         // 广播离开消息
    36         channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 离开
    ");
    37         // channel会自动从ChannelGroup中删除 
    38     }
    39     
    40     /**
    41      * 收到消息时调用
    42      * 将消息转发给其他客户端
    43      */
    44     @Override
    45     protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    46         Channel incoming = ctx.channel();
    47         for(Channel channel : channels) {        // 遍历所有连接的客户端
    48             if(channel != incoming) {            // 其他客户端
    49                 channel.writeAndFlush("[" + incoming.remoteAddress() + "] " + msg + "
    " );
    50             } else {                            // 自己
    51                 channel.writeAndFlush("[you] " + msg + "
    " );
    52             }
    53         }
    54     }
    55     
    56     /**
    57      * 监听到客户端活动时调用
    58      */
    59     @Override
    60     public void channelActive(ChannelHandlerContext ctx) throws Exception {
    61         Channel incoming = ctx.channel();
    62         System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 在线");
    63     }
    64     
    65     /**
    66      * 监听到客户端不活动时调用
    67      */
    68     @Override
    69     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    70         Channel incoming = ctx.channel();
    71         System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 掉线");
    72     }
    73     
    74     /**
    75      * 当Netty由于IO错误或者处理器在处理事件抛出异常时调用
    76      * 关闭连接
    77      */
    78     @Override
    79     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    80         Channel incoming = ctx.channel();
    81         System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 异常");
    82     }
    83 }

     SimpleChatServer类:

     1 package com.coder.server;
     2 
     3 import io.netty.bootstrap.ServerBootstrap;
     4 import io.netty.channel.ChannelFuture;
     5 import io.netty.channel.ChannelOption;
     6 import io.netty.channel.EventLoopGroup;
     7 import io.netty.channel.nio.NioEventLoopGroup;
     8 import io.netty.channel.socket.nio.NioServerSocketChannel;
     9 
    10 /**
    11  * 服务端 main 启动
    12  */
    13 public class SimpleChatServer {
    14     private int port;        // 端口
    15     
    16     public SimpleChatServer(int port) {
    17         this.port = port;
    18     }
    19     
    20     // 配置并开启服务器
    21     public void run() throws Exception {
    22         EventLoopGroup bossGroup = new NioEventLoopGroup();        // 用来接收进来的连接
    23         EventLoopGroup workerGroup = new NioEventLoopGroup();    // 用来处理已接收的连接
    24         
    25         try {
    26             ServerBootstrap sb = new ServerBootstrap();            // 启动NIO服务的辅助启动类
    27             sb.group(bossGroup, workerGroup)
    28                 .channel(NioServerSocketChannel.class)                // 设置如何接受连接
    29                 .childHandler(new SimpleChatServerInitializer())    // 配置Channel
    30                 .option(ChannelOption.SO_BACKLOG, 128)                // 设置缓冲区
    31                 .childOption(ChannelOption.SO_KEEPALIVE, true);    // 启用心跳机制
    32             
    33             System.out.println("SimpleChatServer 启动了");
    34             ChannelFuture future = sb.bind(port).sync();        // 绑定端口,开始接收连接
    35             future.channel().closeFuture().sync();                // 等待关闭服务器(不会发生)
    36         } finally {
    37             workerGroup.shutdownGracefully();
    38             bossGroup.shutdownGracefully();
    39             System.out.println("SimpleChatServer 关闭了");
    40         }
    41     }
    42     
    43     public static void main(String[] args) throws Exception {
    44         int port = 8080;
    45         new SimpleChatServer(port).run();     // 开启服务器
    46     }
    47 }

    5.2、客户端程序

     SimpleChatClientInitializer类:

     1 package com.coder.client;
     2 
     3 import io.netty.channel.ChannelInitializer;
     4 import io.netty.channel.ChannelPipeline;
     5 import io.netty.channel.socket.SocketChannel;
     6 import io.netty.handler.codec.DelimiterBasedFrameDecoder;
     7 import io.netty.handler.codec.Delimiters;
     8 import io.netty.handler.codec.string.StringDecoder;
     9 import io.netty.handler.codec.string.StringEncoder;
    10 
    11 /**
    12  * 客户端配置初始化
    13  * 与服务端类似
    14  */
    15 public class SimpleChatClientInitializer extends ChannelInitializer<SocketChannel> {
    16 
    17     @Override
    18     protected void initChannel(SocketChannel ch) throws Exception {
    19         ChannelPipeline pipeline = ch.pipeline();
    20         // 添加处理类
    21         // 使用'
    ''
    '分割帧
    22         pipeline.addLast("framer", 
    23                 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    24         // 解码、编码器
    25         pipeline.addLast("decoder", new StringDecoder());
    26         pipeline.addLast("encoder", new StringEncoder());
    27         // 处理器
    28         pipeline.addLast("handler", new SimpleChatClientHandler());
    29     }
    30 
    31 }

     SimpleChatClientHandler类:

     1 package com.coder.client;
     2 
     3 import io.netty.channel.ChannelHandlerContext;
     4 import io.netty.channel.SimpleChannelInboundHandler;
     5 
     6 /**
     7  * 客户端处理类
     8  * 直接输出收到的消息
     9  */
    10 public class SimpleChatClientHandler extends SimpleChannelInboundHandler<String> {
    11 
    12     @Override
    13     protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    14         System.out.println(msg);    // 直接输出消息        
    15     }
    16 
    17 }

     SimpleChatClient类:

     1 package com.coder.client;
     2 
     3 import java.io.BufferedReader;
     4 import java.io.InputStreamReader;
     5 
     6 import io.netty.bootstrap.Bootstrap;
     7 import io.netty.channel.Channel;
     8 import io.netty.channel.EventLoopGroup;
     9 import io.netty.channel.nio.NioEventLoopGroup;
    10 import io.netty.channel.socket.nio.NioSocketChannel;
    11 /**
    12  * 客户端
    13  * 开启客户端,接收控制台输入并发送给服务端
    14  */
    15 public class SimpleChatClient {
    16     private final String host;        // IP
    17     private final int port;        // 端口
    18     
    19     public SimpleChatClient(String host, int port) {
    20         this.host = host;
    21         this.port = port;
    22     }
    23     
    24     // 配置并运行客户端
    25     public void run() throws Exception {
    26         EventLoopGroup group = new NioEventLoopGroup();
    27         try {
    28             Bootstrap b = new Bootstrap();        // 客户端辅助启动类
    29             b.group(group)                                    // 客户端只需要一个用来接收并处理连接
    30                 .channel(NioSocketChannel.class)            // 设置如何接受连接
    31                 .handler(new SimpleChatClientInitializer());// 配置 channel
    32             // 连接服务器
    33             Channel channel = b.connect(host, port).sync().channel();
    34             // 读取控制台输入字符
    35             BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    36             while(true) {
    37                 // 每行成一帧输出,以"
    "结尾
    38                 channel.writeAndFlush(in.readLine() + "
    ");
    39             }
    40         } catch (Exception e) {
    41             e.printStackTrace();        // 输出异常
    42         } finally {
    43             group.shutdownGracefully();    // 关闭
    44         }
    45     }
    46     
    47     public static void main(String[] args) throws Exception {
    48         new SimpleChatClient("localhost", 8080).run();        // 启动客户端
    49     }
    50 
    51 }

       运行结果:

    六、总结

        通过代码的形式,我们对netty有了直观的了解和实际上的掌握。

       程序源码

    参考文献:https://www.cnblogs.com/coderJiebao/tag/netty/

  • 相关阅读:
    C++11 并发指南三(Lock 详解)
    C++11 并发指南六(atomic 类型详解四 C 风格原子操作介绍)
    C++11 并发指南六(atomic 类型详解三 std::atomic (续))
    C++11 并发指南六( <atomic> 类型详解二 std::atomic )
    C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)
    C++11 并发指南五(std::condition_variable 详解)
    腾讯地图定位及坐标解析
    控制器里把文件转为二进制输出下载
    C#操作mongodb简记
    MYSQL备份还原问题
  • 原文地址:https://www.cnblogs.com/zyrblog/p/9861027.html
Copyright © 2020-2023  润新知