Netty-概述与组件
选择Netty的原因
- 虽然NIO提供了多路复用IO,但是没有信息格式的封装,例如对Json,但是Netty的ChannelHandler提供了封装
- NIO的类库和API很复杂,需要了解Selector、ByteBuffer等,很复杂
- 客户端权限、心跳控制、粘包拆包的处理,Netty都提供了支持
- Java的NIO框架有一个bug,Selector doesn’t block on Selector.select(timeout),这是JNI的问题,这个在linux环境会出现
- 不用Netty5,因为Netty5使用了ForkJoinPool,增大了复杂度但是性能并不高
为什么使用NIO而不是AIO
- Netty重视在Linux上的部署,而Linux下AIO仍然使用的是EPOLL,没有很好的实现AIO,性能并没有很明显提升。
- AIO还有个缺点就是接收数据需要预先分配缓存,而不是NIO那样的接收时才分配缓存,所以对连接大流量小的情况内存浪费很多
- AIO不成熟,处理回调结果速度跟不上处理需求,类似于外卖员少顾客多供不应求造成处理速度有瓶颈。
ChannelFuture
JDK 预置了interface java.util.concurrent.Future,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以Netty 提供了它自己的实现——ChannelFuture,用于在执行异步操作的时候使用。
Netty组件
Channel
对应Socket
EventLoop
控制、多线程处理、并发。
- 一个EventLoopGroup包含一个或多个EventLoop
- 一个EventLoop在它的生命周期内只能和一个Thread绑定
- 所有由EventLoop处理的IO事件都由对应的Thread处理
- 一个Channel在它的生命周期内只注册于一个EventLoop
- 一个EventLoop可能会被分配一个或多个Channel
ChannelFuture
异步通知
ChannelHandler
处理所有入站出站数据应用逻辑的地方,以适配器的方式提供了大量默认的ChannelHandler。
ChannelPipeline
提供了ChannelHandler链的容器,并定义了入站和出站事件流的API。
当一个ChannelHandler被添加到ChannelPipeline后,会被分配一个ChannelContext,代表了ChannelHandler和ChannelPipeline之间的绑定。
ByteBuf
一个Buf对应一次网络请求
Netty 的ByteBuffer 替代品是ByteBuf,一个强大的实现,既解决了JDK API 的局限性,又为网络应用程序的开发者提供了更好的API。
- 堆缓冲区
- 直接缓冲区
- 复合缓冲区
public class TestServer {
private final int port;
public TestServer(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
int port = 9999;
TestServer testServer = new TestServer(port);
System.out.println("服务器即将启动");
echoServer.start();
System.out.println("服务器关闭");
}
public void start() throws InterruptedException {
final TestServerHandler serverHandler = new TestServerHandler();
/*线程组*/
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
.localAddress(new InetSocketAddress(port))/*指定服务器监听端口*/
/*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
所以下面这段代码的作用就是为这个子channel增加handle*/
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
/*添加到该子channel的pipeline的尾部*/
ch.pipeline().addLast(serverHandler);
}
});
ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/
} finally {
group.shutdownGracefully().sync();/*优雅关闭线程组*/
}
}
}
@ChannelHandler.Sharable
/*不加这个注解那么在增加到childHandler时就必须new出来*/
public class TestServerHandler extends ChannelInboundHandlerAdapter {
/*客户端读到数据以后,就会执行*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf)msg;
System.out.println("Server accept"+in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
/*** 服务端读取完成网络数据后的处理*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
/*** 发生异常后的处理*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
因为有TCP滑动窗口机制,所有并不是每次都能读满一个完整的数据,因此只要读了就会执行channelReadComplete,而只有读完整了某个Bean菜会执行channelRead,并且当未读满滑动窗口就读完了某个Bean,这时先执行channelRead,当窗口内其他内容填满后才会执行channelReadComplete。