netty 抽象nio字节通道

本文深入解析了Netty中的AbstractNioByteChannel类,详细介绍了其写操作流程和NioByteUnsafe类的读操作过程。写操作通过遍历刷新链上的写请求,并根据写请求消息类型调用相应的方法完成数据发送。读操作则从通道接收缓冲区读取数据,触发读取事件。
netty 通道接口定义:[url]http://donald-draper.iteye.com/blog/2392740[/url]
netty 抽象通道初始化:[url]http://donald-draper.iteye.com/blog/2392801[/url]
netty 抽象Unsafe定义:[url]http://donald-draper.iteye.com/blog/2393053[/url]
netty 通道Outbound缓冲区:[url]http://donald-draper.iteye.com/blog/2393098[/url]
netty 抽象通道后续:[url]http://donald-draper.iteye.com/blog/2393166[/url]
netty 抽象nio通道:[url]http://donald-draper.iteye.com/blog/2393269[/url]
[size=medium][b]引言[/b][/size]
前一篇文章,我们看了抽象nio通道,先来回顾一下:
抽象nio通道AbstractNioChannel内部关联一个可选择通道(SelectableChannel)和一个选择key(selectionKey)。抽象Nio通道构造,主要是初始化通道并配置为非阻塞模式。

注册doRegister工作主要是,注册可选择通道到通道所在事件循环的选择器中。反注册doDeregister,委托给事件循环,取消选择key,即从事件循环关联选择器的选择key集合中移除当前选择key。开始读操作doBeginRead,实际工作为将读操作事件,添加选择key的兴趣事件集

抽象nioUnsafe为特殊的Unsafe,允许访问底层的选择通道。选择通道方法返回的实际为抽象nio通道内部的底层可选择通道。移除读兴趣事件removeReadOp,即从选择key兴趣事件集中,移除读操作事件。连接操作,将实际连接操作委托给doConnect,待子类实现,如果连接成功,则通知异步任务连接成功,如果是第一次连接,则触发通道的激活事件fireChannelActive。完成连接操作,实际工作委托给抽象Nio通道的doFinishConnect方法,待子类实现,完成后更新任务结果,触发通道的激活事件fireChannelActive,如果出现异常,则更新连接任务为异常失败。

今天我们来看一下抽象nio字节通道AbstractNioByteChannel
package io.netty.channel.nio;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.FileRegion;
import io.netty.channel.RecvByteBufAllocator;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
import io.netty.util.internal.StringUtil;

import java.io.IOException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;

/**
* {@link AbstractNioChannel} base class for {@link Channel}s that operate on bytes.
*/
public abstract class AbstractNioByteChannel extends AbstractNioChannel {
private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);//通道元数据
private static final String EXPECTED_TYPES =
" (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ", " +
StringUtil.simpleClassName(FileRegion.class) + ')';

private Runnable flushTask;//刷新任务
/**
* Create a new instance
*创建nio字节通道
* @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
* @param ch the underlying {@link SelectableChannel} on which it operates
*/
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
//读操作事件为SelectionKey.OP_READ
super(parent, ch, SelectionKey.OP_READ);
}
}

来看实际写操作:

@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
int writeSpinCount = -1;//写操作自旋此数计数器

boolean setOpWrite = false;
for (;;) {
//获取当前通道Outbound缓冲区中的当前待刷新消息
Object msg = in.current();
if (msg == null) {
// Wrote all messages.
//清除通道选择key兴趣事件集中写操作事件
clearOpWrite();
// Directly return here so incompleteWrite(...) is not called.
return;
}

if (msg instanceof ByteBuf) {//如果为字节buf
ByteBuf buf = (ByteBuf) msg;
int readableBytes = buf.readableBytes();
if (readableBytes == 0) {
//可读字节数为0,则从通道Outbound缓存区中移除写请求
in.remove();
continue;
}

boolean done = false;
long flushedAmount = 0;
if (writeSpinCount == -1) {
//获取通道配置的写请求自旋次数
writeSpinCount = config().getWriteSpinCount();
}
for (int i = writeSpinCount - 1; i >= 0; i --) {
//实际发送字节数据
int localFlushedAmount = doWriteBytes(buf);
if (localFlushedAmount == 0) {
//没有字节要发送
setOpWrite = true;
break;
}
//更新刷新字节计数器
flushedAmount += localFlushedAmount;
if (!buf.isReadable()) {
//如果buf不可读,则数据发送完毕
done = true;
break;
}
}
//报告字节数据发送进度
in.progress(flushedAmount);

if (done) {
//发送完,则移除写请求
in.remove();
} else {
// Break the loop and so incompleteWrite(...) is called.
break;
}
} else if (msg instanceof FileRegion) {
//如果是文件Region消息
FileRegion region = (FileRegion) msg;
boolean done = region.transferred() >= region.count();
if (!done) {
long flushedAmount = 0;
if (writeSpinCount == -1) {
writeSpinCount = config().getWriteSpinCount();
}
for (int i = writeSpinCount - 1; i >= 0; i--) {
//完成实际写操作
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount == 0) {
setOpWrite = true;
break;
}
//更新刷新字节计数器
flushedAmount += localFlushedAmount;
if (region.transferred() >= region.count()) {
done = true;
break;
}
}
//更新数据发送进度
in.progress(flushedAmount);
}

if (done) {
//发送完,则移除写请求
in.remove();
} else {
// Break the loop and so incompleteWrite(...) is called.
break;
}
} else {
// Should not reach here.
throw new Error();
}
}
//完成写后继任务
incompleteWrite(setOpWrite);
}


/**
* Write a {@link FileRegion}
*写一个文件region,待子类扩展
* @param region the {@link FileRegion} from which the bytes should be written
* @return amount the amount of written bytes
*/
protected abstract long doWriteFileRegion(FileRegion region) throws Exception;

/**
* Write bytes form the given {@link ByteBuf} to the underlying {@link java.nio.channels.Channel}.
写一个字节buf,待子类扩展
* @param buf the {@link ByteBuf} from which the bytes should be written
* @return amount the amount of written bytes
*/
protected abstract int doWriteBytes(ByteBuf buf) throws Exception;


//完成写后继任务
protected final void incompleteWrite(boolean setOpWrite) {
// Did not write completely.
if (setOpWrite) {
//发送数据完成,则重新添加写操作事件到选择key兴趣事件集
setOpWrite();
} else {
//否则,继续刷新通道Outbound缓冲区中的写请求
// Schedule flush again later so other tasks can be picked up in the meantime
Runnable flushTask = this.flushTask;
if (flushTask == null) {
flushTask = this.flushTask = new Runnable() {
@Override
public void run() {
flush();
}
};
}
eventLoop().execute(flushTask);
}
}
//添加写操作事件到选择key兴趣事件集
protected final void setOpWrite() {
final SelectionKey key = selectionKey();
// Check first if the key is still valid as it may be canceled as part of the deregistration
// from the EventLoop
// See https://github.com/netty/netty/issues/2104
if (!key.isValid()) {
return;
}
final int interestOps = key.interestOps();
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
key.interestOps(interestOps | SelectionKey.OP_WRITE);
}
}
//清除通道选择key兴趣事件集中写操作事件
protected final void clearOpWrite() {
final SelectionKey key = selectionKey();
// Check first if the key is still valid as it may be canceled as part of the deregistration
// from the EventLoop
// See https://github.com/netty/netty/issues/2104
if (!key.isValid()) {
return;
}
final int interestOps = key.interestOps();
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
}
}

从上面可以看出,写通道Outbound缓冲区,即遍历刷新链上的写请求,如果写请求消息为
字节buf,则调用doWriteBytes完成实际数据发送操作,待子类扩展,如果写请求消息为文件Region,调用doWriteFileRegion完成实际数据发送操作,待子类扩展,数据发送,则更新通道的数据发送进度,并从刷新链上移除写请求;如果所有写请求发送完毕,则重新添加写操作事件到选择key兴趣事件集,否则继续刷新通道Outbound缓冲区中的写请求。

//过滤消息
@Override
protected final Object filterOutboundMessage(Object msg) {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
//如果消息为direct buf,直接返回消息
if (buf.isDirect()) {
return msg;
}
//否则包装buf为direct buf,这个newDirectBuffer方法在上一篇文章中以说
return newDirectBuffer(buf);
}

if (msg instanceof FileRegion) {
//如果是文件Region直接返回
return msg;
}

throw new UnsupportedOperationException(
"unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
}


再看其他方法:
//获取通道元数据
@Override
public ChannelMetadata metadata() {
return METADATA;
}
/**
* Shutdown the input side of the channel.
关闭通道输入流
*/
protected abstract ChannelFuture shutdownInput();
//通道是否关闭输入流
protected boolean isInputShutdown0() {
return false;
}
//创建底层通道操作类Unsafe
@Override
protected AbstractNioUnsafe newUnsafe() {
return new NioByteUnsafe();
}

从newUnsafe方法,返回的为NioByteUnsafe
我们来看nio字节Unsafe:
protected class NioByteUnsafe extends AbstractNioUnsafe {
//读操作
@Override
public final void read() {
//获取通道配置,Channel管道,字节buf分配器,接受字节buf分配器Handle
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);

ByteBuf byteBuf = null;
boolean close = false;
try {
do {
//分配一个字节buf
byteBuf = allocHandle.allocate(allocator);
//读取通道接收缓冲区数据
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
//没有数据可读,则释放buf
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
break;
}
//更新读取消息计数器
allocHandle.incMessagesRead(1);
readPending = false;
//通知通道处理读取数据,触发Channel管道线的fireChannelRead事件
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
//读取操作完毕
allocHandle.readComplete();
//触发Channel管道线的fireChannelReadComplete事件
pipeline.fireChannelReadComplete();

if (close) {
//如果通道关闭,关闭读操作
closeOnRead(pipeline);
}
} catch (Throwable t) {
//处理读操作异常
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
//读操作完毕,且没有配置自动读,则从选择key兴趣集中移除读操作事件
removeReadOp();
}
}
}
//关闭读操作
private void closeOnRead(ChannelPipeline pipeline) {
if (!isInputShutdown0()) {
if (Boolean.TRUE.equals(config().getOption(ChannelOption.ALLOW_HALF_CLOSURE))) {
//关闭通道输入流
shutdownInput();
//触发通道输入关闭事件
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
} else {
close(voidPromise());
}
} else {
//触发通道输入关闭没有数据可读取事件
pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
}
}
//处理读操作异常
private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close,
RecvByteBufAllocator.Handle allocHandle) {
if (byteBuf != null) {
if (byteBuf.isReadable()) {
readPending = false;
//字节buf中还有数据没处理,则继续处理
pipeline.fireChannelRead(byteBuf);
} else {
byteBuf.release();
}
}
//读取操作完毕,触发Channel管道线的fireChannelReadComplete事件及fireExceptionCaught事件
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
pipeline.fireExceptionCaught(cause);
if (close || cause instanceof IOException) {
//如果需要,关闭读操作
closeOnRead(pipeline);
}
}
}

从上面可以看出,nio字节Unsafe读操作,从通道接收缓冲区读取数据,
通知通道处理读取数据,触发Channel管道线的fireChannelRead事件,待数据读取完毕,
触发Channel管道线的fireChannelReadComplete事件,如果在读数据的过程中,通道关闭,则
触发通道输入关闭事件(fireUserEventTriggered),如果在读数据的过程中,发生异常,则
读取缓存区中没有读完的数据,并通道通道处理剩余数据。

[size=medium][b]总结:[/b][/size]

[color=blue]写通道Outbound缓冲区,即遍历刷新链上的写请求,如果写请求消息为字节buf,则调用doWriteBytes完成实际数据发送操作,待子类扩展,如果写请求消息为文件Region,调用doWriteFileRegion完成实际数据发送操作,待子类扩展,数据发送,则更新通道的数据发送进度,并从刷新链上移除写请求;如果所有写请求发送完毕,则重新添加写操作事件到选择key兴趣事件集,否则继续刷新通道Outbound缓冲区中的写请求。

nio字节Unsafe读操作,从通道接收缓冲区读取数据,通知通道处理读取数据,触发Channel管道线的fireChannelRead事件,待数据读取完毕,触发Channel管道线的fireChannelReadComplete事件,如果在读数据的过程中,通道关闭,则触发通道输入关闭事件(fireUserEventTriggered),如果在读数据的过程中,发生异常,则读取缓存区中没有读完的数据,并通道通道处理剩余数据。[/color]

[size=medium][b]附:[/b][/size]

//ChannelMetadata
/**
* Represents the properties of a {@link Channel} implementation.
*/
public final class ChannelMetadata {

private final boolean hasDisconnect;//是否基于无连接的通信,fasle-》TCP,true-》UDP
private final int defaultMaxMessagesPerRead;//每次读取,允许读取的最大消息数
/**
* Create a new instance
*
* @param hasDisconnect {@code true} if and only if the channel has the {@code disconnect()} operation
* that allows a user to disconnect and then call {@link Channel#connect(SocketAddress)}
* again, such as UDP/IP.
* @param defaultMaxMessagesPerRead If a {@link MaxMessagesRecvByteBufAllocator} is in use, then this value will be
* set for {@link MaxMessagesRecvByteBufAllocator#maxMessagesPerRead()}. Must be {@code > 0}.
*/
public ChannelMetadata(boolean hasDisconnect, int defaultMaxMessagesPerRead) {
if (defaultMaxMessagesPerRead <= 0) {
throw new IllegalArgumentException("defaultMaxMessagesPerRead: " + defaultMaxMessagesPerRead +
" (expected > 0)");
}
this.hasDisconnect = hasDisconnect;
this.defaultMaxMessagesPerRead = defaultMaxMessagesPerRead;
}
...
}

//ChannelInputShutdownReadComplete
package io.netty.channel.socket;

/**
* User event that signifies the channel's input side is shutdown,
and we tried to shut it down again. This typically
* indicates that there is no more data to read.
*/
public final class ChannelInputShutdownReadComplete {
public static final ChannelInputShutdownReadComplete INSTANCE = new ChannelInputShutdownReadComplete();

private ChannelInputShutdownReadComplete() {
}
}


//ChannelInputShutdownEvent
package io.netty.channel.socket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;

/**
* Special event which will be fired and passed to the
* {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} methods once the input of
* a {@link SocketChannel} was shutdown and the {@link SocketChannelConfig#isAllowHalfClosure()} method returns
* {@code true}.
*/
public final class ChannelInputShutdownEvent {

/**
* Instance to use
*/
@SuppressWarnings("InstantiationOfUtilityClass")
public static final ChannelInputShutdownEvent INSTANCE = new ChannelInputShutdownEvent();

private ChannelInputShutdownEvent() { }
}
### 回答1: NioSocketChannel类本身并没有实现Serializable接口,也就是说它本身不能被序列化。如果你想将NioSocketChannel对象序列化,你需要自己定义一个类来实现Serializable接口,并将NioSocketChannel对象作为该类的一个成员变量。然后你就可以将这个自定义类的对象序列化了。 具体实现方式可以参考如下代码: ``` import java.io.Serializable; public class MyClass implements Serializable { private NioSocketChannel socketChannel; public MyClass(NioSocketChannel socketChannel) { this.socketChannel = socketChannel; } public NioSocketChannel getSocketChannel() { return socketChannel; } public void setSocketChannel(NioSocketChannel socketChannel) { this.socketChannel = socketChannel; } } ``` 这样你就可以通过如下代码将MyClass对象序列化: ``` MyClass myObject = new MyClass(socketChannel); // 将myObject对象序列化到文件中 FileOutputStream fos = new FileOutputStream("myObject.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(myObject); oos.close(); ``` ### 回答2: NettyNioSocketChannel对象是一个网络套接字通道,用于在Java NIO(非阻塞I/O)中进行网络通信。NioSocketChannel类本身并没有直接实现Serializable接口,因为它不是一个可序列化的对象。 Serializable接口是一个标记接口,用于指示一个类可以被序列化,即可以将其转换为字节流以便在网络中传输或持久化到存储设备中。可序列化对象需要实现Serializable接口,并提供相应的序列化和反序列化方法。 然而,NioSocketChannel是Netty抽象类实现之一,它的子类AbstractChannel已经实现了Serializable接口。可以通过自定义一个类继承NioSocketChannel,并实现Serializable接口来实现将NioSocketChannel对象序列化。 以下是一个可能的示例代码: ```java import io.netty.channel.socket.nio.NioSocketChannel; import java.io.Serializable; public class SerializableNioSocketChannel extends NioSocketChannel implements Serializable { // 添加一个默认的构造函数 public SerializableNioSocketChannel() { super(); } } ``` 在上述示例中,我们创建了一个新的类SerializableNioSocketChannel,该类继承了NioSocketChannel并实现了Serializable接口。通过将NioSocketChannel对象转换为SerializableNioSocketChannel对象,就可以实现对NioSocketChannel对象的序列化。 需要注意的是,由于NioSocketChannel作为网络套接字通道,它包含了底层网络连接状态等非序列化的信息,因此在实际使用中,可能需要根据具体需求选择序列化对象中需要保留的属性。 ### 回答3: NettyNioSocketChannel对象不能直接实现Serializable接口,因为NioSocketChannel类并没有实现Serializable接口。Serializable接口是Java提供的一种机制,用于将对象转换为字节流,以便能够在网络中传输或持久化到磁盘。 然而,我们可以通过一些其他方式来实现将NioSocketChannel对象序列化和反序列化。比如,可以通过将NioSocketChannel的一些关键信息提取出来,例如远程地址、本地地址、管道选项等,并将这些信息进行序列化和反序列化。 具体实现方式如下: 1. 创建一个包含NioSocketChannel关键信息的可序列化类,例如NioSocketChannelWrapper。 2. 在NioSocketChannelWrapper中定义所需的关键信息字段,例如remoteAddress、localAddress、channelOptions等。 3. 实现NioSocketChannelWrapper类的序列化接口Serializable,并添加序列化和反序列化方法。 4. 在需要将NioSocketChannel对象序列化时,通过构建NioSocketChannelWrapper对象,并将关键信息赋值给NioSocketChannelWrapper的相应字段。 5. 将NioSocketChannelWrapper对象进行序列化处理,并传输到目标。 6. 在需要反序列化NioSocketChannel对象时,接收到NioSocketChannelWrapper对象后,进行反序列化操作,将关键信息提取出来。 7. 通过Netty的Bootstrap或其他相关类,使用提取出的关键信息重新构建NioSocketChannel对象。 需要注意的是,反序列化后的NioSocketChannel对象并不是原始NioSocketChannel对象的完全克隆,而是通过关键信息重新构建的新对象。一些与网络连接、管道状态等相关的信息可能无法被序列化和恢复。 总之,NettyNioSocketChannel对象不能直接实现Serializable接口,但通过提取关键信息并自定义可序列化类,我们可以实现将NioSocketChannel对象进行序列化和反序列化操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值