要了解Netty的线程模型,我们首先要对Reactor线程模型有所了解,因为netty模型正是基于它的一个实现,所谓Reactor模型是基于事件驱动开发的,它也叫IO多路复用统一监听事件。它核心理念就是,当监听到客户端的连接时就将连接交给合适的业务线程去处理,监听的线程就叫做reactor,根据reactor线程和业务线程的数量,可以划分出三种reactor模型。
EventLoopGroup worker=new NioEventLoopGroup(1);
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(worker);
此处的group方法实际上将worker同时设置为了Boss线程和工作线程,worker的线程数量又被限制为1,所以符合了单线程Reactor模型
EventLoopGroup worker=new NioEventLoopGroup();
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(worker);
这里创建的NioEventLoopGroup线程池没有限制数量,它的默认线程数是处理器核数*2,因此也符合多线程Reactor模型
EventLoopGroup worker=new NioEventLoopGroup();
EventLoopGroup boss=new NioEventLoopGroup();
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(boss,worker);
理论上来说这应该就是主从Reactor模型,但是网上的资料显示netty似乎做出了一些修改
以下引用自https://segmentfault.com/a/1190000007403873
Netty 的服务器端的 acceptor 阶段, 没有使用到多线程, 因此上面的 主从多线程模型 在 Netty 的服务器端是不存在的.
服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 所以对只有一个服务的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费.
根据https://www.cnblogs.com/lvyahui/p/9030232.html博客描述,需要绑定多个套接字才会创建多个监听线程,测试同时监听两个端口
serverBootstrap.bind(8080).sync();
serverBootstrap.bind(8022).sync();
测试结果:在idea打印线程快照信息,发现当只监听一个端口时,即使指定了创建4个线程,但实际还是只有一个监听线程,只有监听了多个端口时才会创建多个监听线程。
服务端
package FrameWork.com.nio.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledDirectByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
/**
* Created by forget on 2019/12/2.
*/
public class NettyServer {
private EventLoopGroup worker;
private EventLoopGroup boss;
private ServerBootstrap serverBootstrap;
private ChannelFuture future;
public NettyServer() {
this.worker=new NioEventLoopGroup();
this.boss=new NioEventLoopGroup();
this.serverBootstrap=new ServerBootstrap();
}
public void start(int port){
try
{
this.serverBootstrap.group(this.boss,this.worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
//编码器,将字符串消息转为字节发送给客户端
pipeline.addLast(new StringEncoder());
//解码器,将收到的字节消息转为字符串交给下一个处理器
pipeline.addLast(new StringDecoder());
//业务处理器
pipeline.addLast(new HelloServeHandle());
}
});
this.future = this.serverBootstrap.bind(port).sync();
System.out.println("netty服务器启动");
this.future.channel().closeFuture().sync();
}
catch (Exception e)
{
e.printStackTrace();
}
finally {
this.boss.shutdownGracefully();
this.worker.shutdownGracefully();
}
}
}
public class HelloServeHandle extends SimpleChannelInboundHandler<String>{
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String o) throws Exception {
System.out.println("收到客户端消息:"+o);
channelHandlerContext.writeAndFlush(o);
}
}
客户端
package FrameWork.com.nio.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;
public class Client {
private EventLoopGroup eventLoopGroup;
private Bootstrap Bootstrap;
private ChannelFuture channelFuture;
public Client() {
this.eventLoopGroup=new NioEventLoopGroup();
this.Bootstrap=new Bootstrap();
}
public void start(){
this.Bootstrap.group(this.eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline=channel.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new HelloClientHandle());
}
});
try {
this.channelFuture=this.Bootstrap.connect("localhost",8080).sync();
this.channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class HelloClientHandle extends SimpleChannelInboundHandler<String>{
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String o) throws Exception {
System.out.println("收到消息:"+o);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(new String("hello world"));
}
}
以上的代码运行之后,当客户端首次连接到服务端时会触发客户端的channelActive方法,向服务器发送"hello world",此时客户端在写入时会先在自身的pipeline寻找第一个outboundhandle,在这里就是StringEncoder编码器,将字符串编码为字节消息,然后继续寻找下一个处理器,没有则发送到服务器。服务器接收到消息后,也是先寻找inboundhandle,这里则是StringDecoder解码器,将字节消息解码为字符串,交给下一个处理器,即HelloServeHandle,触发channelRead0方法,将消息回写给客户端,下面的流程也和客户端相同,进行字符串编码操作。