概述
当Netty与客户端建立连接后(SocketChannel建立),通道处于 OP_READ 状态(对读感兴趣),这时就需要分配一定的缓冲区(如ByteBuffer,实则为字节数组)来用于数据的读取。之前学习 Java NIO:Buffer缓冲区源码详解以及“零拷贝” 的时候我们其实已经知道 NIO 中存在堆内和堆外两种缓冲,在 Netty 底层的实现过程中是怎么来做缓存分配的呢,又是怎么决定分配多大的缓冲区用于Channel的读与写呢?
Netty 提供了一种“可预测性” 的分配方式来处理问题,这种方式可以通过一定策略性的预判来决定分配缓冲区的大小。该策略解决的问题:
- 相对于实际数据分配过大的缓冲区,浪费空间,给 GC 增加压力
- 相对于实际数据分配过小的缓冲区,当前缓冲区装不下所有数据,缓冲区将“扩容”,就会产生数据拷贝的动作,牺牲性能
源码解析
以一个简单的 Netty 服务端为例开始:
package com.leolee.netty.secondExample;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @ClassName MySocketServer
* @Description: socket服务端
* @Author LeoLee
* @Date 2020/8/23
* @Version V1.0
**/
public class MySocketServer {
public static void main(String[] args) throws InterruptedException {
//定义线程组 EventLoopGroup为死循环
//boss线程组一直在接收客户端发起的请求,但是不对请求做处理,boss会将接收到的请i交给worker线程组来处理
//实际可以用一个线程组来做客户端的请求接收和处理两件事,但是不推荐
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//启动类定义
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
//子处理器,自定义处理器,服务端可以使用childHandler或者handler,handlerr对应接收线程组(bossGroup),childHandler对应处理线程组(workerGroup)
.childHandler(new MySocketServerInitializer());
//绑定监听端口
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
//定义关闭监听
channelFuture.channel().closeFuture().sync();
} finally {
//Netty提供的优雅关闭
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
在这个示例中,serverBootstrap.bind(8899).sync() 之前是一些服务端启动初始化相关配置的一些操作,并没有真正的启动,我们暂不详解(之后会总结),目前只关注 serverBootstrap.bind(8899) 这一点,因为这一步才是 Netty 真正开始启动服务。
让我们从 bind() 开始一步一步的解读源码
AbstractBootstrap类:
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind(SocketAddress localAddress) {
validate();
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
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.
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;
}
}
至此我们找到了关键方法