Netty初次使用
简介
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。 也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
使用
服务端的建立:
1 创建ServerBootstrap实例。ServerBootstrap是Netty服务端的启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层通过门面模式对各种能力进行抽象和封装,尽量不需要用户跟过多的底层API打交道,降低用户的开发难度。
2 设置并绑定Reactor线程池。Netty的Reactor线程池是EventLoopGroup,它实际就是EventLoop的数组。EventLoop的职责是处理所有注册到本线程多路复用器Selector上的Channel,Selector的轮询操作由绑定的EventLoop线程run方法驱动,在一个循环体内循环执行。值得说明的是,EventLoop的职责不仅仅是处理网络I/O事件,用户自定义的Task和定时任务Task也统一由EventLoop负责处理,这样线程模型就实现了统一。从调度层面看,也不存在在EventLoop线程中再启动其它类型的线程用于异步执行其它的任务,这样就避免了多线程并发操作和锁竞争,提升了I/O线程的处理和调度性能。
3 设置并绑定服务端Channel。作为NIO服务端,需要创建ServerSocketChannel,Netty对原生的NIO类库进行了封装,对应实现是NioServerSocketChannel。对于用户而言,不需要关心服务端Channel的底层实现细节和工作原理,只需要指定具体使用哪种服务端Channel即可。因此,Netty的ServerBootstrap方法提供了channel方法用于指定服务端Channel的类型。Netty通过工厂类,利用反射创建NioServerSocketChannel对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。
4 链路建立的时候创建并初始化ChannelPipeline。ChannelPipeline并不是NIO服务端必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行ChannelHandler。网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline根据ChannelHandler的执行策略调度ChannelHandler的执行。
5 初始化ChannelPipeline完成之后,添加并设置ChannelHandler。ChannelHandler是Netty提供给用户定制和扩展的关键接口。利用ChannelHandler用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL认证、流量控制和流量整形等。Netty同时也提供了大量的系统ChannelHandler供用户使用,比较实用的系统ChannelHandler总结如下:
-
系统编解码框架-ByteToMessageCodec;
-
通用基于长度的半包解码器-LengthFieldBasedFrameDecoder;
-
码流日志打印Handler-LoggingHandler;
-
SSL安全认证Handler-SslHandler;
-
链路空闲检测Handler-IdleStateHandler;
-
流量整形Handler-ChannelTrafficShapingHandler;
-
Base64编解码-Base64Decoder和Base64Encoder。
6 绑定并启动监听端口。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将ServerSocketChannel注册到Selector上监听客户端连接
7 Selector轮询。由Reactor线程NioEventLoop负责调度和执行Selector轮询操作,选择准备就绪的Channel集合
8 当轮询到准备就绪的Channel之后,就由Reactor线程NioEventLoop执行ChannelPipeline的相应方法,最终调度并执行ChannelHandler
9 执行Netty系统ChannelHandler和用户添加定制的ChannelHandler。ChannelPipeline根据网络事件的类型,调度并执行ChannelHandler
简单实例:
服务端:
package mynetty;
import java.nio.channels.SocketChannel;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
int port;
public Server(int port) {
this.port = port;
}
public void start() throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();//用于服务器端接受客户端的连接
ServerBootstrap serverBootstrap = new ServerBootstrap();//用于网络事件的处理
serverBootstrap.group(eventLoopGroup) //绑定线性池
.channel(NioServerSocketChannel.class) //指定使用的channel
.localAddress(this.port)
.childHandler(new ChannelInitializer<Channel>() { //绑定客户端连接后触发的操作
@Override
protected void initChannel(Channel arg0) throws Exception {
System.out
.println("等待连接。。。。。。。");
arg0.pipeline().addLast(new ServerHandler());//客户端触发操作
}
});
ChannelFuture cf = serverBootstrap.bind().sync();//服务端异步创建绑定
System.out.println("服务器开始在端口"+this.port+"监听");
cf.channel().closeFuture().sync(); //关闭服务器通道
}
public static void main(String[] args) throws Exception {
Server server = new Server(3391);
server.start();
}
}
服务端对客户端的处理:
package mynetty;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class ServerHandler extends ChannelInboundHandlerAdapter{
/*
* (non-Javadoc)
* @see io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.ChannelHandlerContext, java.lang.Object)
* 当收到对方发来的数据后,就会触发,参数msg就是发来的信息,可以是基础类型,也可以是序列化的复杂对象。
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.write(msg);
}
/*
* (non-Javadoc)
* @see io.netty.channel.ChannelInboundHandlerAdapter#channelReadComplete(io.netty.channel.ChannelHandlerContext)
* channelRead执行后触发
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
客户端:
package mynetty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
String hoString;
int port;
public Client(String hoString,int port) {
this.hoString = hoString;
this.port = port;
}
public void start() throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup) //绑定线程池
.channel(NioSocketChannel.class) //使用NioSocketChannel来作为连接用的channel类
.remoteAddress(this.hoString,this.port) //绑定端口和host信息
.handler(new ChannelInitializer<Channel>() { //绑定连接初始化器
@Override
protected void initChannel(Channel arg0) throws Exception {
arg0.pipeline().addLast(new ClientHandler());
}
});
System.out.println("创建客户端。。。。");
ChannelFuture cf = bootstrap.connect().sync(); //异步连接服务器
System.out.println("创建成功");
cf.channel().closeFuture().sync(); //异步关闭连接通道
}
public static void main(String[] args) throws InterruptedException {
Client client = new Client("127.0.0.1",3391);
client.start();
}
}
客户端处理:
package mynetty;
import java.nio.charset.Charset;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/*
* (non-Javadoc)
* @see io.netty.channel.SimpleChannelInboundHandler#channelRead0(io.netty.channel.ChannelHandlerContext, java.lang.Object)
* 接收到服务器数据后被调用
*/
@Override
protected void channelRead0(ChannelHandlerContext arg0, ByteBuf arg1) throws Exception {
System.out.println("客户端进行读取");
ByteBuf buf = arg1.readBytes(arg1.readableBytes());
System.out.println(buf.toString(Charset.forName("utf-8")));
}
/*
* (non-Javadoc)
* @see io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.ChannelHandlerContext)
* 连接到服务器后被调用
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端创建成功");
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello",CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}