Netty实践(一):轻松入门

简介:

前言

Netty作为目前世界上最流行的NIO框架之一,在功能、性能、健壮性方面首屈一指,而且在很多项目中得到验证,比如消息中间件RocketMQ、分布式通信框架Dubbox。Netty内部实现复杂,但是提供给外界的API却十分简单,轻松的让我们的网络处理代码和业务逻辑处理代码分离开,从而快速的开发网络应用。

如果你还不了解JAVA NIO,JAVA SOCKET,可以先参考博主以前关于这方面的博客:《走进Java NIO的世界》、《Java NIO 服务器与客户端实现文件下载》、《Java通信实战:编写自定义通信协议实现FTP服务》。



从代码实例来分析Netty


服务器端启动代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public  class  Main {
 
     public  static  void  main(String[] args) {
 
         EventLoopGroup bossGroup =  new  NioEventLoopGroup();  // (1)
         EventLoopGroup workerGroup =  new  NioEventLoopGroup();  // (2)
         int  port =  8867 ;
         try  {
             ServerBootstrap b =  new  ServerBootstrap();  // (3)
             b.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel. class // (4)
                     .childHandler( new  ChannelInitializer<SocketChannel>() {  // (5)
                 @Override
                 public  void  initChannel(SocketChannel ch)  throws  Exception {
                     ch.pipeline().addLast( new  ServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG,  128 )           // (6)
             .childOption(ChannelOption.SO_KEEPALIVE,  true );  // (7)
 
             // Bind and start to accept incoming connections.
             ChannelFuture f = b.bind(port).sync();  // (8)
 
             // Wait until the server socket is closed.
             // In this example, this does not happen, but you can do that to gracefully
             // shut down your server.
             System.out.println( "start server...." );
             f.channel().closeFuture().sync();
             System.out.println( "stop server...." );
         catch  (InterruptedException e) {
             e.printStackTrace();
         finally  {
             workerGroup.shutdownGracefully();
             bossGroup.shutdownGracefully();
             System.out.println( "exit server...." );
         }
 
     }
}

上面大致揭示了Netty服务端启动编写的代码步骤,下面我们来具体分析下:

在原始的JAVA SOCKET通信中,不论是基于IO/NIO,实质上服务端有2个工作需要处理:第一,接受客户端的连接请求;第二,处理客户端的请求进行通信。在上面的代码里,Netty已经为我们抽象出来2个EventLoopGroup线程组(bossGroup/workerGroup)来完成这2个任务。

服务端启动前,显然需要进行一些配置(Channel的类型、服务端进行业务处理的Handler、一些TCP/IP协议的配置等),Netty可以利用ServerBootstrap/Bootstrap分别对Server/Client进行配置。

绑定端口、启动服务,注意返回的对象ChannelFuture,既然叫Future,那么猜测就是一个异步的行为。需要注意的是通过ChannelFuture可以获取到Channel,从而利用Channel在通道上进行读、写、关闭等操作。

通过bind方法,可以绑定多个端口,实现N个Clients在Server端的M个端口上进行数据通信。


下面,我们来看一下服务端进行业务处理的Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public  class  ServerHandler   extends  ChannelHandlerAdapter {
 
     //每当从客户端收到新的数据时,这个方法会在收到消息时被调用
     @Override
     public  void  channelRead(ChannelHandlerContext ctx, Object msg)  throws  Exception {
 
         ByteBuf in = (ByteBuf) msg;
         try  {
             // Do something with msg
             System.out.println( "server get :"  + in.toString(CharsetUtil.UTF_8));
 
             ChannelFuture channelFuture = ctx.writeAndFlush(Unpooled.copiedBuffer(( "server send time: "  new  Date()).getBytes()));
 
             //服务端发送数据完毕后,关闭通道
             channelFuture.addListener(ChannelFutureListener.CLOSE);
 
         finally  {
             //ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放
             //or ((ByteBuf)msg).release();
             ReferenceCountUtil.release(msg);
         }
 
     }
 
     //exceptionCaught()事件处理方法是当出现Throwable对象才会被调用
     //当Netty由于IO错误或者处理器在处理事件时抛出的异常时
     @Override
     public  void  exceptionCaught(ChannelHandlerContext ctx, Throwable cause)  throws  Exception {
 
         // Close the connection when an exception is raised.
         cause.printStackTrace();
         ctx.close();
 
     }
 
}


首先来看服务端的Handler继承了ChannelHandlerAdapter,实际上这里体现到了适配器设计模式。ChannelHandlerAdapter implements ChannelHandler,如果我们的服务端Handler直接implements ChannelHandler的话,将需要override非常多的API,而先通过ChannelHandlerAdapter实现通用的ChannelHandler,然后让服务端的Handler去复写特定的API即可。

当通道上特定的事件发生时,就会调用特定的方法进行处理,看起来清晰明了。

Netty进行网络通信的数据类型是缓冲数据类型,如ByteBuf。以前在NIO中我们利用ByteBuffer进行通信时,需要额外注意position的位置变化,而现在Netty中,我们不在需要关心这些。

Client/Server端都存在缓冲区,所以我们需要注意,缓冲区的消息释放和刷新。如果读,那么需要release,如果写,只需要flush(flush的时候已经做了release)进行发送到对方。

由于Netty是一个NIO框架,即操作都是异步的,所以上面writeAndFlush操作返回了Future对象,我们可以在这个Future上进行监听,比如操作完毕关闭通道。



客户端启动代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public  class  Client {
 
     public  static  void  main(String[] args) {
 
         EventLoopGroup group =  new  NioEventLoopGroup();
 
         try  {
               Bootstrap b =  new  Bootstrap();
               b.group(group)
                .channel(NioSocketChannel. class )
                .handler( new  ChannelInitializer<SocketChannel>() {
                    @Override
                    public  void  initChannel(SocketChannel ch)  throws  Exception {
                        ChannelPipeline p = ch.pipeline();
                        p.addLast( new  ClientHandler());
                    }
                });
 
               // Start the client.
               ChannelFuture f = b.connect( "127.0.0.1" 8867 ).sync();
 
               // Wait until the connection is closed.
               f.channel().closeFuture().sync();
 
           catch  (InterruptedException e) {
             e.printStackTrace();
         finally  {
               // Shut down the event loop to terminate all threads.
               group.shutdownGracefully();
           }
 
     }
 
}

这里,我们主要看一下客户端和服务端启动的区别点:

第一,客户端仅仅需要一个线程组,而服务端需要2个

第二,服务启动辅助类,客户端是Bootstrap,服务端是ServerBootstrap

第三,服务端通道配置是NioServerSocketChannel,客户端是NioSocketChannel



客户端业务处理Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public  class  ClientHandler  extends  ChannelHandlerAdapter {
 
     @Override
     public  void  channelActive(ChannelHandlerContext ctx)  throws  Exception {
 
         ctx.writeAndFlush(Unpooled.copiedBuffer(( "client send hello " ).getBytes()));
 
     }
 
     @Override
     public  void  channelRead(ChannelHandlerContext ctx, Object msg)  throws  Exception {
 
         ByteBuf in = (ByteBuf) msg;
         try  {
             // Do something with msg
             System.out.println( "client get :"  + in.toString(CharsetUtil.UTF_8));
 
             ctx.close();
         finally  {
             //ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放
             //or ((ByteBuf)msg).release();
             ReferenceCountUtil.release(msg);
         }
     }
}


运行起来

wKioL1hwq8nSykRsAAAbP6u2-GU304.png


wKioL1hwq9XguTjSAAAiN9E-tHQ820.png


到这里,你体会到Netty的简单、强大了吗?

咱们下篇博客见~


本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1890017,如需转载请自行联系原作者

相关文章
|
11天前
|
缓存 网络协议 算法
Netty的基础入门(上)
Netty的基础入门(上)
40 0
|
2月前
|
缓存 网络协议 算法
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析
134 0
|
2月前
|
消息中间件 缓存 Java
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
90 0
|
8月前
|
消息中间件 编解码 Java
Netty 入门指南
上文《[BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程》](https://wangbinguang.blog.csdn.net/article/details/132047951)介绍了几种IO模型以及Java NIO,了解了在网络编程时使用哪种模型可以提高系统性能及效率。即使Java NIO可以帮助开发人员编写和维护网络应用程序,但由于其复杂性以及bug问题,还是诞生很多强大和流行的网络编程框架,比如Netty、Undertow、Grizzly,在平时的开发中大家更倾向于选择这些框架进行开发,而在我们学习和理解网络编程的底层原理时,使用Java NIO可以更加直接和深
44 0
|
4月前
|
缓存 网络协议 算法
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(二)
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析
59 1
|
4月前
|
设计模式 网络协议 算法
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)
82 1
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)
|
4月前
|
消息中间件 缓存 Java
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty(二)
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
85 1
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty(二)
|
4月前
|
缓存 Java 数据挖掘
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty(一)
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
81 0
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty(一)
|
4月前
|
编解码 网络协议 Java
Netty基础入门学习
Netty基础入门学习
31 0
|
4月前
|
存储 设计模式 安全
Netty基础入门和基本使用-2
在Reactor反应器经典模型中,反应器查询到IO事件后,分发到Handler业务处理器,由Handler完成IO操作和业务处理。整个的IO处理操作包括:从通道读取数据包、数据包解码、业务处理、目标数据编码、把数据包写到通道,然后由通道发送到对端。前后两个环节,从通道读取数据包和由通道发送到对端由Netty的底层完成,不需要用户程序负责。