📚Linux系统IO线程模型
在Linnux系统级别,提供了5种IO线程模型,分别是同步阻塞IO、同步非阻塞IO、多路服务用IO、信号驱动IO和异步IO。
在了解IO模型之前,我们要对用户空间和内核空间有一定的了解,在进行IO的时候,用户线程它不是直接操作的数据,而是把请求交给了操作系统,由操作系统与磁盘或网卡进行交互获取数据,之后,数据会被操作系统放入内核缓冲区,然后用户线程会从内核缓冲区服务数据到用户缓冲区。
对于同步阻塞IO,在数据被从内核缓冲区成功复制到用户空间缓冲区前,用户线程是被阻塞的,也就是说,直到数据被准备好以前,用户线程什么也做不了。
对于同步非阻塞线程,当数据读取请求交给操作系统后,用户线程就会返回,并不会一直阻塞,一般在这种情况下,需要不断轮询来确定数据是否已经在内核空间了,如何是,则会同步的将数据从内核空间复制到用户空间缓冲区。
以上两种IO模型,都是一个线程对应一个读写的,在并发量比较大的情况下,会造成线程数量暴增,而IO多路复用技术就是为了解决这个问题,在这种模型下,一个线程可以监听多个线程对应的IO数据是否已经就绪,如果就绪再进行后续处理,一般被用在网络IO上,而磁盘IO并不会用到这种技术。
对于信号驱动IO,可以与多路服务用IO进行对比着理解,多路复用IO在检查IO状态时是通过轮询的方式实现的,这种方式在被监听线程数量比较多时,效率比较低,而信号驱动IO指的是,不需要用户线程不断轮询,而是当数据被读取到内核缓冲区后,系统会自动通知用户线程,这样用户线程就能被动的知道哪些数据已经准备好了,从而避免不断轮询,目前Java NIO底层就是基于这种信号驱动IO实现的IO多路复用。
而异步IO把数据从内核缓冲区到用户缓冲区也进行了异步化,就是,当数据已经从内核缓冲区到用户缓冲区时,系统才会通知用户线程,这是真正意义的异步IO。
Java中的NIO是采用多路复用的IO模型,底层基于调用的是基于信号驱动实现的多路服务用IO,也就是我们常说的epoll。
📚Java NIO概述
Java目前支持的IO模型有BIO、NIO和AIO,NIO是在jdk1.4中被引入的,主要为了解决BIO服务无法应对高性能网络编程的问题,Java NIO底层基于epoll模型实现,编程模型中主要涉及Buffer、Channel和Selector等概念,其中Buffer是数据的载体在Channel中传递,Channel类似于BIO中流的概念,但Channel是全双工的,Selector是选择器,主要用于监听注册在他上面的Channel,Selector只适用于网络IO,对于文件IO,并没有Selector的概念。
下面看两个基于BIO和NIO开发的Demo
- BIO
package test.netty.bio;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
public class TimeServer {
public static void main(String[] args) throws Exception {
int port = 8080;
ServerSocket serverSocket = null ;
try {
serverSocket = new ServerSocket(port) ;
System.out.println("Time server is started in port " + port);
Socket socket = null ;
while (true) {
socket = serverSocket.accept();
new Thread(new TimeServerHanler(socket)).start();
}
}finally {
}
}
private static class TimeServerHanler implements Runnable {
private Socket socket ;
public TimeServerHanler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
out = new PrintWriter(socket.getOutputStream());
String currentTime ;
String body ;
//while (true) {
body = in.readLine() ;
/*if(Objects.isNull(body)) {
break;
}*/
//}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? dateFormat.format(new Date()) : "BAD ORDER";
out.println(currentTime);
}catch (Exception ex) {
try {
in.close();
out.close();
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
package test.netty.bio;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
public class TimeClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 8080);
//socket.bind(new InetSocketAddress(8080));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.write("QUERY TIME ORDER");
//printWriter.ne
printWriter.flush();
System.out.println("send order 2 server success.");
String response = bufferedReader.readLine();
System.out.println("time server response : " + response);
}
}
- NIO
package test.netty.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
public class TimeServer2 {
/**
* NIO Server
* 1. 打开ServerSocketChannel,监听客户端连接
* 2. 绑定监听端口,设置为非阻塞
* 3. 创建Reactor线程,创建多路复用器,并启动线程
* 4. 将ServerSocketChannel注册到Reactor线程 多路复用器上,监听accept事件
* 5. 多路复用器在while中无限循环 accept事件
* 6. 拿到客户端连接对应的SocketChannel
* 7. 将SocketChannel也设置为非阻塞模式
* 8. 将SocketChannel注册到Reactor线程的Selector上,并监听read事件
* 9. 读取数据到bytebuffer
* 10. 把数据交给业务线程池进行处理
* 11. 将encode后的pojo通过SocketChannel写回给客户端
* */
public static void main(String[] args) {
MultiplexerTimeServer multiplexerTimeServer = new MultiplexerTimeServer(8080);
//该线程为Reactor线程
new Thread(multiplexerTimeServer,"MultiplexerServer").start();
}
private static class MultiplexerTimeServer implements Runnable {
//Reactor Selector
private Selector selector ;
//Server Main Thread
private ServerSocketChannel serverSocketChannel ;
private volatile boolean stop;
public MultiplexerTimeServer(int port) {
init(port);
}
//init server
public void init(int port) {
try {
/**
*
* */
serverSocketChannel = ServerSocketChannel.open();
selector = Selector.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(port), 1024);
//监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("server initialize complete.");
}catch (Exception ex) {
System.out.println("init server error," + ex.getMessage());
ex.printStackTrace();
}
}
public void stop() {
this.stop = true;
}
@Override
public void run() {
/**
* 这里是reactor线程逻辑
* 主要负责监听客户端连接事件
* */
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
//这里拿到所有连接成功的客户端连接
Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
SelectionKey selectionKey = null ;
while (selectionKeyIterator.hasNext()) {
selectionKey = selectionKeyIterator.next();
selectionKeyIterator.remove();
try {
handleInput(selectionKey);
}catch (IOException ex) {
if (selectionKey != null) {
selectionKey.cancel();
if (selectionKey.channel() != null) {
selectionKey.channel().close();
}
}
}
}
}catch (Exception ex) {
ex.printStackTrace();
}
}
}
//处理接入成功的请求
private void handleInput(SelectionKey key) throws IOException {
if(key.isValid()) {
//判断是否为接入状态
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
//将对应的客户端连接重新注册到Selector上,此时监听READ事件
socketChannel.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()) {
SocketChannel socketChannel = (SocketChannel)key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(byteBuffer);
if(readBytes > 0) {
//复位读写位置
byteBuffer.flip();
byte[] bs = new byte[byteBuffer.remaining()];
byteBuffer.get(bs);
String body = new String(bs, "UTF-8");
System.out.println("Time server receive order:" + body);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? dateFormat.format(new Date()) : "BAD ORDER";
//将响应写回到客户端
byte[] respbs = currentTime.getBytes();
ByteBuffer respBuffer = ByteBuffer.allocate(respbs.length);
respBuffer.put(respbs);
respBuffer.flip();
socketChannel.write(respBuffer);
}
}
}
}
}
}
package test.netty.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class TimeClient2 {
private static class TimeClientHandler implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private boolean stop;
public TimeClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
}catch (Exception ex) {
ex.printStackTrace();
System.exit(-1);
}
}
@Override
public void run() {
try {
doConnect();
}catch (IOException ex) {
ex.printStackTrace();
System.exit(-1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
SelectionKey selectionKey = null;
while (selectionKeyIterator.hasNext()) {
selectionKey = selectionKeyIterator.next();
selectionKeyIterator.remove();
try {
handleInput(selectionKey);
}catch (Exception ex) {
selectionKey.cancel();
if (selectionKey.channel() != null) {
selectionKey.channel().close();
}
}
}
}catch (Exception ex) {
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if(key.isValid()) {
SocketChannel socketChannel = (SocketChannel)key.channel();
if(key.isConnectable()) {
if(socketChannel.finishConnect()) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
}else {
//连接失败
System.exit(-1);
}
}
if(key.isReadable()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readBs = socketChannel.read(byteBuffer);
if (readBs > 0) {
byte[] bs = new byte[byteBuffer.remaining()];
byteBuffer.get(bs);
String body = new String(bs, "UTF-8");
System.out.println("Now is " + body);
this.stop = true;
}else if (readBs < 0){
key.cancel();
socketChannel.close();
} else {
//ignore
}
}
}
}
//创建于服务端的连接
private void doConnect() throws IOException {
if(socketChannel.connect(new InetSocketAddress(host,port))) {
//连接成功,注册读事件
socketChannel.register(selector, SelectionKey.OP_READ);
//开始写
doWrite(socketChannel);
}else {
//注册连接成功事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel socketChannel) throws IOException {
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer byteBuffer = ByteBuffer.allocate(req.length);
byteBuffer.put(req);
byteBuffer.flip();
socketChannel.write(byteBuffer);
if (!byteBuffer.hasRemaining()) {
System.out.println("send command to server success.");
}
}
}
}
从代码量上,可以很明显的看出,基于BIO实现的时间服务器要比基于NIO实现的时间服务器要简单的多,但是BIO无法应对大量的客户端连接,而NIO利用多路复用技术实现了一个reactor线程同时监听很多客户端连接,极大的避免了在大并发情况下服务器端线程数量暴增的问题。
那么,为什么还要使用netty呢,主要有以下几个原因
- netty提供了简单的编程模型,解决了nio的易用性问题
- netty解决了nio的很多内部bug,比如epoll空转问题
- netty提供了很多通用组件来解决网络编程中的通用问题,例如半包/粘包问题
netty虽好,但也不是在所有场景下都有必要用,对于一些比较简单的应用程序,本身连接数量就很少,而须后续也不会再有所发展,这时候,反而BIO是更好的选择,原因在于可以大大减少编码的复杂度,相对于BIO来说,无论是NIO还是Netty,写起来都会复杂些。
📚Netty概述
netty是一个网络编程框架,主要用来编写Client-Server模式下的应用程序,netty基于java nio开发,实现了基于事件的IO模型,同时,基于屏蔽的大量java nio的复杂细节,解决了一些java nio的bug,提供了很多在网络编程过程中通用的组件,例如解决粘包/半包问题、序列化等等,netty是目前最流行的tcp通信框架。
先用netty来改造一下上面的TimeServer
package timeserver;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
public class NettyTimeServer {
public static void main(String[] args)throws Exception {
int port = 8080;
new NettyTimeServer().bind(port);
}
public void bind(int port) throws Exception {
//监听Accept实现的线程
EventLoopGroup bossGroup = new NioEventLoopGroup();
//监听Read事件及处理业务的线程
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024);
//设置处理业务逻辑的Handler
b.childHandler(new ChildChannelHandler());
ChannelFuture future = b.bind(port).sync();
future.channel().closeFuture().sync();
}finally {
//停掉线程池
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeServerHandler());
}
}
private static class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//super.channelRead(ctx, msg);
ByteBuf byteBuf = (ByteBuf)msg;
byte[] bs = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bs);
String body = new String(bs, "UTF-8");
System.out.println("Time Server receive order:" + body);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? simpleDateFormat.format(new Date()) : "BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(byteBuf);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//super.channelReadComplete(ctx);
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//super.exceptionCaught(ctx, cause);
ctx.close();
}
}
}
package timeserver;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyTimeClient {
public static void main(String[] args) throws Exception {
new NettyTimeClient().connect("127.0.0.1", 8080);
}
public void connect(String host, int port) throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
}finally {
workerGroup.shutdownGracefully();
}
}
public static class TimeClientHandler extends ChannelInboundHandlerAdapter {
private ByteBuf firstMsg;
public TimeClientHandler() {
byte[] bs = "QUERY TIME ORDER".getBytes();
firstMsg = Unpooled.copiedBuffer(bs);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//链接成功后,发送消息到服务端
ctx.write(firstMsg);
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
byte[] req = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("Now is " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("ex " + cause.getMessage());
ctx.close();
}
}
}
📚半包/粘包问题
粘包/半包
问题是tcp编程过程中必然会面临的一个问题,由于tcp协议中的数据是以码流的方式传输的,不包含任何的业务逻辑,在传输数据时,会根据tcp缓冲区的使用情况进行数据包的划分,这就造成了上层应用中的数据大的被拆分,小的被合并的结果,也就是粘包/拆包问题。一般解决粘包拆包问题的通用方案有三种
- 根据特殊字符分隔数据包
- 根据固定长度分隔数据包
- 将消息分为消息头和消息体,在消息头中记录消息体的长度,这也是最常用的一种
针对这三种解决方案,netty内置了三个具体的实现来解决对应的问题,这三个实现分别是
- DelimiterBasedFrameDecoder/LineBasedFrameDecoder
- FixedLengthFrameDecoder
- LengthFieldBasedFrameDecoder/LengthFieldPrepender
📚序列化和反序列化
序列化和反序列化经常用来表示把一个Java POJO转换成二进制字节数组和从二进制字节数组中解析出Java POJO对象,有时候也被称为编码和解码,实际上,在netty中有两次编码和解码
- 客户端第一次是将POJO对象转换成二进制字节数组
- 客户端第二次将字节数组在tcp协议层进行编码,也就是我我们前面说的粘包/半包的概念中涉及到的编码和解码
- 服务端第一次按照tcp协议格式解析出对应的字节数组
- 在将字节数组反序列化成POJO对象
netty对着两个步骤的抽象分别是
- MessageToMessageDecoder/MessageToByteDecoder
- MessageToMessageEncoder/MessageToByteEncoder
这里的序列化和反序列化对应的就是MessageToMessageEncoder和MessageToMessageDecoder,在java中,最常见的序列化方式是Serializable,但是这种序列化方式有很大的缺陷
- 不能跨平台
- 序列化后的码流太大
- 性能比较差
业界有一些比较流程的开源序列化框架,例如avro、facebook的thrift、google的protobuf、messagepack等等,netty被用的最多的就是用于构建分布式系统的底层通信,所以一般都会采用这些性能比较好的、能够支持跨平台的序列化框架。
📚简述基于netty的自定义协议
协议一般分为两种,一种是共有协议,类似于http、ftp等,一种是私有协议,最典型的,我们在使用dubbo的时候用到的dubbo协议,这些协议都属于应用层协议,是用于满足特定业务需求而存在的。
私有协议包含一个自定义的协议栈,主要设计协议的数据结构,例如,可以让协议包含Header和Body两个部分,Header中包含一些公共信息,crcCode、length、sessionId、type、priority、attachment等,body存储真实的消息数据,通过length属性配合netty的LengthFieldBasedFrameDecoder很容易解决粘包/半包问题,crcCode可以理解为一个协议的魔法数字,用来标记当前数据包是指定的协议,sessionId表示当前链接为唯一标识,也可以标识节点的唯一标识,type标识数据的类型,例如心跳数据、业务数据、握手数据等,通过还可以区分不同来下的消息的流向,attachement是一个扩展属性,可以加一些附加的动态数据,格式为(size)(keylength)key(valuelength)value[…],消息体的格式为(length)message,之所以增加这么多length,主要是为了能正确的读取数据。
另外在实现私有协议时,还需要考虑其他的问题,例如IP白名单验证、重复连接验证、心跳保活、断线重连等。
对于IP白名单验证,我们可以在activeChannel()中发送连接握手消息,服务端在readChannel中读取消息,如果是连接握手消息的话,首先根据缓存判断是否已经创建过连接,如果已经连接过则返回-1,表示连接被拒绝,然后验证IP白名单,如果不在白名单中,则返回-1,拒绝连接;然后,客户端在readChannel中读取到握手响应消息,判断是否通过,如果没通过,则调用ctx.close()关闭连接,如果通过,则将消息透传到下一个Handler。
当客户端的处理心跳的Handler收到被握手认证的Handler透传过来的握手成功的消息后,启动一个定时任务,每5秒钟通过ctx向服务端发送一个心跳消息包,服务器在readChannel中判断,如果是心跳包,直接返回应答消息。
短信重连逻辑可以写在try {} finally{ 断线重连 } 。
📚netty server端的源码分析
netty server端主要包含两方面的工作,一是启动在指定端口上的监听,二是接收客户端连接并处理客户端数据。我们先回顾一下利用netty创建server端的过程
1. 创建ServerBootstrap启动辅助类
2. 设置Reactor主从线程池
3. 设置Server端channel类型,NioServerSocketChannel
4. 设置tcp参数,例如backlog
5. 设置childHandler,在ChannelPipeline中设置ChannelHandler链
6. 调用bind方法启动server在指定端口上的监听
那么,我们先来看一下,Reactor主从线程池的设置
ServerBootstrap.java
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup); //设置master线程池
this.childGroup = childGroup; //设置slave线程组
}
AbstractBootstrap.java
public B group(EventLoopGroup group) {
this.group = group;
}
可以看到, master线程池被设置到了父类中,而slave线程池设置到了ServerBootstrap中,其实这也很好理解,因为父类抽象了处理Accept事件的核心逻辑,而对Accept事件的处理需要用到master线程池。
下面我们从bind方法入手,来看一下启动的整个流程
AbstractBootstrap.java
private ChannelFuture doBind(final SocketAddress localAddress) {
//① 这里是一个重点
final ChannelFuture regFuture = this.initAndRegister();
....
//②启动server在端口上的监听
doBind0(regFuture, channel, localAddress, promise);
}
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
/*
创建NioServerSocketChannel实例
这里的channelFactor是在我们调用ServerBootstrap.channl方法时被初始化的
public B channel(Class<? extends C> channelClass) {
return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory((Class)ObjectUtil.checkNotNull(channelClass, "channelClass"))));
}
可以看到用到的是ReflectiveChannelFactory,这个Factory会通过反射的方式调用NioServerSocketchannel的构造函数
构造实例对象
*/
channel = this.channelFactory.newChannel();
//这里也是一个重点,主要完成的是对NioServerSocketChannel的初始化工作
this.init(channel);
} catch (Throwable var3) {
if (channel != null) {
channel.unsafe().closeForcibly();
return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
}
return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
}
/**
重点来了,这里是将NioServerSocketChannel注册到EventLoop上,这里用到的EventLoop就是下面方法中ch.eventLoop()拿到的实例。
我们一点一点看,下看group(),这个方法最终会调用
public final EventLoopGroup group() {
return this.group;
}
返回的实际上就是我们在初始化ServerBootstrap中调用group时指定的bossGroup
register(channel)我们移步到下面,单独看一下
*/
ChannelFuture regFuture = this.config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
//这个方法在父类中是抽象的,在子类ServerBootstrap中实现
void init(Channel channel) {
setChannelOptions(channel, (Entry[])this.options0().entrySet().toArray(newOptionArray(0)), logger);
//设置tcp连接参数
setAttributes(channel, (Entry[])this.attrs0().entrySet().toArray(newAttrArray(0)));
ChannelPipeline p = channel.pipeline();
//这里需要注意以下,childHandler是我们制定的slave线程池
final EventLoopGroup currentChildGroup = this.childGroup;
final ChannelHandler currentChildHandler = this.childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(0));
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(0));
//这里开始初始化针对master的ChannelHandler
p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
//这里实际上是获取我们在初始化时利用ServerBootstrap.handler()方法制定的ChannelHandler对象,
//如果没有特殊需求,一般不会制定
ChannelHandler handler = ServerBootstrap.this.config.handler();
if (handler != null) {
pipeline.addLast(new ChannelHandler[]{handler});
}
/**
这里是重点了
先从NioServerSocketChannel中获取与之绑定的EventLoop,EventLoop可以理解为线程,EventLoopGroup可以理解为线程池
在线程上绑定了一个任务,这个任务是在ChannelPipeline上绑定了一个handler
这个handler的名称是ServerBootstrapAcceptor
从名字上看,就是处理tcp的accept时间的,也就是reactor主从模式下master的任务
至于ServerBootstrapAcceptor为什么会处理到accetp事件,还的回到上面的init方法中去看
*/
ch.eventLoop().execute(new Runnable() {
public void run() {
pipeline.addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
}
});
}
}});
}
//由于上面用到的EventLoopGroup类型为MultithreadEventLoopGroup,所以,我们直接看一下这个类的register方法
public ChannelFuture register(Channel channel) {
return this.next().register(channel);
}
public EventLoop next() {
return (EventLoop)super.next();
}
/**
super.next(),这里用到了一个chooser,从名字上看是一个选择器,类型为EventExecutorChooser
在netty中提供了两种类型的chooser,一种是GenericEventExecutorChooser,一种是PowerOfTwoEventExecutorChooser
GenericEventExecutorChooser:
public EventExecutor next() {
return this.executors[Math.abs(this.idx.getAndIncrement() % this.executors.length)];
}
PowerOfTwoEventExecutorChooser:
public EventExecutor next() {
return this.executors[this.idx.getAndIncrement() & this.executors.length - 1];
}
可以看到,这两种类型的主要区别是获取EventExecutor实例的算法不同,第一个通过取余的方式来定位一个executor
而第二个是通过位运算,也就是说,当EventLoopGroup中EventLoop的数量为2的整数次幂时,会用第二种方式来选择executor
这种方式更加高效,与HashMap在初始化时将容量动态调整为2的整数次幂原理相同,都是为了提高运算速度。
*/
public EventExecutor next() {
return this.chooser.next();
}
然后再看一下register方法,这里的register方法是在SingleThreadEventLoop中
public ChannelFuture register(Channel channel) {
return this.register((ChannelPromise)(new DefaultChannelPromise(channel, this)));
}
public ChannelFuture register(ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
这里,我们先看unsafe方法返回的是什么
public AbstractNioChannel.NioUnsafe unsafe() {
return (AbstractNioChannel.NioUnsafe)super.unsafe();
}
AbstractUnsafe
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
eventLoop.execute(new Runnable() {
public void run() {
AbstractUnsafe.this.register0(promise);
}
});
}
private void register0(ChannelPromise promise) {
AbstractChannel.this.doRegister();
this.safeSetSuccess(promise);
AbstractChannel.this.pipeline.fireChannelRegistered();
}
AbstractNioChannel.java
protected void doRegister() throws Exception {
这里就到了jdk的NIO代码了,
this.selectionKey = this.javaChannel().register(this.eventLoop().unwrappedSelector(), 0, this);
}
但是这里注册的时间类型是0,而不是Accept(16),实际上注册0时,简单的说就是能接收到所有事件类型,个人理解应该是
在服务端启动的时候防止有客户端连接被漏掉,在注册完成以后,会修改注册事件类型为accept。
到此,Netty的Server端启动流程就分析完了,实际上并不太复杂
- 初始化参数,例如bossGroup/workerGroup/NioServerSocketChannel/tcp参数/childHandler
- 调用bind开始启动流程
- 通过反射创建NioServerSocketChannel实例
- 初始化NioServerSocketChannel,例如初始化tcp参数,初始化ChannelHandler
- 在bossGroup中设置一个ServerBootstrapAcceptor,由bossGroup中的线程驱动,作用是将read事件分发给workerGroup
- 紧接着,就是注册NioServerSocketChannel,这一步实际上就做了一件事,在Selector上绑定accept事件
- 最后,调用doBind方法启动NioServerSocketChannel在指定端口上的监听