1 Netty模型(简单版)
- 1) BossGroup线程维护selector,只关注Accept
- 2) 当接受到Accept事件,获取到对应的socketChannel,封装成NIOScoketChannel并注册到Worker线程(事件循环),并进行维护
- 3) 当Worker线程监听到selector中通道发生自己感兴趣的事件后,就进行处理
2 Netty模型(进阶版)
3 Netty模型(详细版)
1) Netty抽象出两组线程池BossGroup专门负责客户端的连接,WorkerGroup专门负责网络的读写
2) BossGroup和WorkerGroup类型都是NioEventLoopGroup
3) NioEventLoopGroup相当于一个事件循环组,这个组中含有多个事件循环,每一个事件循环是NioEventLoop
4) NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket的网络通讯
5) NioEventLoopGroup可以有多个线程,即可以含有多个NioEventLoop
6) 每个Boss NioEventLoop循环执行的步骤有三步
- 1) 轮询accept事件
- 2) 处理caaept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个worker NioEventLoop上的selector
- 3) 处理任务队列的任务,即runAllTasks
7) 每个Worker NioEventLoop循环执行的步骤
- 1) 轮询read,write事件
- 2) 在对应的NioSocketChannel处理i/o事件,
- 3)处理任务队列的任务,即runAllTasks
8)每个WorkerNioEventLoop处理业务时,会使用pipeline(管道),pipeline中包含包含了channel,即通过pipeline可以获得对应的通道。管道中维护了很多处理器 ------责任链设计模式
2 Netty快速入门实例
package com.dd.netty.simple;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建bossGroup 和 WorkerGroup
//bossGroup只处理连接请求,真正的客户端业务处理,会交给workerGroup完成
//两个都是无限循环
//bossgroup和workergroup含有的子线程(NIOeventloop)的个数 默认为cpu核数乘以2
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup woekerGroup = new NioEventLoopGroup();
try {
//创建服务器端启动的对象
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, woekerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//使用NIOsocketChannel实现服务器通道
.option(ChannelOption.SO_BACKLOG, 128)//设置线程队列得到连接的个数
.childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//可以使用一个集合管理SocketChannel,再推送消息时,可以将业务加入到各个channle对应的NIOEventLoop的taskQueue或者scheduleTaskQueue
System.out.println("客户对应的socketChannel hashcode="+socketChannel.hashCode());
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});//给WorkerGoop的 EventLoop 对应的管道设置处理器
//绑定一个端口并且同步处理,生成一个ChannelFuture,并启动服务器
ChannelFuture cf = bootstrap.bind(6668).sync();
//给cf注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (cf.isSuccess()){
System.out.println("监听端口6668,成功");
}else{
System.out.println("监听失败");
}
}
});
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
woekerGroup.shutdownGracefully();
}
}
}
package com.dd.netty.simple;
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;
import java.util.concurrent.TimeUnit;
/*
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取实际数据
/*
1.ChannelHandlerContext ctx:上下文对象,含有管道pipeline,通道channel,地址
2.Object msg:就是客户端发送的数据 默认Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/* //将msg转换程一个buteBuffer
//该buf为Netty提供,性能更高
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送消息"+buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址"+ctx.channel().remoteAddress());*/
//加入这里我们有一个非常耗时的业务->异步执行->提交该Channel对应的NioEventLoop的taskQueue中。
/*Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端22",CharsetUtil.UTF_8));*/
System.out.println("go on...");
//解决方案一:用户程序自定义普通任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端22", CharsetUtil.UTF_8));
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端33", CharsetUtil.UTF_8));
}
});
//上面客户端需要30秒才能发送到客户端,因为两个方法还是在一个线程中执行的
//解决方案二:用户程序自定义定时任务,该任务提交到ScheduleTaskQueue中
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端444", CharsetUtil.UTF_8));
}
},5, TimeUnit.SECONDS);
//解决方案三:非当前Reactor线程调用Channel的各种方法
//列如 推送系统 的业务线程,根据用户表示,找到对应的Channel引用,然后调用Write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中被异步消费
}
//数据读取完毕,执行
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入缓冲,再刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端", CharsetUtil.UTF_8));
}
//处理异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.dd.netty.simple;
import com.sun.org.apache.bcel.internal.generic.NEW;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//客户端需要一个循环事件组
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//创建客户端
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(eventExecutors)//设置线程组
.channel(NioSocketChannel.class)//设置客户端通道的实现类
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());//加入自己的处理器
}
});
//启动客户端去连接服务器端
//关于channelFuture要分析,涉及到netty的异步模型
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} finally {
eventExecutors.shutdownGracefully();
}
}
}
package com.dd.netty.simple;
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 {
//当通道就绪就会触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,servre", CharsetUtil.UTF_8));
}
//当通道有读取事件时 会触发
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
System.out.println("服务器回复的消息"+buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器地址"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 案例说明:
1) Netty抽象出两组线程池,BossGroup专门负责接受客户端连接,WorkerGroup专门负责网络的读写
2) NioEventLoop表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上socket网络通道
3) NioEventLoop内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由IO线程NioEventLoop负责
NioEventLoopGroup下包含多个NioEventLoop
每个NioEventLoop中半酣有一个Selector,一个taskQueue
每个NioEventLoop的selector上可以注册监听多个NioChannel
每个NioChannel指挥绑定在唯一的NioEventLoop上
每个NioChannel都绑定有一个自己的ChannelPipeline
3 Netty任务队列
- 推送系统
的业务线程,根据用户表示,找到对应的Channel引用,然后调用Write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中被异步消费
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/* //将msg转换程一个buteBuffer
//该buf为Netty提供,性能更高
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送消息"+buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址"+ctx.channel().remoteAddress());*/
//加入这里我们有一个非常耗时的业务->异步执行->提交该Channel对应的NioEventLoop的taskQueue中。
/*Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端22",CharsetUtil.UTF_8));*/
System.out.println("go on...");
//解决方案一:用户程序自定义普通任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端22", CharsetUtil.UTF_8));
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端33", CharsetUtil.UTF_8));
}
});
//上面客户端需要30秒才能发送到客户端,因为两个方法还是在一个线程中执行的
//解决方案二:用户程序自定义定时任务,该任务提交到ScheduleTaskQueue中
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端444", CharsetUtil.UTF_8));
}
},5, TimeUnit.SECONDS);
//解决方案三:非当前Reactor线程调用Channel的各种方法
//列如 推送系统 的业务线程,根据用户表示,找到对应的Channel引用,然后调用Write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中被异步消费
}