Reactor开发模式
SocketChannel 在client端监听op_connect,op_write,op_read事件,在server只监听op_write,op_read事件,ServerSocketChannel在server端运行,只监听op_accept事件
netty对Reactor模式的实现
首先ServerSocketChannel是可以创建子的socketchannel,创建是由
BootstrapChannelFactory这个工厂类根据传入的class反射来创建,
所以
serverBootstrap.group().channel(xxx.class) .channel这个时候就是由上面的工厂类利用反射动态的创建一个channel,绑定在对应的group上
serverBootstrap.group(bossGroup,workerGroup) .channel传进来的会创建一个channel绑定在bossGroup上,然后传进来的ServerSocketChannel可以创建子socketchannel绑定在wokergroup上
再和上面的reactor开发模式对应,就是serversocketchannel绑定在bossGroup上来负责监听op_accept事件,socketchannel绑定在workergroup上来负责监听其他read write事件
粘包和半包
TCP为什么会出现粘包和半包(UDP其实没有这个问题):主要是因为TCP是流协议
常用的解决这种问题的方案:要么短链接,要么长链接中采用封装成桢framing技术
netty对使用Framing粘包半包的支持:
固定长度的方式:一定就按固定长度的来传,不够就补空,解码FixedLengthFrameDecoder
分割符的方式:以指定的分割符来分割,但要考虑内容中如果有这个分割符需要转义,解码DelimiterBasedFrameDecoder
固定长度字段存内容的长度信息:要考虑预留多少位来存储这个长度,解码器是LengthFieldBasedFramedDecoder,编码器LengthFieldPrepender
上面的编码器都继承了ByteToMessageDecoder这个抽象类
ByteToMessageDecoder 继承 ChannelInboundHandlerAdapter
ChannelInboundHandlerAdapter 中有一个channelRead方法处理数据,ByteToMessageDecoder 中就具体实现了channelRead方法
ByteToMessageDecoder中维护了一个ByteBuf类型的数据积累器cumulation,如果是第一笔数据直接赋值给cumulation,不是第一笔的就追加在cumulation后面,然后调
callDecode(ctx, cumulation, out);
//其中callDecode中会调
decodeRemovalReentryProtection(ctx, in, out);
//然后这个又会调decode(ctx, in, out);//这个decode是ByteToMessageDecoder中提供的抽象方法,具体由上面的各种Decoder来实现
以FixedLengthFrameDecoder为例子
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
private final int frameLength;
public FixedLengthFrameDecoder(int frameLength) {
if(frameLength <= 0) {
throw new IllegalArgumentException("frameLength must be a positive integer: " + frameLength);
} else {
this.frameLength = frameLength;
}
}
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List
一次编码器ByteToMessageDecoder,解决粘包半包问题,将原始的数据流(io.netty.buffer.ByteBuf) 变成用户的数据(io.netty.buffer.ByteBuf这里的用户的数据还是字节流,但是没有粘包半包的问题了)
二次编码器MessageToMessageDecoder, 将一次编码处理后的字节数据(io.netty.buffer.ByteBuf)变成java的对象(java object)
KeepAlive和Idle检测
netty源码核心包
io.netty.transport: 实现协议
io.netty.codec: 实现编解码的部分
io.netty.handler: 各种handler的实现
其他工具类:io.netty.buffer,io.netty.common,io.netty.resolver
netty的启动
从线程角度看,启动的时候一个是启动线程要做几件事情:创建selector,创建并初始化serverSocketChannel,给serversocketChannel从boss group中选择一个NioEventLoop
一个是boss thread要做的几件事情:将serverSocketChannel注册到选择的NioEventLoop上的selector(也就是第一步创建的selector),绑定地址启动,然后就可以开始注册OP_acceptor事件到selector上
从源码的角度上看
new NioEventLoopGroup的时候就会new 一个selector
创建完group之后,调bind(port)异步去绑定地址,在这个bind的过程中有一句
//类AbstractBootstrap中的dobind方法
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
//初始化一个serverSocketChannel,register到selector上,返回的是一个regFuture,下面会根据isDone判断是否完成了
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) { //已经完成
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
//没完成,建一个task绑定在listener上
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
ServerBootstrapAcceptor:负责接收客户端连接创建连接后的连接初始化工作
netty构建连接
构建连接的主线:
对于boss thread:
1 NioEventLoop中的selector轮询创建连接事件(OP_Accept)
2 创建socket Channel
3 初始化socket channel 并从work group中选择一个NioEventLoop
对于worker thread:
1 将socket channel 注册到上面第3步选择的NioEventLoop的selector(这里怎么选择出来一个NioEventLoop有多种,策略模式)
2 注册OP_READ 到selector
netty 读取数据
selector(多路复用器)接收到OP_read事件
处理这个事件,NioSocketChannel.NioSocketChannelUnsafe.read()
1) 分配一个byte buffer来接收数据,初始化这个buffer是1024个字节
2)从channel接收数据到byte buffer
3)记录实际接收数据的大小,调整下次分配byte buffer的大小
4)触发pipeline.fireChannelRead(bytebuf)把读到的数据传播出去
5)判断接收数据的bytebuffer是否满载而归,是的话继续读取直到没有数据或者已经满来16次,否则本轮读取结束,等待下次OP_read事件
读取数据的关键代码
SocketChannelImpl.read(java.nio.ByteBuffer)
NioSocketChannel.read是读取数据,NioServerSocketChannel.read是创建连接
pipeline.fireChannelReadComplete() 一次读事件完成
pipeline.fireChannelRead(bytebuf) 一次读数据完成
一次读事件包括多个读数据
netty处理业务逻辑
inboud和outbound
head - handler1 - handler2 - tail
inbound是从head到tail,outbound是从tail到head
每一个handler都是实现了ChannelInboundHandler的