如想了解更多更全面的Java必备内容可以阅读:所有JAVA必备知识点面试题文章目录:
来…直接进入主题:
Netty网易云课堂在线学习资料:https://study.163.com/course/courseMain.htm?courseId=1209596850
Netty是由JBOSS提供的一个异步的、基于事件驱动的网络应用开源框架。用于快速开发 可维护的 高性能 协议服务器和客户端。
Netty是底层完全基于NIO实现 的。提高了吞吐量,降低了延迟;减少了资源消耗;减少了不必要的内存复制。
传统阻塞IO服务模型,简略版如下:
针对传统阻塞I/O服务模型的 2 个缺点,解决方案:
Reactor 模式,简略版如下:
说明:Reactor在一个单独的线程中运行,负责监听和分发事件。
工作原理和简单示意图:
优点: 模型简单、没有多线程、进程通信、竞争等问题。
缺点: 只有一个线程;handler在处理某个连接上的业务时,整个线程无法处理其他连接事件;可靠性问题,线程意外终止或者死循环可能导致整个通讯模块不可用。
使用场景: 客户端数量有限 或者 业务处理非常快速的场景可使用单Reactor单线程。
工作原理和简单示意图:
优点: 可以充分利用多核CPU进行业务处理。
缺点: 多线程之间进行数据共享和访问比较复杂;单个Reactor处理所有事件的监听和响应,在高并发场景下容易出现性能瓶颈。
工作原理和简单示意图:
注:Reactor主线程可以对应多个Reactor子线程,即MainReactor可以对应多个SubReactor。
优点: MainReactor与SubReactor的数据交互简单职责明确,MainReactor只需要接收新连接,SubReactor完成后续业务处理。
缺点: 编程复杂度较高。
应用场景: Nginx主从Reactor多线程模型;Memcache主从多线程;Netty对主从Reactor多线程的引进并做了一些改良等等。
工作原理和简单示意图:
客户端/服务器端-简单通讯案列:
maven 引入netty依赖:
<dependencies>
<!-- 引入netty依赖-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
Server端:
package com.netty.test01;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author 精彩猿笔记
*/
public class NettyServer {
public static void main(String[] args) {
//创建线程组BossGroup,处理连接请求
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//创建线程组workerGroup,完成和客户端具体的业务处理
//无参:则workerGroup含有的子线程个数=NettyRuntime.availableProcessors() * 2,即CPU核数*2
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器端启动对,用来配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//设置参数
//设置两个线程组
bootstrap.group(bossGroup,workerGroup);
//使用NioServerSocketChannel作为服务器的通道
bootstrap.channel(NioServerSocketChannel.class);
//设置线程队列得到连接个数
bootstrap.option(ChannelOption.SO_BACKLOG,1024);
//设置保持活动的连接状态
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
//给pipeLine设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//给workerGroup的EventLoop对应的管道设置处理器
//处理器可以用netty自带的,也可以自己创建处理器,如:NettyServerHandler
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("Netty服务端启动...is OK!");
//绑定一个端口并启动服务器,生成一个ChannelFuture对象 绑定:bind
ChannelFuture channelFuture = bootstrap.bind(8888).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
} finally {
//异常~优雅的关闭(netty提供)
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
Server端处理器:
package com.netty.test01;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* 自定义Handler需要继承netty规定的某个 XXXHandlerAdapter
* idea: Ctrl+O可以选择父类的方法进行重写
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 当通道有读取事件时,就会触发channelRead
* @param ctx:上下文对象,含有:channel、pipeLine
* @param msg:客户端发送的实际的数据
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//将msg转成一个ByteBuf
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("客户端[IP:"+ctx.channel().remoteAddress()+"]说:"+byteBuf.toString(CharsetUtil.UTF_8));
}
/**
* 数据读取完毕时,就会触发channelReadComplete
* @param ctx 上下文对象
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓存,并刷新 writeAndFlush
String resMag = "客户端,你好吖!";
//一般需要对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer(resMag,CharsetUtil.UTF_8));
}
/**
* 异常处理,关闭通道
* @param ctx 上下文对象
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭
cause.printStackTrace();
ctx.close();
}
}
Client端:
package com.netty.test01;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @author 精彩猿笔记
*/
public class NettyClient {
public static void main(String[] args){
//客户端需要一个事件循环组
EventLoopGroup eventExecutors = new NioEventLoopGroup();
try{
//创建客户端启动对象,客户端使用的是Bootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
//设置线程组
bootstrap.group(eventExecutors);
//设置通道处理类
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
//设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("Netty客户端启动...is OK!");
//启动客户端去连接服务器端 连接:connect
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
} finally {
//异常~优雅的关闭(netty提供)
eventExecutors.shutdownGracefully();
}
}
}
Client端处理器:
package com.netty.test01;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当通道就绪就会触发该方法
* @param ctx 上下文对象
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓存,并刷新 writeAndFlush
String resMag = "服务端,你好吖!";
//一般需要对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer(resMag,CharsetUtil.UTF_8));
}
/**
* 当通道有读取事件时,就会触发channelRead
* @param ctx:上下文对象,含有:channel、pipeLine
* @param msg:客户端发送的实际的数据
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//将msg转成一个ByteBuf
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("服务端[IP:"+ctx.channel().remoteAddress()+"]说:"+byteBuf.toString(CharsetUtil.UTF_8));
}
/**
* 异常处理,关闭通道
* @param ctx 上下文对象
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭
cause.printStackTrace();
ctx.close();
}
}
客户端运行结果:
Netty客户端启动...is OK!
服务端[IP:/127.0.0.1:8888]说:客户端,你好吖!
服务器端运行结果:
Netty服务端启动...is OK!
客户端[IP:/127.0.0.1:65403]说:服务端,你好吖!
当某些业务场景处理时间很长,可能导致客户端超时或者阻塞,则可以将任务加入到任务队列里,可以实现异步处理。
加入任务队列的方式:
【方案1】和【方案2】都有弊端:即当前处理器handler线程与普通任务的线程是同一个线程,当普通任务线程处理业务逻辑时,当前是阻塞的。因为整个是同一个线程。即如下代码:channelReadThreadId=taskThreadId=scheduleThreadId;channelReadThreadName=taskThreadName=scheduleThreadName。
【方案1】和【方案2】代码简单实现如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelReadThreadId="+Thread.currentThread().getId());
System.out.println("channelReadThreadName="+Thread.currentThread().getName());
//***************方案1:将一个普通任务加入到TaskQueue中
ctx.channel().eventLoop().execute(new Runnable() {
public void run() {
try {
//通过让线程休眠 模拟业务处理很长时间的场景
Thread.sleep(2*1000);
System.out.println("taskThreadId="+Thread.currentThread().getId());
System.out.println("scheduleThreadName="+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
});
//***************方案2:用户自定义定时任务,将任务加入到scheduledTaskQueue,10秒后执行
ctx.channel().eventLoop().schedule(new Runnable() {
public void run() {
try {
//通过让线程休眠 模拟业务处理很长时间的场景
Thread.sleep(3*1000);
System.out.println("scheduleThreadId="+Thread.currentThread().getId());
System.out.println("scheduleThreadName="+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
},10, TimeUnit.SECONDS);
//将msg转成一个ByteBuf
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("客户端[IP:"+ctx.channel().remoteAddress()+"]说:"+byteBuf.toString(CharsetUtil.UTF_8));
}
输出结果:
channelReadThreadId=15
channelReadThreadName=nioEventLoopGroup-3-1
客户端[IP:/127.0.0.1:56418]说:服务端,你好吖!
taskThreadId=15
scheduleThreadName=nioEventLoopGroup-3-1
scheduleThreadId=15
scheduleThreadName=nioEventLoopGroup-3-1
更好的解决方案是:将耗时任务添加到异步线程池中。
【方案3】:在Handler中加入业务线程池。【如下Handler线程与耗时任务线程非同一个线程,耗时任务执行完后,在执行pipeline write方法的时候,会将这个任务交给IO线程】【使用灵活,单异步会拖长接口的响应时间,可能导致在规定的响应时间内未响应。】
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**在handler中自定义业务线程池,这里在线程池中创建了8个线程*/
EventExecutorGroup executorGroup= new DefaultEventExecutorGroup(8);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelReadThreadId="+Thread.currentThread().getId());
System.out.println("channelReadThreadName="+Thread.currentThread().getName());
//将耗时任务1 放入线程池中
executorGroup.submit(new Callable<Object>() {
//重写Callable的call方法
public Object call() throws Exception {
//通过让线程休眠 模拟业务处理很长时间的场景
Thread.sleep(2*1000);
System.out.println(System.currentTimeMillis()+"task1ThreadId="+Thread.currentThread().getId());
System.out.println(System.currentTimeMillis()+"task1ThreadName="+Thread.currentThread().getName());
//耗时任务执行完后,在执行pipeline write方法的时候,会将这个任务交给IO线程。
ctx.writeAndFlush(Unpooled.copiedBuffer("task1执行完毕",CharsetUtil.UTF_8));
return null;
}
});
//将耗时任务2 放入线程池中
executorGroup.submit(new Callable<Object>() {
//重写Callable的call方法
public Object call() throws Exception {
//通过让线程休眠 模拟业务处理很长时间的场景
Thread.sleep(2*1000);
System.out.println(System.currentTimeMillis()+"task2ThreadId="+Thread.currentThread().getId());
System.out.println(System.currentTimeMillis()+"task2ThreadName="+Thread.currentThread().getName());
//耗时任务执行完后,在执行pipeline write方法的时候,会将这个任务交给IO线程。
ctx.writeAndFlush(Unpooled.copiedBuffer("task2执行完毕",CharsetUtil.UTF_8));
return null;
}
});
//将msg转成一个ByteBuf
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("客户端[IP:"+ctx.channel().remoteAddress()+"]说:"+byteBuf.toString(CharsetUtil.UTF_8));
}
}
【客户端】输出结果:
服务端[IP:/127.0.0.1:8888]说:客户端,你好吖!
服务端[IP:/127.0.0.1:8888]说:task1执行完毕
服务端[IP:/127.0.0.1:8888]说:task2执行完毕
【服务器端】输出结果:
channelReadThreadId=15
channelReadThreadName=nioEventLoopGroup-3-1
客户端[IP:/127.0.0.1:57223]说:服务端,你好吖!
task2ThreadId=17
task1ThreadId=16
task1ThreadName=defaultEventExecutorGroup-4-1
task2ThreadName=defaultEventExecutorGroup-4-2
【方案4】:Context中添加线程池。【是Netty的标准方式,将整个Handler交给业务线程池,不够灵活】
public class NettyServer {
/**自定义业务线程池,这里在线程池中创建了2个子线程*/
static final EventExecutorGroup executorGroup = new DefaultEventExecutorGroup(2);
............
//给workerGroup添加处理器
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
//给pipeLine设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//给workerGroup的EventLoop对应的管道设置处理器
//处理器可以用netty自带的,也可以自己创建处理器,如:NettyServerHandler
//socketChannel.pipeline().addLast(new NettyServerHandler());
//将NettyServerHandler加入到EventExecutorGroup线程池中
socketChannel.pipeline().addLast(executorGroup,new NettyServerHandler());
}
});
}
Netty的异步模型是建立在future和callback之上的。Future的核心思想是:在调用方法后会立马返回一个Future,后续可以通过Future去监听这个方法的执行过程(即:Futrue-Listener机制)。
当Future对象刚刚创建时,处理非完成状态,调用者可以通过返回的ChannelFuture来获取操作的执行状态,注册监听方法执行完成后的操作。
常见操作:
Bootstrap是引导的意思,主要负责配置整个Netty程序,串联各个组件。Bootstrap类是客户端程序启动引导类;ServerBootstrap是服务器端启动引导类。
常见的配置方法有:
Channel是Netty通信的组件,不同协议、不同阻塞类型的连接都有不同的Channel与之对应,常用的Channel类型:
ChannelHandler 是一个接口,处理I/O事件或者连接I/O操作,将其转发到其ChannelPipeline(业务处理链)中的下一个个处理程序。
ChannelHandler本身没有提供很多方法,但是其子类非常多提供里很多重要的基于事件监听的方法。
主要的子类关系图:
主要的基于事件监听的方法:
//一旦建立连接就会触发该事件,ChannelHandler接口定义的方法
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
}
//一旦断开连接时会触发,ChannelHandler接口定义的方法
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
}
//通道被注册时发生事件
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
//通道被注销时发生事件
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelUnregistered();
}
//通道就绪事件,活动状态
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
}
//通道非活动状态
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelInactive();
}
//通道读取数据事件
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
//通道读取数据完毕后的事件
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelReadComplete();
}
//通道异常事件
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
........等等
ChannelPipeline是Handler的集合,它负责处理和拦截入栈(inBound)或出栈(outBound)的事件和操作。
在Netty中每一个Channel都有一个ChannelPipeline与之对应,一个ChannelPipeline维护了一个由ChannelHandlerContext组成的双向链表。
入栈事件和出栈事件在一个双向链表中,入栈事件会从链表的head节点(头节点)往后传递到最后一个Handler;出栈事件会从链表tail节点(尾结点)往前传递到最前一个出栈的Handler;两种类型的Handler互不干扰。
关系图如下:
常用的方法:
ChannelHandlerContext 保存了Channel相关的所有上下文信息,同时关联一个ChannelHandler对象,即ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时也绑定了对应的PipeLine和Channel的信息。
常用的方法:
在Netty创建Channel实例后,一般需要设置ChannelOption参数。
常见的设置如下:
Unpooled 是Netty提供的一个专门用来操作缓冲区(Netty的数据容器)的工具类。
常用的方法:
Netty的数据容器ByteBuf。
三个重要的属性:
因为Netty的ByteBuf维护了writerIndex和readerIndex,所以读写之前切换不需要使用到flip(),这也和NIO有区别的。
属性之间的区域描述:
codec(编/解码器)由两个组成部分有两个:
Netty发送和接收一个消息时都会产生一次数据转换:
Netty提供了一系列实用的编码、解码器。它们都实现了ChannelInboundHandler(入栈解码:对应decode()方法) 或者ChannelOutboundHandler(出栈编码:对应encode()方法) 。
自定义编码器-简单例子:
/**
* 自定义编码器 MessageToByteEncoder extends ChannelOutboundHandlerAdapter
* @author 精彩猿笔记
*/
public class MyMessageToByteEncoder extends MessageToByteEncoder<Integer> {
/**自定义编码器:重写父类的编码encode方法*/
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Integer integer, ByteBuf byteBuf) throws Exception {
//以int格式进行编码
byteBuf.writeInt(integer);
}
}
当自定义编码器(重写父类的encode()方法)时,需要编码的消息类型必须与自定义待处理的消息类型一致,否则不会执行自定义handler内的业务逻辑,直接将需要编码的数据写入,没有达到自定义编码的业务。
例如:JDK 1.8【MessageToByteEncoder抽象类的write方法】源码如下:
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
//*************this.acceptOutboundMessage(msg) 判断需要编码的消息类型是否与自定义待处理的消息类型一致
if (this.acceptOutboundMessage(msg)) {
I cast = msg;
buf = this.allocateBuffer(ctx, msg, this.preferDirect);
try {
//*************这调用自定义重写的encode方法,完成编码业务逻辑
this.encode(ctx, cast, buf);
} finally {
ReferenceCountUtil.release(msg);
}
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
//*************不一致,则不会处理自定义编码业务逻辑,直接将需要编码的数据写入,没有达到自定义编码的业务。
ctx.write(msg, promise);
}
} catch (EncoderException var17) {
throw var17;
} catch (Throwable var18) {
throw new EncoderException(var18);
} finally {
if (buf != null) {
buf.release();
}
}
}
.......
}
自定义解码器-简单例子:
/**
* 自定义解码器 ByteToMessageDecoder extends ChannelInboundHandlerAdapter
* @author 精彩猿笔记
*/
public class MyByteToMessageDecoder extends ByteToMessageDecoder {
/**自定义解码器:重写父类的编码decode方法*/
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
//判断ByteBuf可读字节为4个字节(int类型)
//注意:当发送数据大于这个个数,此方法会循环执行,从而就会分段发送多次到下一个handler进行处理(handler业务逻辑也会被触发多次),即如果4发送一次;8发送两次……
if (byteBuf.readableBytes()>=4){
list.add(byteBuf.readInt());
}
}
}
Netty自身提供了一些常用的codec(编/解码器)
Netty自带的编码、解码底层使用的仍然是Java序列化技术,而Java序列化技术本身效率不是很高,所以存在如下问题:
解决方案:可以推荐使用Google的Protobuf。
Protobuf(Google Protocol Buffers) 是Google发布的开源项目。是一种轻便高效的结构化存储格式,可以用于结构化数据串行化,或者说序列化。很适合做数据存储 和RPC[远程过程调用 remote procedure call]。
特点:
支持跨平台、跨语言(即发送端和接收端可以是不同的编程语言,目前支持绝大多数语言,比如C++、C#、Java、python等等)、高性能、高可靠。
编译器:
ProtoBuf是以massage的方式来管理数据的,Protobuf是以 .proto 结尾 的文件,通过protoc.exe编译器 根据.proto自动生成使用的编程语言对应的代码。
简单图解分析,什么是TCP粘包拆包:
Netty使用自定义协议+编码解码 来解决Tcp粘包和拆包问题。关键就是要解决服务器每次读取数据长度的问题 ,这个解决就不会出现多读或者少读的问题,从而避免了TCP粘包拆包问题。
核心代码:
例:EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
两个对象是整个Netty的核心对象。bossGroup 用于接收TCP请求,它会将请求交给workerGroup,workerGroup会获取真正的连接,然后和连接进行通信。EventLoopGroup是一个事件循环线程组,含有多个EventLoop。
new NioEventLoopGroup(1); 有参构造,这个1表示生成一个子线程的bossGroup。
new NioEventLoopGroup(); 无参构造,则workerGroup含有的子线程个数,即CPU核数*2。
JDK 1.8 源码如下:
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
底层都会创建EventExecutor数组:
JDK 1.8 源码如下:
if (executor == null) {
executor = new ThreadPerTaskExecutor(this.newDefaultThreadFactory());
}
this.children = new EventExecutor[nThreads];
JDK 1.8 源码如下:
/**
*@param nThreads 使用线程数 默认为CPU核数*2
*@param executor 执行器,如果传null则采用Netty默认的线程工厂和默认的执行器ThreadPerTaskExecutor
*@param chooserFactory 单例 new DefaultEventExecutorChooserFactory()
*@param args args在创建执行器的时候穿日固定参数
*/
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
this.terminatedChildren = new AtomicInteger();
this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
} else {
//如果传null则采用Netty默认的线程工厂和默认的执行器ThreadPerTaskExecutor
if (executor == null) {
executor = new ThreadPerTaskExecutor(this.newDefaultThreadFactory());
}
//创建指定线程数的执行器数组
this.children = new EventExecutor[nThreads];
int j;
for(int i = 0; i < nThreads; ++i) {
boolean success = false;
boolean var18 = false;
try {
var18 = true;
//创建 new NIOEventLoop
this.children[i] = this.newChild((Executor)executor, args);
success = true;
var18 = false;
} catch (Exception var19) {
throw new IllegalStateException("failed to create a child event loop", var19);
} finally {
if (var18) {
if (!success) {
int j;
for(j = 0; j < i; ++j) {
this.children[j].shutdownGracefully();
}
for(j = 0; j < i; ++j) {
EventExecutor e = this.children[j];
try {
while(!e.isTerminated()) {
e.awaitTermination(2147483647L, TimeUnit.SECONDS);
}
} catch (InterruptedException var20) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
if (!success) {
for(j = 0; j < i; ++j) {
//优雅的关闭
this.children[j].shutdownGracefully();
}
for(j = 0; j < i; ++j) {
EventExecutor e = this.children[j];
try {
while(!e.isTerminated()) {
e.awaitTermination(2147483647L, TimeUnit.SECONDS);
}
} catch (InterruptedException var22) {
Thread.currentThread().interrupt();
break;
}
}
}
}
this.chooser = chooserFactory.newChooser(this.children);
FutureListener<Object> terminationListener = new FutureListener<Object>() {
public void operationComplete(Future<Object> future) throws Exception {
if (MultithreadEventExecutorGroup.this.terminatedChildren.incrementAndGet() == MultithreadEventExecutorGroup.this.children.length) {
MultithreadEventExecutorGroup.this.terminationFuture.setSuccess((Object)null);
}
}
};
EventExecutor[] var24 = this.children;
j = var24.length;
//为每一个单例线程池添加一个关闭监听器
for(int var26 = 0; var26 < j; ++var26) {
EventExecutor e = var24[var26];
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet(this.children.length);
//将所有的单例线程池添加到一个HashSet中
Collections.addAll(childrenSet, this.children);
this.readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
}
// JDK 1.8 NioEventLoop.run()如下:
protected void run() {
while(true) {
while(true) {
try {
switch(this.selectStrategy.calculateStrategy(this.selectNowSupplier, this.hasTasks())) {
case -2:
continue;
case -1:
//******************************step1:select
//调用selector的select方法,默认阻塞一秒钟,如果有定时任务,则在定时任务剩余时间的基础上在家0.5秒进行阻塞,当执行execute方法添加任务的时候,唤醒selector,防止selector阻塞时间过长。
this.select(this.wakenUp.getAndSet(false));
if (this.wakenUp.get()) {
this.selector.wakeup();
}
default:
this.cancelledKeys = 0;
this.needsToSelectAgain = false;
int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
//******************************step2:processSelectedKeys
//调用processSelectedKeys方法对selectKey进行处理
this.processSelectedKeys();
} finally {
//******************************step3:runAllTasks
//按照ioRatio 的比例执行runAllTasks方法,默认IO任务时间和非IO任务时间是相同的,你也可以根据你的应用特点进行调优
//比如:非IO任务较多,那么你就将ioRatio 调小一点,这样非IO任务就能执行的长一点,房子队列积攒过多的任务。
this.runAllTasks();
}
} else {
long ioStartTime = System.nanoTime();
boolean var13 = false;
try {
var13 = true;
this.processSelectedKeys();
var13 = false;
} finally {
if (var13) {
long ioTime = System.nanoTime() - ioStartTime;
this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
}
}
long ioTime = System.nanoTime() - ioStartTime;
//******************************step3:runAllTasks
this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
}
}
} catch (Throwable var21) {
handleLoopException(var21);
}
try {
if (this.isShuttingDown()) {
this.closeAll();
if (this.confirmShutdown()) {
return;
}
}
} catch (Throwable var18) {
handleLoopException(var18);
}
}
}
}
根据如下例子说明:
//创建服务器端启动对,用来配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//设置参数
//设置两个线程组
bootstrap.group(bossGroup,workerGroup);
//使用NioServerSocketChannel作为服务器的通道,应道类将通过这个Class对象反射创建ChannelFactory
bootstrap.channel(NioServerSocketChannel.class);
//设置线程队列得到连接个数,放入private final Map, Object> options = new LinkedHashMap(); 进行管理
bootstrap.option(ChannelOption.SO_BACKLOG,1024);
//设置保持活动的连接状态
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
//给bossGroup添加日志处理器,传入一个handler,这个handler只属于ServerSocketChannel,而不属于SocketChannel
bootstrap.handler(new LoggingHandler(LogLevel.ERROR));
//给workerGroup添加处理器,传入一个handler,这个handler将会在每个客户端连接的时候调用,供socketChannel使用
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
//给pipeLine设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//给workerGroup的EventLoop对应的管道设置处理器
//处理器可以用netty自带的,也可以自己创建处理器,如:NettyServerHandler
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
例: ChannelFuture channelFuture = bootstrap.bind(8888).sync(); //服务器就在通过ServerBootstrap对象的bind方法来绑定一个端口的,生成一个ChannelFuture对象。
通过bind调用到底层【private ChannelFuture doBind(final SocketAddress localAddress) 】方法。
//JDK 1.8 源码如下:
private ChannelFuture doBind(final SocketAddress localAddress) {
//**************initAndRegister
final ChannelFuture regFuture = this.initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
} else if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
//**************doBind0
doBind0(regFuture, channel, localAddress, promise);
return promise;
}
........
doBind方法里有两个重要的方法,分别是:
this.initAndRegister(),如下:
//JDK 1.8 源码如下:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
/**主要完成:
* 1.通过NIO的SelectorProvider的openServerChannel的方法得到JDK的channel,目的是通过Nett包装JDK的channel。
* 2.创建一个ChannelId;创建了一个NIOMessageUnsafe,用于操作下消息;穿件了一个DefaultChannelPipeline管道,是双向链表,用于过滤所有进出的消息
* 穿件一个NioServerSocketChannelConfig对象,用于对外展示一些配置。
*/
channel = this.channelFactory.newChannel();
/**
*主要完成:
* 1.设置NioServerSocketChannel的TCP属性
* 2.由于LinkedHashMap是非线程安全的,所以使用了同步进行处理
* 3.对NioServerSocketChannel的ChannelPipeline添加ChannelInitializer处理器。
*/
this.init(channel);
}
........
}
doBind0(regFuture, channel, localAddress, promise);
总体流程:接收连接 -----> 创建一个新的NioSocketChannel -----> 注册一个Worker EventLoop上 -----> 注册select Read事件。
ChannelPipeline、ChannelHandler、ChannelHandlerContext之间的关系:
ChannelPipeline、ChannelHandler、ChannelHandlerContext创建过程:
每当创建channelSocket的时候都会创建绑定的Pipeline,一一对应关系,创建Pipeline的时候会创建head节点和tail节点,形成最初的链表。head是实现inBound类型和outBound类型的Handler;tail是实现inBound类型的Handler。在调用pipeline的addLast方法的时候,会根据给定的Handler创建一个Context,然后将这个Context插入到链表的尾端(tail节点前)。
Context包装handler,多个Context在Pipeline中形成了双向链表。入栈叫inBound,有head节点开始;出栈叫outBound,由tail节点来时。
而节点中间的传递通过AbstractChannelHandlerContext类内部的fire系列方法,找到当前节点下一个节点不断的循环传递。是一个过滤器模式完成对Handler的调度。
调度过程如下图:
说明:
Netty作为一个网络应用框架,提供了很多功能,如非常重要的心跳机制heartBeat。通过心跳检查与对方的连接是否有效。这也是RPC远程调用框架必不可少的功能。
Nett提供了IdleStateHandler、ReadTimeoutHandler、writeTimeoutHandler三个Handler检测连接的有效性。
IdleStateHandler:当连接的空闲时间(读或写)太长时,将会触犯一个IdleStateEvent事件。
IdleStateHandler四个常用属性:
IdleStateHandler可以可以实现心跳功能,当服务器和客户端在规定时间内没有发生读写事件时,这会触发用户Handler的userEventTriggered方法。用户可以在这个方法中尝试向对方发送信息,如果发送失败,则关闭连接。
IdleStateHandler的实现是基于EventLoop的定时任务,每次读写都会记录一个值,在定会任务运行的时候,会通过计算当前时间和设置时间和上一次时间放生的时间结果,来判断是否空闲。
内部由3个定时任务,分别是:读事件、写事件、读或写事件。
ReadTimeoutHandler:继承IdleStateHandler,当触发读空闲事件的时候,就会触发crx.fireExceptionCaught方法,并传入一个ReadTimeoutHandler,然后关闭socket。
WriteTimeoutHandler:继承ChannelOutboundHandlerAdapter,当调用write方法的时候,会创建一个定时任务,任务内容是根据传入的promise的完成情况来判断是否超出写的时间。当定时任务根据指定时间开始运行,发现promise的isDone(isDone-判断当前操作是否完成)方法返回false,表明还没写完,说明超时了,则抛出异常。
RPC[Remote Procedure Call 远程过程调用] :是一个计算机通信协议,该协议允许运行在一台计算器的程序调用另一台计算机的程序。两个或多个应用程序都分布在不同的服务器上。
使用场景:
阿里的Dubbo、Google的gRPC、Go语言的rpcx、Apache的thrift、Spring Cloud 等等。
后续更多关于Netty的相关内容会不断更新中……
······
帮助他人,快乐自己,最后,感谢您的阅读!
所以如有纰漏或者建议,还请读者朋友们在评论区不吝指出!
个人网站…知识是一种宝贵的资源和财富,益发掘,更益分享…