当前位置:首页 > Web开发 > 正文

Netty网络框架

2024-03-31 Web开发

标签:

Netty网络框架

Netty是一个异步的基于事件驱动的网络框架。

为什么要使用Netty而不直接使用JAVA中的NIO

1.Netty支持三种IO模型同时支持三种Reactor模式。

2.Netty支持很多应用层的协议,提供了很多decoder和encoder。

3.Netty能够解决TCP长连接所带来的缺陷(粘包、半包等)

4.Netty支持应用层的KeepAlive。

5.Netty规避了JAVA NIO中的很多BUG,性能更好。

Netty启动服务端

1.创建ServerBootstrap服务端启动对象。

2.配置bossGroup和workerGroup,其中bossGroup负责接收连接,workerGroup负责处理连接的读写就绪事件。

3.配置父Channel,一般为NioServerSocketChannel。

4.配置子Channel与Handler之间的关系。

5.给父Channel配置参数。

6.给子Channel配置参数。

7.绑定端口,启动服务。

private void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 配置父Channel .childHandler(new ChannelInitializer<SocketChannel>() { // 配置子Channel与Handler之间的关系 @Override protected void initChannel(SocketChannel socketChannel) { // 往ChannelPipeline中添加ChannelHandler socketChannel.pipeline().addLast( new HttpRequestDecoder(), new HttpObjectAggregator(65535), new HttpResponseEncoder(), new HttpServerHandler() ); } }) .option(ChannelOption.SO_BACKLOG, 128) // 给父Channel配置参数 .childOption(ChannelOption.SO_KEEPALIVE, true); // 给子Channel配置参数 try { // 绑定端口,启动服务 System.out.println("start server and bind 8888 port ..."); serverBootstrap.bind(8888).sync(); } catch (InterruptedException e) { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } Netty启动客户端

1.创建Bootstrap客户端启动对象。

2.配置workerGroup,负责处理连接的读写就绪事件。

3.配置父Channel,一般为NioSocketChannel。

4.给父Channel配置参数。

5.配置父Channel与Handler之间的关系。

6.连接服务器。

private void start() { EventLoopGroup workerGroup = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) // 配置父Channel .option(ChannelOption.SO_KEEPALIVE, true) // 给父Channel配置参数 .handler(new ChannelInitializer<SocketChannel>() { // 配置父Channel与Handler之间的关系 @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new TimeClientHandler()); } }); try { bootstrap.connect(new InetSocketAddress(8888)).sync(); // 连接服务器 } catch (InterruptedException e) { workerGroup.shutdownGracefully(); } } ChannelInBoundHandler接口声明了事件的处理方法 channelActive():当建立一个新的Channel时调用该方法 handlerAdd():当往Channel的ChannelPipeline中添加Handler时调用该方法 handlerRemove():当移除ChannelPipeline中的Handler时调用该方法 channelRead():当Channel有数据可读时调用该方法 exceptionCaught():当在处理事件发生异常时调用该方法

ServerSocketChannel每接收到一个新的连接时都会建立一个SocketChannel,然后调用ChannelInitializer的init方法初始化Channel,方法中配置Channel与Handler之间的关系,然后调用Handler的handlerAdd()和channelActive()方法。

关于ChannelPipeline

ChannelPipeline底层使用双向链表。

技术图片

当Channel有数据可读时,会沿着链表从前往后寻找有IN性质的Handler进行处理。

当Channel写入数据时,会沿着链表从后往前寻找有OUT性质的Handler进行处理。

关于write()和flush()方法

graph TB; S1[Channel的write方法] --将数据写入到缓冲区--> buffer[缓冲区]; S2[Channel的flush方法] --发送缓冲区中的数据并清空--> buffer[缓冲区]; buffer --发送--> S3[SocketChannel];

write():将数据写入到缓冲区 flush():发送缓冲区中的数据并进行清空 writeAndFlush():将数据写入到缓冲区,同时发送缓冲区中的数据并进行清空

Channel的writeAndFlush()和flush()方法会从链表的最后一个节点开始从后往前寻找有OUT性质的Handler进行处理。

ChannelHandlerContext的writeAndFlush()和flush()方法会从当前节点从后往前寻找有OUT性质的Handler进行处理。

关于写就绪事件

当SocketChannel可以写入数据时,将会触发写就绪事件,所以一般不能随便监听,否则将会一直触发。

当SocketChannel在写入数据写不进时(缓冲区已经满了),向Selector传递要监听此Channel的写就绪事件,然后强制发送缓冲区中的数据并进行清空,此时将会触发写就绪事件,当Selector处理完写就绪事件后,应当剔除监听此Channel的写就绪事件。

为什么说Netty中的所有操作都是异步的

Channel中的所有任务都会放入到其绑定的EventLoop的任务队列中,然后等待被EventLoop中的线程处理。

关于ChannelFuture

由于Netty中的所有操作都是异步的,因此一般会返回ChannelFuture对象,用于存储Channel异步执行的结果。

当创建ChannelFuture实例时,isDone()方法返回false,仅当ChannelFuture被设置成成功或者失败时,isDone()方法才返回true。

可以往ChannelFuture中添加ChannelFutureListener,,当任务被执行完毕后由IO线程自动调用。

Netty中的ByteBuf

技术图片

ByteBuf有readerIndex和writerIndex两个指针,默认都为0,当进行写操作时移动writerIndex指针,读操作时移动readerIndex指针。

可读容量 = writerIndex - readerIndex

*只有read()/write()方法才会移动指针,get()/set()方法不会移动指针。

*ByteBuf支持动态扩容。

ByteBuf的创建和管理

使用ByteBufAllocator来创建和管理ByteBuf,其分别提供PooledByteBufAllocator和UnpooledByteBufAllocator实现类,分别代表池化和非池化。

*Netty同时也提供了Pooled和Unpooled工具类来创建和管理ByteBuf。

池化的ByteBuf(Pooled)

每次使用时都从池中取出一个ByteBuf对象,当使用完毕后再放回到池中。

每个ByteBuf都有一个refCount属性,仅当refCount属性为0时才将ByteBuf对象放回到池中。

ByteBuf的release()方法可以使refCount属性减1(一般由最后一个访问ByteBuf的Handler进行处理)

非池化的ByteBuf(Unpooled)

每次使用时都创建一个新的ByteBuf对象。

使用池化ByteBuf的风险

如果每次使用ByteBuf后却不进行释放,那么有可能发生内存泄漏,对象池中会不停的创建ByteBuf对象。

非池化的ByteBuf对象能够依赖JVM自动进行回收。

关于堆内和堆外的ByteBuf

池化和非池化的ByteBufAllocator中都可以创建堆内和堆外的ByteBuf对象。

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/web/41712.html