Netty之Channel(二)write操作

在原生的java Nio SocketChannel只有一种write方法,将数据写到对端,
关于Netty NioSocketChannel 写入对端数据的过程,和写入相关的,在Netty Channel中有三种api方法:

ChannelFuture write(Object msg)
ChannelFuture write(Object msg, ChannelPromise promise);

ChannelOutboundInvoker flush();

ChannelFuture writeAndFlush(Object msg);
ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
  1. write方法:将数据写到内存队列中,此时数据并没有写入到对端。
  2. flush方法:刷新内存队列,将其中的数据写入到对端。此时数据真正写入到对端。
  3. writeAndFlush方法:将数据写入到内存队列后,立马刷新内存队列,又将其中的数据写入到对端。此时数据已经写入对端。

然而,在Netty Channel中的write(…)和writeAndFlush(…)方法是异步写入的,需要通过监听返回的ChannelFuture来确定是真正的写入。

// 异步监听:
channel.write(msg).addListener(new ChannelFutureListener() {
                
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        // ... 相关逻辑,例如是否成功?    
    }  
});

// 同步异步写入结果:
channel.write(msg).sync();

这里write()方法返回的Promise对象,只有在数据真正被flush()方法调用方法执行后,才会被回调通知。

在AbstractChannel中对write()方法的实现:

@Override
public ChannelFuture write(Object msg) {
    return pipeline.write(msg);
}

@Override
public ChannelFuture write(Object msg, ChannelPromise promise) {
    return pipeline.write(msg, promise);
}

方法内部,调用对应ChannelPipeline中write()方法将write事件在pipeline上传播:
DefaultChannelPipeline中write()方法:

@Override
public final ChannelFuture write(Object msg) {
    return tail.write(msg);
}

@Override
public final ChannelFuture write(Object msg, ChannelPromise promise) {
    return tail.write(msg, promise);
}

在方法的内部会调用TailContext中write()方法,将write事件在pipeline中,从尾结点向头结点传播:
TailContext 对 write()方法的实现,是从AbstractChannelHandlerContext抽象类继承:

 @Override
 public ChannelFuture write(Object msg) {
     return write(msg, newPromise());
//调用write(Object msg)方法,会调用write(Object msg, ChannelPromise promise)方法。(缺少的promise方法参数,通过调用newPromise()方法创建Promise对象,如下:)(放下面比较方便,以后都这样!)
/*
@Override
public ChannelPromise newPromise() {
    return new DefaultChannelPromise(channel(), executor());
    // 返回DefaultChannelPromise对象
}
*/
 }
 
 @Override
 public ChannelFuture write(final Object msg, final ChannelPromise promise) {
     // 消息( 数据 )为空,抛出异常
     if (msg == null) {
         throw new NullPointerException("msg");
     }
 
     try {
         // 判断是否为合法的 Promise 对象
         if (isNotValidPromise(promise, true)) {
             // 释放消息( 数据 )相关的资源
             ReferenceCountUtil.release(msg);
             // cancelled
             return promise;
         }
     } catch (RuntimeException e) {
         // 发生异常,释放消息( 数据 )相关的资源
         ReferenceCountUtil.release(msg);
         throw e;
     }
 
     // 写入消息( 数据 )到内存队列
     write(msg, false, promise);
     return promise;
//返回的结果即为上面传入的promise对象
 }

AbstractChannelHandlerContext抽象类继承的最后调用write(Object msg, boolean flush, ChannelPromise promise)方法,写入消息(数据)到内存队列:

 private void write(Object msg, boolean flush, ChannelPromise promise) {
     // 获得下一个 Outbound 节点
     AbstractChannelHandlerContext next = findContextOutbound();
     // 记录 Record 记录
     final Object m = pipeline.touch(msg, next);

     EventExecutor executor = next.executor();
     // 在 EventLoop 的线程中
     if (executor.inEventLoop()) {
         // 执行 writeAndFlush 事件到下一个节点
         if (flush) {
             next.invokeWriteAndFlush(m, promise);
         // 执行 write 事件到下一个节点
         } else {
             next.invokeWrite(m, promise);
         }
     } else {
         AbstractWriteTask task;
         // 创建 writeAndFlush 任务
         if (flush) {
             task = WriteAndFlushTask.newInstance(next, m, promise);
         // 创建 write 任务
         }  else {
             task = WriteTask.newInstance(next, m, promise);
         }
         // 提交到 EventLoop 的线程中,执行该任务
         safeExecute(executor, task, promise, m);
     }
 }

由上面这段知:当flush为true时,执行的操作为write及flush(将数据写入到内存队列后,刷新内存队列,将其中的数据写入到对端)。
上面代码调用touch(Object msg, AbstractChannelHandlerContext next) 方法来记录 Record 记录:

// DefaultChannelPipeline.java
final Object touch(Object msg, AbstractChannelHandlerContext next) {
    return touch ? ReferenceCountUtil.touch(msg, next) : msg;
}

// ReferenceCountUtil.java
/**
 * Tries to call {@link ReferenceCounted#touch(Object)} if the specified message implements
 * {@link ReferenceCounted}.  If the specified message doesn't implement {@link ReferenceCounted},
 * this method does nothing.
 */
@SuppressWarnings("unchecked")
public static <T> T touch(T msg, Object hint) {
    if (msg instanceof ReferenceCounted) {
        return (T) ((ReferenceCounted) msg).touch(hint);
    }
    return msg;
}

至此,再回到AbstractChannel对write()方法的实现,最终在pipeline中,write事件到HeadContext节点,将数据写入到内存队列:

@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    unsafe.write(msg, promise);
}

这里调用了AbstractUnsafe中write(Object msg, ChannelPromise promise) 方法,将数据写到内存队列中:

/**
 * 内存队列
 */
private volatile ChannelOutboundBuffer outboundBuffer = new ChannelOutboundBuffer(AbstractChannel.this);
//outboundBuffer属性,内存队列,用于缓存写入的数据。
  @Override
  public final void write(Object msg, ChannelPromise promise) {
      assertEventLoop();
  
      ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
      // 内存队列为空,
      if (outboundBuffer == null) {
          // 内存队列为空,一般是 Channel 已经关闭,所以通知 Promise 异常结果
          // If the outboundBuffer is null we know the channel was closed and so
          // need to fail the future right away. If it is not null the handling of the rest
          // will be done in flush0()
          // See https://github.com/netty/netty/issues/2362
          safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
          // 释放消息( 对象 )相关的资源
          // release message now to prevent resource-leak
          ReferenceCountUtil.release(msg);
          return;
      }
  
      int size;
      try {
          // 过滤写入的消息( 数据 )
          msg = filterOutboundMessage(msg);
          // 计算消息的长度
          size = pipeline.estimatorHandle().size(msg);
          if (size < 0) {
              size = 0;
          }
      } catch (Throwable t) {
          // 通知 Promise 异常结果
          safeSetFailure(promise, t);
          // 释放消息( 对象 )相关的资源
          ReferenceCountUtil.release(msg);
          return;
      }
  
      // 写入消息( 数据 )到内存队列
      outboundBuffer.addMessage(msg, size, promise);
  }

上面关于filterOutboundMessage(msg) 方法,过滤写入的消息( 数据 ):

// AbstractNioByteChannel.java

@Override
protected final Object filterOutboundMessage(Object msg) {
    //  这里是当消息(数据)是 ByteBuf 类型的情况,如果不是该类型对象,则需调用newDirectBuffer(ByteBuf)方法,复制封装成Direct ByteBuf对象。
    //原因是:在使用 Socket 传递数据时性能很好,由于数据直接在内存中,不存在从 JVM 拷贝数据到直接缓冲区的过程,性能好
    if (msg instanceof ByteBuf) {
        ByteBuf buf = (ByteBuf) msg;
        // 已经是内存 ByteBuf
        if (buf.isDirect()) {
            return msg;
        }

        // 非内存 ByteBuf ,需要进行创建封装
        return newDirectBuffer(buf);
    }

    //  FileRegion 的情况
    if (msg instanceof FileRegion) {
        return msg;
    }

    //  不支持其他类型
    throw new UnsupportedOperationException("unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
}

接下来看AbstractWriteTask,实现Runnable接口,写入任务抽象类。它有两个子类实现:

  1. WriteTask ,write 任务实现类
  2. WriteAndFlushTask ,write + flush 任务实现类。

它们都是 AbstractChannelHandlerContext 的内部静态类。

关于AbstractWriteTask的构造方法:

/**
 * 提交任务时,是否计算 AbstractWriteTask 对象的自身占用内存大小
 */
private static final boolean ESTIMATE_TASK_SIZE_ON_SUBMIT = SystemPropertyUtil.getBoolean("io.netty.transport.estimateSizeOnSubmit", true);

/**
 * 每个 AbstractWriteTask 对象自身占用内存的大小。
 */
// Assuming a 64-bit JVM, 16 bytes object header, 3 reference fields and one int field, plus alignment
private static final int WRITE_TASK_OVERHEAD = SystemPropertyUtil.getInt("io.netty.transport.writeTaskSizeOverhead", 48);
/*
WRITE_TASK_OVERHEAD 静态字段,每个 AbstractWriteTask 对象自身占用内存的大小。为什么占用的 48 字节呢?
- 16 bytes object header ,对象头,16 字节。
- 3 reference fields ,3 个对象引用字段,3 * 8 = 24 字节。
- 1 int fields ,1 个 int 字段,4 字节。
padding ,补齐 8 字节的整数倍,因此 4 字节。
因此,合计 48 字节( 64 位的 JVM 虚拟机,并且不考虑压缩 )。
*/
private final Recycler.Handle<AbstractWriteTask> handle;
/**
 * pipeline 中的节点
 */
private AbstractChannelHandlerContext ctx;
/**
 * 消息( 数据 )
 */
private Object msg;
/**
 * Promise 对象
 */
private ChannelPromise promise;
/**
 * 对象大小
 */
private int size;

@SuppressWarnings("unchecked")
private AbstractWriteTask(Recycler.Handle<? extends AbstractWriteTask> handle) {
    this.handle = (Recycler.Handle<AbstractWriteTask>) handle;
}

init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) 方法,初始化 AbstractWriteTask 对象:

protected static void init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
    task.ctx = ctx;
    task.msg = msg;
    task.promise = promise;
    // 计算 AbstractWriteTask 对象大小 
    if (ESTIMATE_TASK_SIZE_ON_SUBMIT) {
        task.size = ctx.pipeline.estimatorHandle().size(msg) + WRITE_TASK_OVERHEAD;
        // 增加 ChannelOutboundBuffer 的 totalPendingSize 属性  
        ctx.pipeline.incrementPendingOutboundBytes(task.size);
    } else {
        task.size = 0;
    }
}

incrementPendingOutboundBytes(long size) 方法,增加 ChannelOutboundBuffer 的 totalPendingSize 属性:

// DefaultChannelPipeline.java
@UnstableApi
protected void incrementPendingOutboundBytes(long size) {
    ChannelOutboundBuffer buffer = channel.unsafe().outboundBuffer();
    if (buffer != null) {
        buffer.incrementPendingOutboundBytes(size);
    }
}

上面incrementPendingOutboundBytes(long size, …) 方法,增加 totalPendingSize 计数:

 /**
  * Increment the pending bytes which will be written at some point.
  * This method is thread-safe!
  */
 void incrementPendingOutboundBytes(long size) {
     incrementPendingOutboundBytes(size, true);
 }
 
 private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
     if (size == 0) {
         return;
     }
 
     // 增加 totalPendingSize 计数
     long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
     // totalPendingSize 大于高水位阀值时,设置为不可写
     if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
         setUnwritable(invokeLater);
     }
 }

关于第 16 至 19 行:totalPendingSize 大于高水位阀值时,调用 #setUnwritable(boolean invokeLater) 方法,设置为不可写:

 private void setUnwritable(boolean invokeLater) {
     for (;;) {
         final int oldValue = unwritable;
         // 或位操作,修改第 0 位 bits 为 1
         final int newValue = oldValue | 1;
         // CAS 设置 unwritable 为新值
         if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
             // 若之前可写,现在不可写,触发 Channel WritabilityChanged 事件到 pipeline 中。
             if (oldValue == 0 && newValue != 0) {
                 fireChannelWritabilityChanged(invokeLater);
             }
             break;
         }
     }
 }

这里写了一些flush相关源码,具体等下次更新。
再回到AbstractWriteTask中,run()实现方法:

 @Override
 public final void run() {
     try {
         // 减少 ChannelOutboundBuffer 的 totalPendingSize 属性 
         // Check for null as it may be set to null if the channel is closed already
         if (ESTIMATE_TASK_SIZE_ON_SUBMIT) {
             ctx.pipeline.decrementPendingOutboundBytes(size);
         }
         // 执行 write 事件到下一个节点
         write(ctx, msg, promise);
     } finally {
         // 置空AbstractWriteTask 对象,help gc
         // Set to null so the GC can collect them directly
         ctx = null;
         msg = null;
         promise = null;
         // 回收对象
         handle.recycle(this);
     }
 }

上面调用 调用decrementPendingOutboundBytes(long size) 方法,减少 ChannelOutboundBuffer 的 totalPendingSize 属性。

@UnstableApi
protected void decrementPendingOutboundBytes(long size) {
    ChannelOutboundBuffer buffer = channel.unsafe().outboundBuffer();
    if (buffer != null) {
        buffer.decrementPendingOutboundBytes(size);
    }
}

回到run()方法实现,调用 write(ctx, msg, promise) 方法,执行 write 事件到下一个节点:

protected void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
    ctx.invokeWrite(msg, promise);
}

WriteTask: 实现了 SingleThreadEventLoop.NonWakeupRunnable 接口,继承 AbstractWriteTask 抽象类,write 任务实现类。
由于实现了SingleThreadEventLoop.NonWakeupRunnable 接口,write 操作,仅仅将数据写到内存队列中,无需唤醒 EventLoop ,从而提升性能,
WriteTask 无需实现 write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) 方法,直接重用父类该方法即可。
newInstance(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) 方法,创建 WriteTask 对象:

private static final Recycler<WriteTask> RECYCLER = new Recycler<WriteTask>() {

    @Override
    protected WriteTask newObject(Handle<WriteTask> handle) {
        return new WriteTask(handle); // 创建 WriteTask 对象
    }

};

private static WriteTask newInstance(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
    // 从 Recycler 的对象池中获得 WriteTask 对象
    WriteTask task = RECYCLER.get();
    // 初始化 WriteTask 对象的属性
    init(task, ctx, msg, promise);
    return task;
}

构造方法:

private WriteTask(Recycler.Handle<WriteTask> handle) {
    super(handle);
}

WriteAndFlushTask ,继承 WriteAndFlushTask 抽象类,write + flush 任务实现类:
newInstance(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) 方法,创建 WriteAndFlushTask 对象:

private static final Recycler<WriteAndFlushTask> RECYCLER = new Recycler<WriteAndFlushTask>() {

    @Override
    protected WriteAndFlushTask newObject(Handle<WriteAndFlushTask> handle) {
        return new WriteAndFlushTask(handle); // 创建 WriteAndFlushTask 对象
    }

};

private static WriteAndFlushTask newInstance(AbstractChannelHandlerContext ctx, Object msg,  ChannelPromise promise) {
    // 从 Recycler 的对象池中获得 WriteTask 对象
    WriteAndFlushTask task = RECYCLER.get();
    // 初始化 WriteTask 对象的属性
    init(task, ctx, msg, promise);
    return task;
}

构造方法:

private WriteAndFlushTask(Recycler.Handle<WriteAndFlushTask> handle) {
    super(handle);
}

write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) 方法,在父类的该方法的基础上,增加执行 flush 事件到下一个节点:

@Override
public void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
    // 执行 write 事件到下一个节点
    super.write(ctx, msg, promise);
    // 执行 flush 事件到下一个节点
    ctx.invokeFlush();
}

这里有write相关操作并且涵盖一些flush操作,(两者关系密切,下节flush操作继续)暂时结束。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要自定义一个可序列化的 `io.netty.channel.Channel`,你需要实现以下步骤: 1. 创建一个 Java 类来实现你的 Channel。这个类需要继承 `io.netty.channel.AbstractChannel` 类,并且实现必要的构造函数和方法。 2. 为了实现信息的接收与发送,你需要覆盖 `io.netty.channel.AbstractChannel` 类中的 `doBind()`、`doConnect()`、`doWrite()`、`doReadMessages()` 方法。这些方法分别负责绑定、连接、写入数据和读取数据。 3. 为了实现序列化,你需要实现 `java.io.Serializable` 接口,并且在你的 Channel 类中添加 `writeObject()` 和 `readObject()` 方法。这些方法分别负责将 Channel 对象序列化成字节流和反序列化字节流成 Channel 对象。 下面是一个简单的示例代码: ```java import io.netty.channel.AbstractChannel; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class MyChannel extends AbstractChannel implements Serializable { // 构造函数 public MyChannel(Channel parent) { super(parent); } // 实现信息的接收与发送 @Override protected void doBind(SocketAddress localAddress) throws Exception { // 绑定操作 } @Override protected void doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { // 连接操作 } @Override protected void doWrite(ChannelOutboundBuffer outboundBuffer) throws Exception { // 写入数据操作 } @Override protected void doReadMessages(List<Object> buf) throws Exception { // 读取数据操作 } // 实现序列化 private void writeObject(ObjectOutputStream out) throws IOException { // 将 Channel 对象序列化成字节流 } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 将字节流反序列化成 Channel 对象 } } ``` 你可以根据你的具体需求进行更改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值