上一节我们了解了基础的IO线程模型,而Netty是对主从Reactor多线程模型的改进。
不了解的可以看看这篇博客:Netty入门之IO线程模型(一)
这一节,我们就来了解下Netty模型,并通过一段代码实现来了解Netty的基本使用。
说明:
Netty抽象出两组线程池BossGroup和WorkerGroup,其中BossGroup用来负责客户端的连接,而WorkerGroup用来负责网路的读写
BossGroup和WorkerGroup只是一种叫法,对应的Java类都是NioEventLoopGroup
NioEventLoopGroup相当于一个事件循环组,其中包含多个事件,每一个事件循环叫NioEventLoop
NioEventLoop表示一个不断循环执行处理任务的线程,每一个NioEventLoop都有一个selector,用于监听绑定在其上的socket的网路通讯
NioEventLoopGroup可以有多个线程(可以看作是一个线程池),既可以含有多个NioEventLoop
BossGroup的NioEventLoopGroup有三个步骤:
WorkerGroup的NioEventLoopGroup也有三个步骤:
WorkerGroup的NioEventLoopGroup 处理业务时,会使用pipeline(管道), pipeline 中包含了 channel , 即通过pipeline可以获取到对应通道, 管道中维护了很多的 处理器
功能:
通过该实例,我们能够初步了解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
功能:
服务端:
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);
}
}
}
通过本片博客我们能够大概梳理几个点:
本篇博客仅仅是对Netty的一个简单应用,通过两个案例来了解如何使用Netty以及相关的重要组件。
下一篇我们来详细了解下Netty各组件的作用,方便我们对Netty的理解。