继续Netty第二部的内容
ChannelHandler是Netty的主要组件,处理所有的入站和出站数据的应用程序逻辑的容器,可以应用在数据的格式转换、异常处理、数据报文统计等
继承ChannelHandler的两个子接口:
ChannelInboundHandler:处理入站数据以及各种状态变化
ChannelOutboundHandler:处理出站数据并且允许拦截所有的操作
出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel、ChannelPipeline 以及 ChannelHandlerContext 调用。
所有由 ChannelOutboundHandler 本身所定义的方法:
ChannelInboundHandlerAdapter(处理入站)和ChannelOutboundHandlerAdapter(处理出站)是Netty提供的ChannelHandler基类,降低了ChannelHandler实现的复杂度
ChannelOutboundHandler.read()是主动触发读事件的,或者是处理读事件的前置处理器;调用ChannelOutboundHandler.read()方法时Channel会向Selector读取数据,读取数据之后会交给ChannelPipeline,这时候ChannelPipeline就会触发ChannelInboundHandler.channelRead()读事件
继承ChannelDuplexHandler,也可以同时实现ChannelOutboundHandler, ChannelInboundHandler这两个接口
ChannelHandlerAdapter提供了isSharable()方法,如果实现ChannelHandler的类上指定了@Sharable注解,isSharable()方法就会返回true,反之则否;
每个SocketChannel都有自己的ChannelPipeline,同时SocketChannel会对应一个EventLoop,也就是说只会一个线程来处理,ChannelHandler实例之间是完全独立,只要不是共享了全局变量,ChannelHandler是线程安全的
继承ChannelDuplexHandler类,同时类上加上@Sharable注解注解,指定该实例为static,保证只有一个,添加到ChannelPipeline中,重写read()和flush()方法,就可以具体发包收包记录了
在NIO实现中需要依靠创建buffer来进行Channel之间的数据交换;
Netty也是同样的设计,在read网络数据时由Netty创建Buffer,write时会拿到这个Buffer写到网络中(outBoundHandler处理了write()操作并丢弃了数据,而read()需要继续往下一个Handler传递,所以没有相关处理)
可能产生内存泄露的情况:
正常执行fireChannelRead方法入站往后传递Buffer,Netty都会释放Buffer,Netty提供了SimpleChannelInboundHandler类来支持这种情况,继承SimpleChannelInboundHandler类,重写channelRead0方法,即使不抛出异常,也不调用fireChannelRead方法,最终Netty也会帮我们做Buffer的释放
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- boolean release = true;
- try {
- if (acceptInboundMessage(msg)) {
- @SuppressWarnings("unchecked")
- I imsg = (I) msg;
- channelRead0(ctx, imsg);
- } else {
- release = false;
- ctx.fireChannelRead(msg);
- }
- } finally {
- if (autoRelease && release) {
- ReferenceCountUtil.release(msg);
- }
- }
- }
-
- /**
- * Is called for each message of type {@link I}.
- *
- * @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
- * belongs to
- * @param msg the message to handle
- * @throws Exception is thrown if an error occurred
- */
- protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
ServerBootstrap将绑定到一个端口,因为服务器必须要监听连接,而Bootstrap则是由想要连接到远程节点的客户端应用程序所使用的。
引导一个客户端只需要一个EventLoopGroup,但是一个ServerBootstrap则需要两个(也可以是同一个实例)
- @Override
- public ServerBootstrap group(EventLoopGroup group) {
- return group(group, group);
- }
-
- /**
- * Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
- * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and
- * {@link Channel}'s.
- */
- public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
- super.group(parentGroup);
- if (this.childGroup != null) {
- throw new IllegalStateException("childGroup set already");
- }
- this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
- return this;
- }
与ServerChannel相关联的EventLoopGroup将分配一个负责为传入连接请求创建Channel的EventLoop。一旦连接被接受,第二个EventLoopGroup就会给它的Channel分配 一个EventLoop。
ChannelInitializer是ChannelInboundHandlerAdapter的子类,当中的initChannel()方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。你只需要简单地向Bootstrap或ServerBootstrap的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel()版本。在该方法返回之后,ChannelInitializer的实例将会从ChannelPipeline中移除它自己。
- /**
- * This method will be called once the {@link Channel} was registered. After the method returns this instance
- * will be removed from the {@link ChannelPipeline} of the {@link Channel}.
- *
- * @param ch the {@link Channel} which was registered.
- * @throws Exception is thrown if an error occurs. In that case it will be handled by
- * {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
- * the {@link Channel}.
- */
- protected abstract void initChannel(C ch) throws Exception;
-
- @SuppressWarnings("unchecked")
- private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
- if (initMap.add(ctx)) { // Guard against re-entrance.
- try {
- initChannel((C) ctx.channel());
- } catch (Throwable cause) {
- // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
- // We do so to prevent multiple calls to initChannel(...).
- exceptionCaught(ctx, cause);
- } finally {
- ChannelPipeline pipeline = ctx.pipeline();
- if (pipeline.context(this) != null) {
- pipeline.remove(this);
- }
- }
- return true;
- }
- return false;
- }
ByteBuf 维护了两个不同的索引,名称以 read 或者 write 开头的 ByteBuf 方法,将会 推进其对应的索引,而名称以 set 或者 get 开头的操作则不会。 如果打算读取字节直到 readerIndex 达到和 writerIndex 同样的值时会发生什么。在那 时,你将会到达“可以读取的”数据的末尾。就如同试图读取超出数组末尾的数据一样,试 图读取超出该点的数据将会触发一个 IndexOutOf-BoundsException。 可以指定 ByteBuf 的最大容量。试图移动写索引(即 writerIndex)超过这个值将会触发 一个异常。(默认的限制是 Integer.MAX_VALUE。)
分包产生的原因就简单的多:就是一个数据包被分成了多次接收。 更具体的原因至少包括:
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节 数是不确定的,故可能存在以下 4 种情况。
如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。
由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重 组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案, 可以归纳如下。
具体可以参考:几种Java常用序列化框架的选型与对比-阿里云开发者社区