1.Netty Demo代码
下面是Netty服务端代码,可以先大致阅读下代码,后面我们将对照着这个代码我们来看看Netty有哪些组件以及他们各自的作用是什么。
public class ServerTest {
/**
* 服务端口
*/
private int port=9999;
/**
* 开启服务的方法
*/
public void StartNetty(){
/**创建两个EventLoop的组,EventLoop 这个相当于一个处理线程,
是Netty接收请求和处理IO请求的线程。不理解的话可以百度NIO图解*/
/*
相关资料:NioEventLoopGroup是一个处理I/O操作的多线程事件循环。
Netty为不同类型的传输提供了各种EventLoopGroup实现。
在本例中,我们正在实现一个服务器端应用程序,因此将使用两个NioEventLoopGroup。
第一个,通常称为“boss”,接受传入的连接。
第二个,通常称为“worker”,当boss接受连接并注册被接受的连接到worker时,处理被接受连接的流量。
使用了多少线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。
*/
EventLoopGroup acceptor = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
//1、创建启动类
ServerBootstrap bootstrap = new ServerBootstrap();
//2、配置启动参数等
/**设置循环线程组,前者用于处理客户端连接事件,后者用于处理网络IO(server使用两个参数这个)
*public ServerBootstrap group(EventLoopGroup group)
*public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
*/
bootstrap.group(acceptor,worker);
/**设置选项
* 参数:Socket的标准参数(key,value),可自行百度
* eg:
* bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
*bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
* */
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
//用于构造socketchannel工厂
bootstrap.channel(NioServerSocketChannel.class);
/**
* 传入自定义客户端Handle(服务端在这里搞事情)
*/
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 注册handler
ch.pipeline().addLast(new SimpleServerHandler());
}
});
// 绑定端口,开始接收进来的连接
ChannelFuture f = bootstrap.bind(port).sync();
// 等待服务器 socket 关闭 。
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
acceptor.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
new ServerTest().StartNetty();
}
}
public class SimpleServerHandler extends ChannelInboundHandlerAdapter {
/**
* 本方法用于读取客户端发送的信息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("SimpleServerHandler.channelRead");
ByteBuf result = (ByteBuf) msg;
byte[] result1 = new byte[result.readableBytes()];
// msg中存储的是ByteBuf类型的数据,把数据读取到byte[]中
result.readBytes(result1);
String resultStr = new String(result1);
// 接收并打印客户端的信息
System.out.println("Client said:" + resultStr);
// 释放资源,这行很关键
result.release();
// 向客户端发送消息
String response = "hello client!";
// 在当前场景下,发送的数据必须转换成ByteBuf数组
ByteBuf encoded = ctx.alloc().buffer(4 * response.length());
encoded.writeBytes(response.getBytes());
ctx.write(encoded);
ctx.flush();
}
/**
* 本方法用作处理异常
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
}
/**
* 信息获取完毕后操作
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
}
2.Netty核心组件
1.ServerBootstrap
netty服务端应用开发的入口,服务端使用ServerBootstrap,客户端使用Bootstrap。对服务端和客户端做配置和启动的类。
2.Channel
网络通信的主体,由它负责同对端进行网络通信、注册和数据操作等功能,写数据,读数据操作,对java NIO的channel进行包装,主体就是socket。
3.EventLoop
EventLoop 主要是为Channel 处理 I/O 操作,两者配合参与 I/O 操作。实际上是一个单个线程的线程池,对Java NIO中的selector进行优化和封装。EventLoop线程会不断的轮询,看那个Channel准备好read或者write。
4.EventLoopGroup
管理EventLoop,实际上是线程池,包含多个子EventLoop。
5.ChannelHandler
ChannelHandler 为 Netty 中最核心的组件,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。例如下面列举的一些channel的事件,有点类似如filter拦截处理过滤:
- channelRegistered 当前channel注册到EventLoop
- channelUnregistered 当前channel从EventLoop取消注册
- channelActive 当前channel激活的时候
- channelInactive 当前channel不活跃的时候,也就是当前channel到了它生命周期末
- channelRead 当前channel从远端读取到数据
- channelReadComplete channel read消费完读取的数据的时候被触发
6.ChannelPipeline
ChannelPipeline是ChannelHandler的容器,ChannelHandler是多个的,ChannelPipeline维护这ChannelHandler的触发顺序。看下面的图,ChannelPipeline中有多个Outboundhandler和多个inboundhandler,数据首先经过Indoundhandler的处理,处理完成后经过Outboundhandler的处理,然后出去。
7.ChannelFuture
ChannelFuture的作用是用来保存Channel异步操作的结果。Netty是异步的,任何的I/O调用都将立即返回,而不保证这些被请求的I/O操作在调用结束的时候已经完成。你会得到一个返回的ChannelFuture实例,这个实例将给你一些关于I/O操作结果或者状态的信息。
8.ByteBuf
Netty提供的字节容器,Java NIO 提供了 ByteBuffer 作为它 的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。Netty使用ByteBuf来替代ByteBuffer。
9.编码器和解码器
网络中传输的是字节码,编码器的作用就是把我们传输的消息变成字节码,解码器的作用就是把字节码变成我们的消息。编码器和解码器一般需要我们自己来编写,当然Netty也提供一些基本的编码器和解码器,例如:
- DelimiterBasedFrameDecoder 解决TCP的粘包解码器
- StringDecoder 消息转成String解码器
- LineBasedFrameDecoder 自动完成标识符分隔解码器
- FixedLengthFrameDecoder 固定长度解码器,二进制
- Base64Decoder base64 解码器
- Base64Encoder base64 编码器
- StringEncoder 消息转成String编码器
- LineBasedFrameDecoder 自动完成标识符分隔编码器
- MessageToMessageEncoder 根据 消息对象 编码为消息对象
我们查看一下这些类的源码,发现它们都实现了channelHandler接口,编码器和解码器也是channelHandler的一种,用来处理数据的,很好理解。
3.Netty线程模型
通过下面的架构图,我们来从整体上认识一下Netty各个组件的作用和Netty对于线程的使用。看下面的架构图:
我们对着序号一部分一部分的来看:
- 序号1,Netty服务端初始化的时候创建了两个NioEventLoopGroup,一个是bossGroup,一个是workerGroup。对应着下面的Demo代码。BossGroup负责接收客户端connect,并生成对应的NioSocketChannel,workerGroup负责channel的读取和写入操作。
EventLoopGroup acceptor = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(acceptor,worker);
- 序号2,Netty服务端绑定端口,等待客户端连接。对应代码:
ChannelFuture f = bootstrap.bind(port).sync();
- 序号3,客户端连接服务端,bossGroup生成对应客户端的Channel。
- 序号4,客户端连接后对应服务端生成的Channel,Channel会注册到NioEventLoop上。多个Channel可以注册到同一个NioEventLoop,前面我们知道NioEventLoop对应Java NIO的selector,selector的作用就是轮询Channel并进行读写操作。
- 序号5,设置Channel对应的ChannelHandler(读写事件,编码,解码事件等),出现特定事件ChannelHandler做出处理,ChannelPipeline会维护多个ChannelHandler的处理顺序。对应代码
ch.pipeline().addLast(new SimpleServerHandler());
我们来总结一下,服务端端启动好以后,一个客户端连接了服务端,bossGroup(NioEventLoopGroup)接收到了连接,生成一个channel(NioSocketChannel),这个channel注册到一个NioEventLoop,NioEventLoop监听读写事件,有对应事件发生,channel的多个各种channelHandler(ChannelPipeline来维护处理顺序)对应数据处理,处理完成后返回一个channelFuture。