Netty入门应用
Netty是业界最流行的NIO框架之一,它的健壮性,功能,性能,可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如hadoopRPC框架的avro使用netty作为底层通信框架,很多其他业界主流的RPC框架,也使用netty来构建高性能的异步通信能力。
开发Netty入门应用之前我们先了解一下NIO服务端开发的步骤,如下图所示
一个简单的NIO服务端程序,如果我们直接使用JDK的NIO类库记性开发,要经过十几步操作才能完成最基本的消息读取和发送,这也是我们要使用netty的原因。
一.Netty服务端开发
1.1 TimeServer实现:
importio.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
importio.netty.channel.nio.NioEventLoopGroup;
importio.netty.channel.socket.SocketChannel;
importio.netty.channel.socket.nio.NioServerSocketChannel;
public class TimeServer {
publicvoid bind(int port) throws Exception{
//配置服务端的NIO线程组,专门用于网络事件的处理,实际就是Reactor线程组
//一个用于服务端接收客户端的连接,另一个用于进行SocketChannel的网络读写
EventLoopGroupbossGroup =new NioEventLoopGroup();
EventLoopGroupworkerGroup =new NioEventLoopGroup();
try{
//用于启动NIO服务器的辅助启动类,降低服务端的开发复杂度
ServerBootstrapb=new ServerBootstrap();
// group方法将2个NIO线程组当做入参传递到ServerBootstrap
b.group(bossGroup,workerGroup)
// 对应于JDK NIO类库中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
// 配置TCP,此处将它的backlog设置为1024
.option(ChannelOption.SO_BACKLOG,1024)
// 绑定IO事件的处理类ChildChannelHandler,作用类似于Reactor模式中的handler类,主要用于处理网络I/O事件,例如记录日志,对消息进行编解码等。
.childHandler(new ChildChannelHandler());
//绑定端口,同步阻塞方法sync,同步等待成功,功能类似于JDK的java.uti.concurrent.Future,主要用于异步操作的通知回调
ChannelFuturef=b.bind(port).sync();
//等待服务端监听端口关闭
f.channel().closeFuture().sync();
}catch (Exception e) {
//TODO: handle exception
}finally{
//优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
privateclass ChildChannelHandler extends ChannelInitializer <SocketChannel> {
@Override
protectedvoid initChannel(SocketChannel arg0) throws Exception{
arg0.pipeline().addLast(newTimeServerHandler());
}
}
publicstatic void main(String[] args) throws Exception{
intport=8080;
if(args!=null&&args.length>0){
try{
port=Integer.valueOf(args[0]);
}catch (Exception e) {
//TODO: handle exception
}
}
newTimeServer().bind(port);
}
}
2.TimeServerHandler实现:
TimeServerHandler继承自ChannelHandlerAdapter,用于对网络时间进行读写操作
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
importio.netty.channel.ChannelHandlerAdapter;
importio.netty.channel.ChannelHandlerContext;
/*
* 对网络事件进行读写操作
*/
public class TimeServerHandler extendsChannelHandlerAdapter{
@Override
publicvoid channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
//ByteBuf类似于JDK中的java.nio.ByteBuffer对象
ByteBufbuf=(ByteBuf) msg;
//readableBytes方法可以获取缓冲区可读的字节数
byte[]req=new byte[buf.readableBytes()];
buf.readBytes(req);
Stringbody=new String(req,"UTF-8");
System.out.println("Thetime server receive order:"+body);
StringcurrentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?
newjava.util.Date(System.currentTimeMillis()).toString():"BAD ORDER";
ByteBufresp=Unpooled.copiedBuffer(currentTime.getBytes());
//异步发送应答消息给客户端,把待发送的消息放到发送缓冲数组里
ctx.write(resp);
}
/*
* 从性能角度考虑,为了防止频繁地唤醒Selector进行消息发送,
* Netty的write方法并不直接将消息写入SocketChannel中,
* 调用write方法只是把待发送的消息放到发送缓冲数组中,
* 再调用flush方法,将发送缓冲区中的消息全部写到SocketChannel中
* (non-Javadoc)
* @seeio.netty.channel.ChannelHandlerAdapter#channelReadComplete(io.netty.channel.ChannelHandlerContext)
*/
@Override
publicvoid channelReadComplete(ChannelHandlerContext ctx) throws Exception{
//将发送队列中的消息写入到SocketChannel中发送给对方
ctx.flush();
}
/*当发生异常时,关闭ChannelHandlerContext,释放和ChannelHandlerContext相关联的句柄等资源
* (non-Javadoc)
* @seeio.netty.channel.ChannelHandlerAdapter#exceptionCaught(io.netty.channel.ChannelHandlerContext,java.lang.Throwable)
*/
@Override
publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
ctx.close();
}
}
2. Netty客户端开发
2.1 TimeClient实现:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
importio.netty.channel.nio.NioEventLoopGroup;
importio.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class TimeClient {
publicvoid connect(int port,String host) throws Exception{
//配置客户端NIO线程组,处理IO读写
EventLoopGroupgroup=new NioEventLoopGroup();
try{
//创建客户端辅助启动类
Bootstrapb=new Bootstrap();
b.group(group)
//客户端的Channel设置为NioSocketChannel,
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true) // 对其进行配置
//此处为了简单,直接创建匿名内部类,实现initChannel方法
//其作用是当创建NioSocketChannel成功之后,
//在初始化它时将它的ChannelHandler设置到ChannelPipeline中,用于处理网络I/O事件
.handler(newChannelInitializer<SocketChannel>() {
@Override
publicvoid initChannel(SocketChannel ch) throws Exception{
ch.pipeline().addLast(newTimeClientHandler());
}
});
//发起异步连接操作
ChannelFuturef=b.connect(host,port).sync();
//等待客户端链路关闭
f.channel().closeFuture().sync();
}catch (Exception e) {
//TODO: handle exception
}finally{
//优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
publicstatic void main(String[] args) throws Exception{
intport=8080;
if(args!=null&&args.length>0){
try{
port=Integer.valueOf(args[0]);
}catch (NumberFormatException e) {
//TODO: handle exception
}
newTimeClient().connect(port,"127.0.0.1");
}
}
}
2.2 TimeClientHandler实现
import java.util.logging.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
importio.netty.channel.ChannelHandlerAdapter;
importio.netty.channel.ChannelHandlerContext;
public class TimeClientHandler extendsChannelHandlerAdapter{
privatestatic final Logger logger=Logger.getLogger(TimeClientHandler.class.getName());
privatefinal ByteBuf firstMessage;
publicTimeClientHandler(){
byte[]req="QUERY TIME ORDER".getBytes();
firstMessage=Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
/*
* 当客户端和服务端TCP链路建立成功之后,
* Netty的NIO线程会调用channelActive方法,发送查询时间的指令给服务端
* (non-Javadoc)
* @seeio.netty.lActive(channel.ChannelHandlerAdapter#channeio.netty.channel.ChannelHandlerContext)
*/
@Override
publicvoid channelActive(ChannelHandlerContext ctx){
//将请求消息发送给服务端
ctx.writeAndFlush(firstMessage);
}
/*
* 当服务端返回应答消息时,channelRead方法被调用
* (non-Javadoc)
* @seeio.netty.channel.ChannelHandlerAdapter#channelRead(io.netty.channel.ChannelHandlerContext,java.lang.Object)
*/
@Override
publicvoid channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
//从Netty的ByteBuf中读取并打印应答消息
ByteBufbuf=(ByteBuf) msg;
byte[]req=new byte[buf.readableBytes()];
buf.readBytes(req);
Stringbody=new String(req,"UTF-8");
System.out.println("Nowis:"+body);
}
@Override
publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
//释放资源
logger.warning("Unexpectedexception from downstream:"+cause.getMessage());
ctx.close();
}
}
可以发现,通过netty开发的nio服务端和客户端非常简单。基于Netty应用开发不单API使用简单,开发模式固定,而且扩展性和定制型非常好。