沉淀再出发:关于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/

原文地址:https://www.cnblogs.com/zyrblog/p/9861027.html