介绍
- 在 Netty 中做耗时的,不可预料的操作,比如数据库,网络请求,会严重影响 Netty 对 Socket 的处理速度。
- 而解决方法就是将耗时任务添加到异步线程池中。但就添加线程池这步操作来讲,可以有2种方式,而且这2种方式实现的区别也蛮大的。
- 处理耗时业务的第一种方式—
handler 中加入线程池
- 处理耗时业务的第二种方式—
Context 中添加线程池
- 当我们使用addLast方法添加线程池后,handler将优先使用这个线程池,如果不添加,将使用IO线程
- 处理耗时业务的第一种方式—
流程
方式一
server
public class Server {
public static void main(String[] args) throws IOException {
//创建BossGroup和WorkerGroup
//bossGroup和workerGroup含有的子线程的个数,默认为cpu核数*2
EventLoopGroup bossGroup=new NioEventLoopGroup(5);
EventLoopGroup workerGroup=new NioEventLoopGroup();
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap=new ServerBootstrap();
try {
//使用链式编程来进行设置
bootstrap.group(bossGroup,workerGroup) //设置两个线程
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel,作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128) //设置线程队列里连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持连接状态
.childHandler(new ChannelInitializer<SocketChannel>() { //handler对应的是boosGruop,childHandler对应的是workerGroup
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//可以使用一个集合管理SocketChannel,在推送消息时,可以将业务加入到各个channel对应的
// NioEventLoop的taskQueue或者scheduleTaskQueue中
System.out.println("客户端socketChannel hashcode="+socketChannel.hashCode());
socketChannel.pipeline().addLast(new ServerHandler());
}
}); //给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println("服务器 is ready ...");
//绑定一个端口并且同步,生成一个ChannelFuture对象
ChannelFuture cf = bootstrap.bind(6668).sync();
//给 cf 注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()){
System.out.println("监听端口成功");
}else{
System.out.println("监听端口失败");
}
}
});
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
serverHandler
public class ServerHandler extends ChannelInboundHandlerAdapter {
static final EventExecutorGroup group=new DefaultEventExecutorGroup(16);
private final DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* @Description 读取数据事件(这里我们可以读取客户端发送的消息)
* @date 2020/7/23 10:33
* @param ctx 上下文对象,含有:管道(pipeline),通道(channel),地址
* @param msg 客户端发送的数据
* @return void
*/
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf=(ByteBuf)msg;
System.out.println("接收到的消息:"+buf.toString(CharsetUtil.UTF_8)+" "+ LocalDateTime.now().format(formatter) +" 当前线程----"+Thread.currentThread().getName());
//任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
System.out.println("服务端发送消息1 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端1\t",CharsetUtil.UTF_8));
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
System.out.println("服务端发送消息2 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端2\t",CharsetUtil.UTF_8));
}
});
//定时任务
ctx.channel().eventLoop().schedule(()->{
System.out.println("服务端发送消息3 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端4\t",CharsetUtil.UTF_8));
},10,TimeUnit.SECONDS);
//将任务提交到group线程池
group.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println("服务端发送消息4 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端4\t",CharsetUtil.UTF_8));
return null;
}
});
System.out.println("发送消息...");
}
/**
* @Description 数据读取完毕
* @date 2020/7/23 10:46
* @param ctx
* @return void
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓冲并刷新
//一般来讲,我们队发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer(new Date().toString()+":"+"hello,客户端",CharsetUtil.UTF_8));
}
/**
* @Description 处理异常,一般是需要管理通道
* @date 2020/7/23 11:13
* @param ctx
* @param cause
* @return void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
核心代码
//将任务提交到group线程池
group.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println("服务端发送消息4 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端4\t",CharsetUtil.UTF_8));
return null;
}
});
运行
方式二
server
public class Server {
static final EventExecutorGroup group=new DefaultEventExecutorGroup(2);
public static void main(String[] args) throws IOException {
//创建BossGroup和WorkerGroup
//bossGroup和workerGroup含有的子线程的个数,默认为cpu核数*2
EventLoopGroup bossGroup=new NioEventLoopGroup(5);
EventLoopGroup workerGroup=new NioEventLoopGroup();
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap=new ServerBootstrap();
try {
//使用链式编程来进行设置
bootstrap.group(bossGroup,workerGroup) //设置两个线程
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel,作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128) //设置线程队列里连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持连接状态
.childHandler(new ChannelInitializer<SocketChannel>() { //handler对应的是boosGruop,childHandler对应的是workerGroup
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//可以使用一个集合管理SocketChannel,在推送消息时,可以将业务加入到各个channel对应的
// NioEventLoop的taskQueue或者scheduleTaskQueue中
System.out.println("客户端socketChannel hashcode="+socketChannel.hashCode());
//如果我们在addLast添加handler,前面有指定EventExecutorGroup,name该handler会优先加入到该线程池中
socketChannel.pipeline().addLast(group,new ServerHandler());
}
}); //给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println("服务器 is ready ...");
//绑定一个端口并且同步,生成一个ChannelFuture对象
ChannelFuture cf = bootstrap.bind(6668).sync();
//给 cf 注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()){
System.out.println("监听端口成功");
}else{
System.out.println("监听端口失败");
}
}
});
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
serverHandler
public class ServerHandler extends ChannelInboundHandlerAdapter {
private final DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* @Description 读取数据事件(这里我们可以读取客户端发送的消息)
* @date 2020/7/23 10:33
* @param ctx 上下文对象,含有:管道(pipeline),通道(channel),地址
* @param msg 客户端发送的数据
* @return void
*/
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf=(ByteBuf)msg;
System.out.println("接收到的消息:"+buf.toString(CharsetUtil.UTF_8)+" "+ LocalDateTime.now().format(formatter) +" 当前线程----"+Thread.currentThread().getName());
//任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
System.out.println("服务端发送消息1 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端1\t",CharsetUtil.UTF_8));
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
System.out.println("服务端发送消息2 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端2\t",CharsetUtil.UTF_8));
}
});
//定时任务
ctx.channel().eventLoop().schedule(()->{
System.out.println("服务端发送消息3 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端4\t",CharsetUtil.UTF_8));
},10,TimeUnit.SECONDS);
System.out.println("服务端发送消息4 "+LocalDateTime.now().format(formatter)+" 当前线程----"+Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端4\t",CharsetUtil.UTF_8));
System.out.println("发送消息...");
}
/**
* @Description 数据读取完毕
* @date 2020/7/23 10:46
* @param ctx
* @return void
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓冲并刷新
//一般来讲,我们队发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer(new Date().toString()+":"+"hello,客户端",CharsetUtil.UTF_8));
}
/**
* @Description 处理异常,一般是需要管理通道
* @date 2020/7/23 11:13
* @param ctx
* @param cause
* @return void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
核心代码
//如果我们在addLast添加handler,前面有指定EventExecutorGroup,name该handler会优先加入到该线程池中
socketChannel.pipeline().addLast(group,new ServerHandler());