初学Netty
前言
前面提到了NIO操作,但是其代码比较复杂,直接使用NIO去开发是比较困难的而且问题较多,比如Epoll Bug,于是就基于NIO产生了一个比较健壮的开源框架-Netty。
一、什么是Netty
Netty 是由 JBOSS 提供的一个基于NIO的网络编程框架,Netty是由JAVA语言实现的,提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序。很多框架像Dobbo、Elasticsearch等框架都是基于Netty实现的。
看Netty官网给出的文档介绍以及架构图Netty使用零拷贝技术最小化不必要的内存复制,同时也减少资源的消耗;具有更高的吞吐量以及低延迟,让其性能是非常高的。
二、Netty线程模型
1.Reactor 模型
Reactor 模式指的是通过一个或多个输入同时传递给服务处理器的模式,服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程,因此 Reactor 模式也叫 Dispatcher模式.。Reactor 模式使用IO 复用监听事件,收到事件后,分发给某个线程(进程),Reactor 模式是网络服务器高并发处理关键。
- Selector可以实现应用程序通过一个阻塞对象对多路连接请求的监听。
- Reactor 对象通过 Selector监控客户端请求事件,收到事件后通过 Dispatch 进行分发。
- 建立连接请求事件后,由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理业务。
- Handler对象Read读取数据后将业务分配到Worker线程池中。
- Worker线程池处理完业务后将结果返回给Hander。
- Hander收到结果后在Send返回给客户端。
当然后面线程池不属于Reactor 模型,现在基本都是多核CPU,为了充分利用CPU提高性能一般在后面加线程池去分别处理业务。
2.Netty线程模型
Netty就是基于Reactor模型设计的。
Netty中石油两组线程池的,BossGroup和WorkerGroup,他们都是多线程的,都有一个Selector用来监听事件。
- BossGroup: 负责专门和客户端建立链接,轮询注册的ServerSocketChannel的请求,并建立连接生成NIOSocketChannel注册到WorkerGroup中。
- WorkerGroup: 负责专门处理连接的业务,轮询注册的NIOSocketChannel,处理对应的业务。
三、示例代码
服务端的代码:
/**
* 客户端创建两个线程池组分别为 boss线程组和工作线程组
*/
// 用于接受客户端连接的请求 (并没有处理请求)
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// 用于处理客户端连接的读写操作
NioEventLoopGroup workGroup = new NioEventLoopGroup();
// 用于创建我们的ServerBootstrap
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 设置我们分割最大长度为1024
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 获取数据的结果为string类型,编码和解码
socketChannel.pipeline().addLast(new StringEncoder());
socketChannel.pipeline().addLast(new ServerHandler());
}
});
// 绑定我们的端口号码
try {
// 绑定端口号,同步等待成功
ChannelFuture future = serverBootstrap.bind(8080).sync();
System.out.println("服务器启动成功:" + 8080);
// 等待服务器监听端口
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭连接
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
Handler
//这里可以加泛型的接受参数就直接使用String
public class ServerHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
// 接受我们的数据
ByteBuf byteBuf = (ByteBuf) o;
String request = byteBuf.toString(CharsetUtil.UTF_8);
System.out.println("request:" + request);
// 响应内容:
ctx.writeAndFlush(Unpooled.copiedBuffer("平均突破3万月薪\n", CharsetUtil.UTF_8));
}
}
客户端代码:
//创建nioEventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 设置我们分割最大长度为1024
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 获取数据的结果为string类型
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ClientHandler());
}
});
try {
// 发起同步连接
ChannelFuture sync = bootstrap.connect().sync();
sync.channel().closeFuture().sync();
} catch (Exception e) {
...
} finally {
group.shutdownGracefully();
}
}
客户端只创建一个就可以了,只有BossGroup,用不到WordGroup。
客户端handler
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 活跃通道可以发送消息
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 发送数据
for (int i = 0; i < 10; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer("mayikt\n", CharsetUtil.UTF_8));
}
}
/**
* 读取消息
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("resp:" + msg.toString(CharsetUtil.UTF_8));
}
}
内容来源:
蚂蚁课堂