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

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

猜你喜欢

转载自www.cnblogs.com/zyrblog/p/9861027.html