Netty入门之Netty模型(二)
1. 前言
上一节我们了解了基础的IO线程模型,而Netty是对主从Reactor多线程模型的改进。
不了解的可以看看这篇博客:Netty入门之IO线程模型(一)
这一节,我们就来了解下Netty模型,并通过一段代码实现来了解Netty的基本使用。
2. Netty模型 – 工作原理图
说明:
-
Netty抽象出两组线程池BossGroup和WorkerGroup,其中BossGroup用来负责客户端的连接,而WorkerGroup用来负责网路的读写
-
BossGroup和WorkerGroup只是一种叫法,对应的Java类都是NioEventLoopGroup
-
NioEventLoopGroup相当于一个事件循环组,其中包含多个事件,每一个事件循环叫NioEventLoop
-
NioEventLoop表示一个不断循环执行处理任务的线程,每一个NioEventLoop都有一个selector,用于监听绑定在其上的socket的网路通讯
-
NioEventLoopGroup可以有多个线程(可以看作是一个线程池),既可以含有多个NioEventLoop
-
BossGroup的NioEventLoopGroup有三个步骤:
- 轮询accept事件。
- 处理accept事件,与client建立连接,生成NioScocketChannel ,并将其注册到WorkerGroup的NioEventLoopGroup中的selector上。
- 处理任务队列的任务,即runAllTasks。
-
WorkerGroup的NioEventLoopGroup也有三个步骤:
- 轮询read,write事件
- 处理 IO 事件, 即 read , write 事件,在对应 NioScocketChannel 处理
- 处理任务队列的任务 , 即 runAllTasks
-
WorkerGroup的NioEventLoopGroup 处理业务时,会使用pipeline(管道), pipeline 中包含了 channel , 即通过pipeline可以获取到对应通道, 管道中维护了很多的 处理器
3. Netty入门实例 – TCP服务
功能:
- Netty服务器在3000端口监听,客户端能发送消息给服务器,内容为:“Hello,NettyServer!”
- 服务端可以回复消息给客户端,内容为:“Hello,NettyClient!”
通过该实例,我们能够初步了解Netty线程模型,便于理解Netty模型理论。
服务端:
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;
public class NettyServer {
public static void main(String[] args) {
// 创建 BossGroup 和 WorkerGroup
// 说明
// 1. 创建两个线程组 bossGroup 和 workerGroup
// 2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup 完成
// 3. 两个都是无限循环
// 4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
// 默认实际 cpu 核数 * 2
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
// 使用链式编程来进行设置
bootstrap.group(bossGroup, workerGroup) // 设置两个线程组
.channel(NioServerSocketChannel.class) // 使用 NioSocketChannel 作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
.childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {// 创建一个通道测试对象(匿名对象)
// 给 pipeline 设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
}); // 给我们的 workerGroup 的 EventLoop 对应的管道设置处理器
System.out.println(".....服务器 is ready...");
// 绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
// 启动服务器(并绑定端口)
ChannelFuture cf = bootstrap.bind(3000).sync();
// 对关闭通道进行监听
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// 读取数据实际(这里我们可以读取客户端发送的消息)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程 " + Thread.currentThread().getName());
System.out.println("server ctx =" + ctx);
System.out.println("看看 channel 和 pipeline 的关系");
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline(); // 本质是一个双向链接, 出站入站
// 将 msg 转成一个 ByteBuf
// ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:" + channel.remoteAddress());
}
// 数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// writeAndFlush 是 write + flush
// 将数据写入到缓存,并刷新
// 一般讲,我们对这个发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello,NettyClient!", CharsetUtil.UTF_8));
}
// 处理异常, 一般是需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户端:
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;
public class NettyClient {
public static void main(String[] args) {
// 客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建客户端启动对象
// 注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
Bootstrap bootstrap = new Bootstrap();
// 设置相关参数
bootstrap.group(group) // 设置线程组
.channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler()); // 加入自己的处理器
}
});
System.out.println("客户端 ok..");
// 启动客户端去连接服务器端
// 关于 ChannelFuture 要分析,涉及到 netty 的异步模型
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 3000).sync();
// 给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
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 {
System.out.println("client " + ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello,NettyServer!", 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();
}
}
客户端输出:
客户端 ok..
client ChannelHandlerContext(NettyClientHandler#0, [id: 0x456c6a18, L:/127.0.0.1:61933 - R:/127.0.0.1:3000])
服务器回复的消息:Hello,NettyClient!
服务器的地址: /127.0.0.1:3000
服务端输出:
.....服务器 is ready...
服务器读取线程 nioEventLoopGroup-3-1
server ctx =ChannelHandlerContext(NettyServerHandler#0, [id: 0x17e25b94, L:/127.0.0.1:3000 - R:/127.0.0.1:61933])
看看 channel 和 pipeline 的关系
客户端发送消息是:Hello,NettyServer!
客户端地址:/127.0.0.1:61933
4. Netty入门实例 – HTTP服务
功能:
- Netty 服务器在 6668 端口监听,浏览器发出请求 "http://localhost:6668/ "
- 服务器可以回复消息给客户端 "Hello! 我是服务器 5 " , 并对特定请求资源进行过滤.
服务端:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class TestServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new TestServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//向管道加入处理器
//得到管道
ChannelPipeline pipeline = socketChannel.pipeline();
//加入一个 netty 提供的 httpServerCodec codec =>[coder - decoder]
//HttpServerCodec 说明
//1. HttpServerCodec 是 netty 提供的处理 http 的 编-解码器
pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
//2. 增加一个自定义的 handler
pipeline.addLast("MyTestHttpServerHandler", new TestHttpServerHandler());
}
}
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//判断 msg 是不是 httprequest 请求
if(msg instanceof HttpRequest) {
System.out.println("pipeline hashcode" + ctx.pipeline().hashCode() + " TestHttpServerHandler hash=" +
this.hashCode());
System.out.println("msg 类型=" + msg.getClass());
System.out.println("客户端地址" + ctx.channel().remoteAddress());
//获取到
HttpRequest httpRequest = (HttpRequest) msg;
//获取 uri, 过滤指定的资源
URI uri = new URI(httpRequest.uri());
if ("/favicon.ico".equals(uri.getPath())) {
System.out.println("请求了 favicon.ico, 不做响应");
return;
}
//回复信息给浏览器 [http 协议]
ByteBuf content = Unpooled.copiedBuffer("hello, 我是服务器", CharsetUtil.UTF_8);
//构造一个 http 的相应,即 httpresponse
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());
//将构建好 response 返回
ctx.writeAndFlush(response);
}
}
}
5. Netty模型总结
通过本片博客我们能够大概梳理几个点:
- NioEventLoopGroup 下包含多个 NioEventLoop
- 每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
- 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
- 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
- 每个 NioChannel 都绑定有一个自己的 ChannelPipeline
本篇博客仅仅是对Netty的一个简单应用,通过两个案例来了解如何使用Netty以及相关的重要组件。
下一篇我们来详细了解下Netty各组件的作用,方便我们对Netty的理解。