reactor和thread线程_Netty学习笔记(三)Reactor线程模型

本文详细介绍了Netty的主从线程模型,解释了单线程模型和多线程模型的优缺点,并展示了Netty如何通过主从线程模型提升效率。在Netty的示例代码中,BossGroup负责接收连接,WorkerGroup处理IO,通过ChannelPipeline配置自定义的ChannelHandler进行业务处理。文章还展示了如何自定义处理类以实现特定的网络通信功能。
摘要由CSDN通过智能技术生成

单线程模型

所有操作都在同一个NIO线程处理,在这个单线程中要负责接收请求,处理IO,编解码所有操作,相当于一个饭馆只有一个人,同时负责前台和后台服务,效率低。

单线程模型

多线程模型

多线程的优点在于有单独的一个线程去处理请求,另外有一个线程池创建多个NIO线程去处理IO。相当于一个饭馆有一个前台负责接待,有很多服务员去做后面的工作,这样效率就比单线程模型提高很多。

多线程模型

主从线程模型

多线程模型的缺点在于并发量很高的情况下,只有一个Reactor单线程去处理是来不及的,就像饭馆只有一个前台接待很多客人也是不够的。为此需要使用主从线程模型。

主从线程模型:一组线程池接收请求,一组线程池处理IO。

主从线程模型

Netty采用了第三种模型:主从线程模型。

在这个模型下,Netty提供了BootStrap类方便我们快速开发,下面是一个示例代码:

public class Server {

public static void main(String[] args) throws Exception {

EventLoopGroup bossGroup = new NioEventLoopGroup(1);

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

ServerBootstrap b = new ServerBootstrap();

b.group(bossGroup, workerGroup)

.channel(NioServerSocketChannel.class)

.childOption(ChannelOption.TCP_NODELAY, true)

.childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")

.handler(new ServerHandler())

.childHandler(new ChannelInitializer() {

@Override

public void initChannel(SocketChannel ch) {

}

});

ChannelFuture f = b.bind(8888).sync();

f.channel().closeFuture().sync();

} finally {

bossGroup.shutdownGracefully();

workerGroup.shutdownGracefully();

}

}

}

下面我们对Netty示例代码进行分析:

首先定义了两个EventLoopGroup,其中bossGroup对应的就是主线程池,只接收客户端的连接(注册,初始化逻辑),具体的工作由workerGroup这个从线程池来完成。可以理解为老板负责招揽接待,员工负责任务完成。线程池和线程组是一个概念,所以名称里有group 。之后就采用ServerBootstrap启动类,传入这两个主从线程组。

客户端和服务器建立连接后,NIO 会在两者之间建立Channel,所以启动类调用channel方法就是为了指定建立什么类型的通道。这里指定的是NioServerSocketChannel这个通道类。

启动类还调用了handler()和childHandler()方法,这两个方法中提及的handler是一个处理类的概念,他负责处理连接后的一个个通道的相应处理。handler()指定的处理类是主线程池中对通道的处理类,childHandler()方法指定的是从线程池中对通道的处理类。

执行ServerBootstrap的bind方法进行绑定端口的同时也执行了sync()方法进行同步阻塞调用。

关闭通道采用Channel的closeFuture()方法关闭。

最终优雅地关闭两个线程组,执行shutdownGracefully()方法完成关闭线程组。

设置ChannelHandler

现在单独分析下处理类handler,每一个Channel由多个handler共同组成管道pipeline。

ChannelHander

管道中的handler可以用netty官方提供的处理类,也可以自行定义处理类。在上面的示例代码中,childHandler方法中传入ChannelInitializer对象,它的initChannel()方法中可以自行扩展,下面是一个具体的例子:

public class CustomerHandler extends SimpleChannelInboundHandler {

@Override

protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

Channel channel = ctx.channel(); // 获取通道

System.out.println(channel.remoteAddress()); // 显示客户端的远程地址

ByteBuf content = Unpooled.copiedBuffer("hello,netty", CharsetUtil.UTF_8);// 定义要返回的数据

// 定义响应

FullHttpResponse response =

new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,content);

// 定义请求头

response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");

response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());

ctx.writeAndFlush(response);

}

}

这是个自定义的处理类,它继承SimpleChannelInboundHandler这个初始化类,在channelRead0方法内部实现读写缓冲区的操作:首先从上下文中获取连接后的通道,然后创建ByteBuf对象,里面保存要显示的字符串,接着创建一个Http response 对象,设置返回的报文头和内容,最后使用上下文放松请求,注意要使用writeAndFlush而不是write,这是因为没有执行flush操作,数据仍在缓冲区中。

写好了上面这个自定义的处理类后将它配置到启动类中:

ServerBootstrap b = new ServerBootstrap();

b.group(bossGroup, workerGroup)

.channel(NioServerSocketChannel.class)

.childOption(ChannelOption.TCP_NODELAY, true)

.childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")

.handler(new ServerHandler())

.childHandler(new ChannelInitializer() {

@Override

public void initChannel(SocketChannel channel) {

ChannelPipeline pipeline = channel.pipeline();

pipeline.addLast("HttpServerCodec",new HttpServerCodec());

pipeline.addLast("customerHandler",new CustomerHandler());

}

});

可以看出在childHandler中,pipeline添加了两个处理类,一个是HttpServerCodec,是Netty自带的对请求和响应进行编解码的处理类;另一个就是我们创建的自定义处理类。这样启动这个应用后,在浏览器访问http://localhost:8888/地址后,页面上就会显示hello,netty的字样,说明我们添加的处理类已经生效了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值