首页 > 代码库 > Netty实践(一):轻松入门
Netty实践(一):轻松入门
前言
Netty作为目前世界上最流行的NIO框架之一,在功能、性能、健壮性方面首屈一指,而且在很多项目中得到验证,比如消息中间件RocketMQ、分布式通信框架Dubbox。Netty内部实现复杂,但是提供给外界的API却十分简单,轻松的让我们的网络处理代码和业务逻辑处理代码分离开,从而快速的开发网络应用。
如果你还不了解JAVA NIO,JAVA SOCKET,可以先参考博主以前关于这方面的博客:《走进Java NIO的世界》、《Java NIO 服务器与客户端实现文件下载》、《Java通信实战:编写自定义通信协议实现FTP服务》。
从代码实例来分析Netty
服务器端启动代码
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
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上进行监听,比如操作完毕关闭通道。
客户端启动代码
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
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); } } }
运行起来
到这里,你体会到Netty的简单、强大了吗?
咱们下篇博客见~
本文出自 “学海无涯 心境无限” 博客,请务必保留此出处http://zhangfengzhe.blog.51cto.com/8855103/1890017
Netty实践(一):轻松入门