对Netty一些分析,主要是启动过程、线程模型、客户端和服务端TCP连接建立
netty作为一个优秀的网络通信框架,应该大部分人都接触过,之前看了mina,话说是同一个作者,里面的设计和思想还是比较接近的。不过要涉及到里面原理,可能就不是那么清楚。下面我将自己在工作中接触过,和自己看源码的一些理解记录下来。注:只是个人目前的一个理解程度,这篇文章会比较长,基本都是文字和贴的源码,但如果你读完,对netty多少会有些认识,可能有些地方写的不对,忘指出。
主从模型的启动过程
如果一开始看源码,应该是一脸蒙蔽,毕竟这么庞大的东西。首先我是通过几个重要的组件,看里面具体的实现,然后看netty.bind()怎么启动的,看到bind肯定说的是服务端了,客户端应该差不多了,还没这么负载。
学习的时候,如果带着问题去看,应该就比较容易一些。
为什么说Netty是NIO
1:NIO既同步非阻塞,为什么是非阻塞的呢,netty用到多路复用技术。
2:那netty实现NIO主要有哪些组件呢。
//1:这个我觉得是最核心的一个了,看到eventLoop 很容易会想到,这是一个线程池。确实,没错,netty的线程池,是在java的ExecutorService上进行了封装
NioEventLoopGroup();
//2:服务器引导类,这个没什么说的,就是用来引导启动的
ServerBootstrap
//3:channel,这个最终是一个channel接口,这里会设计到netty的线程模型
NioServerSocketChannel
//4:对channel中进行注册
ChannelInitializer
3:上面这几个是我自己对netty印象最深的几个组件,当然也是最重要的,下面下看一段简单的代码,然后开始看源码,了解整个的启动过程。
public void bind(int port) {
final EventLoopGroup bossGroup = new NioEventLoopGroup();
final EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1023)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_RCVBUF,1024 * 32)
.childOption(ChannelOption.SO_SNDBUF, 1024 * 32)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,new WriteBufferWaterMark(0,128 * 1023))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new IdleStateHandler(0, 0, 300));
}
});
ChannelFuture future = serverBootstrap.bind(port).sync();
logger.debug("websocket port: {}",3322);
future.channel().closeFuture().sync(); // TODO
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
这是主从模型,boss是用来对客户端的请求进行分发,work是真正用来工作的线程池。每个线程池数量,看里面源码就能知道,如果自己不定义的话,默认是CPU合数的两倍。**但是不太懂,明明启动的时候,bossGroup就只分配一次serverChannel,那为什么项目中不设置一一个线程呢,难道后面还会再分配,但不清楚[参考](https://blog.csdn.net/u013076044/article/details/103933774)**
在创建服务端的时候实例化了 2 个 EventLoopGroup,1 个 EventLoopGroup 实际就是一个 EventLoop 线程组,负责管理 EventLoop 的申请和释放。
bossGroup 线程组实际就是 Acceptor 线程池,负责处理客户端的 TCP 连接请求,如果系统只有一个服务端端口需要监听,则建议 bossGroup 线程组线程数设置为 1。
workerGroup 是真正负责 I/O 读写操作的线程组,通过 ServerBootstrap 的 group 方法进行设置,用于后续的 Channel 绑定。
NIOEventLoopGroup后面会继承MultithreadEventExecutorGroup,这个的构造方法有对里面线程进行初始化。
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
//这个构造,主要看这里,线程的初始化就好。最后反复的实例SingleThreadEventExecutor,
//这个我之前看过一次源码,里面有个takeTask()方法,用来获取当前队列中的任务,并实现了阻 塞等待,周期等功能。
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
} finally {
//这里面应该就是一些监听触发,netty很多逻辑,会通过监听和回调去触发。
}
}
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
te