5.2 AbstractNioChannel源碼分析
?AbstractNioChannel從名字可以看出是對NIO的抽象,首先看下這個類的NioUnsafe接口:
/**
* Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel}
*/
public interface NioUnsafe extends Unsafe {
/**
* Return underlying {@link SelectableChannel}
*/
SelectableChannel ch(); // 對應(yīng)NIO中的JDK實現(xiàn)的Channel
/**
* Finish connect
*/
void finishConnect(); // 連接完成
/**
* Read from underlying {@link SelectableChannel}
*/
void read(); // 從JDK的Channel中讀取數(shù)據(jù)
void forceFlush();
}
?回憶NIO的三大概念:Channel、Buffer、Selector,Netty的Channel包裝了JDK的Channel從而實現(xiàn)更為復(fù)雜的功能。Unsafe中可以使用ch()方法,NioChannel中可以使用javaChannel()方法獲得JDK的Channel。接口中定義了finishConnect()方法是因為SelectableChannel設(shè)置為非阻塞模式時,connect()方法會立即返回,此時連接操作可能沒有完成,如果沒有完成,則需要調(diào)用JDK的finishConnect()方法完成連接操作。也許你已經(jīng)注意到,AbstractUnsafe中并沒有connect事件框架,這是因為并不是所有連接都有標(biāo)準(zhǔn)的connect過程,比如Netty的LocalChannel和EmbeddedChannel。但是NIO中的連接操作則有較為標(biāo)準(zhǔn)的流程,在介紹Connect事件框架前,先介紹一下其中使用到的相關(guān)字段,這些字段定義在AbstractNioChannel中:
/**
* The future of the current connection attempt. If not null, subsequent
* connection attempts will fail.
*/
private ChannelPromise connectPromise; // 連接異步結(jié)果
private ScheduledFuture<?> connectTimeoutFuture; // 連接超時檢測任務(wù)異步結(jié)果
private SocketAddress requestedRemoteAddress; // 連接的遠(yuǎn)端地址
Connect事件框架:
@Override
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return; // Channel已被關(guān)閉
}
try {
if (connectPromise != null) {
// Already a connect in process.
throw new ConnectionPendingException(); // 已有連接操作正在進(jìn)行
}
boolean wasActive = isActive();
// 模板方法,細(xì)節(jié)子類完成
if (doConnect(remoteAddress, localAddress)) {
fulfillConnectPromise(promise, wasActive); // 連接操作已完成
} else {
// 連接操作尚未完成
connectPromise = promise;
requestedRemoteAddress = remoteAddress;
// 這部分代碼為Netty的連接超時機(jī)制
// Schedule connect timeout.
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// 連接操作取消則連接超時檢測任務(wù)取消
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
promise.tryFailure(annotateConnectException(t, remoteAddress));
closeIfClosed();
}
}
?Connect事件框架中包含了Netty的連接超時檢測機(jī)制:向EventLoop提交一個調(diào)度任務(wù),設(shè)定的超時時間已到則向連接操作的異步結(jié)果設(shè)置失敗然后關(guān)閉連接。fulfillConnectPromise()設(shè)置異步結(jié)果為成功并觸發(fā)Channel的Active事件:
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
if (promise == null) {
// Closed via cancellation and the promise has been notified already.
return; // 操作已取消或Promise已被通知
}
// Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel.
// We still need to ensure we call fireChannelActive() in this case.
boolean active = isActive();
// trySuccess() will return false if a user cancelled the connection attempt.
boolean promiseSet = promise.trySuccess();
// Regardless if the connection attempt was cancelled, channelActive() event should be triggered,
// because what happened is what happened.
if (!wasActive && active) {
pipeline().fireChannelActive();
}
// If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
if (!promiseSet) {
close(voidPromise());
}
}
FinishConnect事件框架:
@Override
public final void finishConnect() {
// Note this method is invoked by the event loop only if the connection attempt was
// neither cancelled nor timed out.
assert eventLoop().inEventLoop();
try {
boolean wasActive = isActive();
doFinishConnect();
fulfillConnectPromise(connectPromise, wasActive); // 首次Active觸發(fā)Active事件
} catch (Throwable t) {
fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
} finally {
// Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
// See https://github.com/netty/netty/issues/1770
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false); // 連接完成,取消超時檢測任務(wù)
}
connectPromise = null;
}
}
finishConnect()只由EventLoop處理就緒selectionKey的OP_CONNECT事件時調(diào)用,從而完成連接操作。注意:連接操作被取消或者超時不會使該方法被調(diào)用。
Flush事件細(xì)節(jié):
@Override
protected final void flush0() {
// Flush immediately only when there's no pending flush.
// If there's a pending flush operation, event loop will call forceFlush() later,
// and thus there's no need to call it now.
if (!isFlushPending()) {
super.flush0(); // 調(diào)用父類方法,在父類判斷是否已經(jīng)又調(diào)用;
}
}
@Override
public final void forceFlush() {
// directly call super.flush0() to force a flush now
super.flush0();
}
private boolean isFlushPending() {
SelectionKey selectionKey = selectionKey();
return selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0;
}
?forceFlush()方法由EventLoop處理就緒selectionKey的OP_WRITE事件時調(diào)用,將緩沖區(qū)中的數(shù)據(jù)寫入Channel。isFlushPending()方法容易導(dǎo)致困惑:為什么selectionKey關(guān)心OP_WRITE事件表示正在Flush呢?OP_WRITE表示通道可寫,而一般情況下通道都可寫,如果selectionKey一直關(guān)心OP_WRITE事件,那么將不斷從select()方法返回從而導(dǎo)致死循環(huán)。Netty使用一個寫緩沖區(qū),write操作將數(shù)據(jù)放入緩沖區(qū)中,flush時設(shè)置selectionKey關(guān)心OP_WRITE事件,完成后取消關(guān)心OP_WRITE事件。所以,如果selectionKey關(guān)心OP_WRITE事件表示此時正在Flush數(shù)據(jù)。
?AbstractNioUnsafe還有最后一個方法removeReadOp():
protected final void removeReadOp() {
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; // selectionKey已被取消
}
int interestOps = key.interestOps();
if ((interestOps & readInterestOp) != 0) {
// only remove readInterestOp if needed
key.interestOps(interestOps & ~readInterestOp); //設(shè)置為不再感興趣
}
}
?Netty中將服務(wù)端的OP_ACCEPT和客戶端的Read統(tǒng)一抽象為Read事件,在NIO底層I/O事件使用bitmap表示,一個二進(jìn)制位對應(yīng)一個I/O事件。當(dāng)一個二進(jìn)制位為1時表示關(guān)心該事件,readInterestOp的二進(jìn)制表示只有1位為1,所以體會interestOps & ~readInterestOp的含義,可知removeReadOp()的功能是設(shè)置SelectionKey不再關(guān)心Read事件。類似的,還有setReadOp()、removeWriteOp()、setWriteOp()等等。
?分析完AbstractNioUnsafe,我們再分析AbstractNioChannel,首先看其中還沒講解的字段:
private final SelectableChannel ch; // 包裝的JDK Channel
protected final int readInterestOp; // Read事件,服務(wù)端OP_ACCEPT,其他OP_READ
volatile SelectionKey selectionKey; // JDK Channel對應(yīng)的選擇鍵
boolean readPending; // 底層讀事件進(jìn)行標(biāo)記
再看一下構(gòu)造方法:
/**
* Create a new instance
*
* @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
* @param readInterestOp the ops to set to receive data from the {@link SelectableChannel}
*/
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
ch.configureBlocking(false); // 設(shè)置為非阻塞模式
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
?其中的ch.configureBlocking(false)方法設(shè)置Channel為非阻塞模式,從而為Netty提供非阻塞處理I/O事件的能力。
?對于AbstractNioChannel的方法,我們主要分析它實現(xiàn)I/O事件框架細(xì)節(jié)部分的doXXX()方法。
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
// 選擇鍵取消重新selectNow(),清除因取消操作而緩存的選擇鍵
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
@Override
protected void doDeregister() throws Exception {
eventLoop().cancel(selectionKey());
}
?對于Register事件,當(dāng)Channel屬于NIO時,已經(jīng)可以確定注冊操作的全部細(xì)節(jié):將Channel注冊到給定NioEventLoop的selector上即可。注意,其中第二個參數(shù)0表示注冊時不關(guān)心任何事件,第三個參數(shù)為Netty的NioChannel對象本身。對于Deregister事件,選擇鍵執(zhí)行cancle()操作,選擇鍵表示JDK Channel和selector的關(guān)系,調(diào)用cancle()終結(jié)這種關(guān)系,從而實現(xiàn)從NioEventLoop中Deregister。需要注意的是:cancle操作調(diào)用后,注冊關(guān)系不會立即生效,而會將cancle的key移入selector的一個取消鍵集合,當(dāng)下次調(diào)用select相關(guān)方法或一個正在進(jìn)行的select調(diào)用結(jié)束時,會從取消鍵集合中移除該選擇鍵,此時注銷才真正完成。一個Cancel的選擇鍵為無效鍵,調(diào)用它相關(guān)的方法會拋出CancelledKeyException。
@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return; // 選擇鍵被取消而不再有效
}
readPending = true; // 設(shè)置底層讀事件正在進(jìn)行
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
// 選擇鍵關(guān)心Read事件
selectionKey.interestOps(interestOps | readInterestOp);
}
}
?對于NioChannel的beginRead事件,只需將Read事件設(shè)置為選擇鍵所關(guān)心的事件,則之后的select()調(diào)用如果Channel對應(yīng)的Read事件就緒,便會觸發(fā)Netty的read()操作。
@Override
protected void doClose() throws Exception {
ChannelPromise promise = connectPromise;
if (promise != null) {
// Use tryFailure() instead of setFailure() to avoid the race against cancel().
// 連接操作還在進(jìn)行,但用戶調(diào)用close操作
promise.tryFailure(DO_CLOSE_CLOSED_CHANNEL_EXCEPTION);
connectPromise = null;
}
ScheduledFuture<?> future = connectTimeoutFuture;
if (future != null) { // 如果有連接超時檢測任務(wù),則取消
future.cancel(false);
connectTimeoutFuture = null;
}
}
?此處的doClose操作主要處理了連接操作相關(guān)的后續(xù)處理。并沒有實際關(guān)閉Channel,所以需要子類繼續(xù)增加細(xì)節(jié)實現(xiàn)。AbstractNioChannel中還有關(guān)于創(chuàng)建DirectBuffer的方法,將在以后必要時進(jìn)行分析。其他的方法則較為簡單,不在列出。最后提一下isCompatible()方法,說明NioChannel只在NioEventLoop中可用。
@Override
protected boolean isCompatible(EventLoop loop) {
return loop instanceof NioEventLoop;
}
?AbstractNioChannel的子類實現(xiàn)分為服務(wù)端AbstractNioMessageChannel和客戶端AbstractNioByteChannel,我們將首先分析服務(wù)端AbstractNioMessageChannel。
5.3 AbstractNioMessageChannel源碼分析
?AbstractNioMessageChannel是底層數(shù)據(jù)為消息的NioChannel。在Netty中,服務(wù)端Accept的一個Channel被認(rèn)為是一條消息,UDP數(shù)據(jù)報也是一條消息。該類主要完善flush事件框架的doWrite細(xì)節(jié)和實現(xiàn)read事件框架(在內(nèi)部類NioMessageUnsafe完成)。首先看read事件框架:
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
int localRead = doReadMessages(readBuf); // 模板方法,讀取消息
if (localRead == 0) { // 沒有數(shù)據(jù)可讀
break;
}
if (localRead < 0) { // 讀取出錯
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false; // 已沒有底層讀事件
pipeline.fireChannelRead(readBuf.get(i)); //觸發(fā)ChannelRead事件,用戶處理
}
readBuf.clear();
allocHandle.readComplete();
// ChannelReadComplete事件中如果配置autoRead則會調(diào)用beginRead,從而不斷進(jìn)行讀操作
pipeline.fireChannelReadComplete(); // 觸發(fā)ChannelReadComplete事件,用戶處理
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise()); // 非serverChannel且打開則關(guān)閉
}
}
} 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()) {
// 既沒有配置autoRead也沒有底層讀事件進(jìn)行
removeReadOp(); // 清除read事件,不再關(guān)心
}
}
}
}
?read事件框架的流程已在代碼中注明,需要注意的是讀取消息的細(xì)節(jié)doReadMessages(readBuf)方法由子類實現(xiàn)。
?我們主要分析NioServerSocketChannel,它不支持doWrite()操作,所以我們不再分析本類的flush事件框架的doWrite細(xì)節(jié)方法,直接轉(zhuǎn)向下一個目標(biāo):NioServerSocketChannel。
5.4 NioServerSocketChannel源碼分析
?你肯定已經(jīng)使用過NioServerSocketChannel,Netty的example中大量使用了此類,作為處于Channel最底層的子類,NioServerSocketChannel會實現(xiàn)I/O事件框架的底層細(xì)節(jié)。首先需要注意的是:NioServerSocketChannel只支持bind、read和close操作。
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) { // JDK版本1.7以上
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
@Override
protected void doClose() throws Exception {
javaChannel().close();
}
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
// 一個NioSocketChannel為一條消息
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
?其中的實現(xiàn),都是調(diào)用JDK的Channel的方法,從而實現(xiàn)了最底層的細(xì)節(jié)。需要注意的是:此處的doReadMessages()方法每次最多返回一個消息(客戶端連接),由此可知NioServerSocketChannel的read操作一次至多處理的連接數(shù)為config.getMaxMessagesPerRead(),也就是參數(shù)值MAX_MESSAGES_PER_READ。此外doClose()覆蓋了AbstractNioChannel的實現(xiàn),因為NioServerSocketChannel不支持connect操作,所以不需要連接超時處理。
?最后,我們再看關(guān)鍵構(gòu)造方法:
/**
* Create a new instance
*/
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
/**
* Create a new instance using the given {@link SelectorProvider}.
*/
public NioServerSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
}
/**
* Create a new instance using the given {@link ServerSocketChannel}.
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
?其中的SelectionKey.OP_ACCEPT最為關(guān)鍵,Netty正是在此處將NioServerSocketChannel的read事件定義為NIO底層的OP_ACCEPT,統(tǒng)一完成read事件的抽象。
?至此,我們已分析完兩條線索中的服務(wù)端部分,下面分析客戶端部分。首先是AbstractNioChannel的另一個子類AbstractNioByteChannel。
5.5 AbstractNioByteChannel源碼分析
?從字面可推知,AbstractNioByteChannel的底層數(shù)據(jù)為Byte字節(jié)。首先看構(gòu)造方法:
/**
* Create a new instance
*
* @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) {
super(parent, ch, SelectionKey.OP_READ);
}
?其中的SelectionKey.OP_READ,說明AbstractNioByteChannel的read事件為NIO底層的OP_READ事件。
?然后我們看read事件框架:
@Override
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null; // 創(chuàng)建一個ByteBuf
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator); // 創(chuàng)建一個ByteBuf
allocHandle.lastBytesRead(doReadBytes(byteBuf)); // doReadBytes模板方法,子類實現(xiàn)細(xì)節(jié)
if (allocHandle.lastBytesRead() <= 0) { // 沒有數(shù)據(jù)可讀
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0; // 讀取數(shù)據(jù)量為負(fù)數(shù)表示對端已經(jīng)關(guān)閉
if (close) {
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false; // 沒有底層讀事件進(jìn)行
pipeline.fireChannelRead(byteBuf); // 觸發(fā)ChannelRead事件,用戶處理
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
// ReadComplete結(jié)束時,如果開啟autoRead則會調(diào)用beginRead,從而可以繼續(xù)read
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()) {
// 既沒有配置autoRead也沒有底層讀事件進(jìn)行
removeReadOp();
}
}
}
}
?AbstractNioByteChannel的read事件框架處理流程與AbstractNioMessageChannel的稍有不同:AbstractNioMessageChannel依次讀取Message,最后統(tǒng)一觸發(fā)ChannelRead事件;而AbstractNioByteChannel每讀取到一定字節(jié)就觸發(fā)ChannelRead事件。這是因為,AbstractNioMessageChannel需求高吞吐量,特別是ServerSocketChannel需要盡可能多地接受連接;而AbstractNioByteChannel需求快響應(yīng),要盡可能快地響應(yīng)遠(yuǎn)端請求。
?read事件的具體流程請參考代碼和代碼注釋進(jìn)行理解,不再分析。注意到代碼中有關(guān)于接收緩沖區(qū)的代碼,這一部分我們單獨使用一節(jié)講述,之后會分析。當(dāng)讀取到的數(shù)據(jù)小于零時,表示遠(yuǎn)端連接已關(guān)閉,這時會調(diào)用closeOnRead(pipeline)方法:
private void closeOnRead(ChannelPipeline pipeline) {
if (!isInputShutdown0()) {
if (isAllowHalfClosure(config())) {
shutdownInput();
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
} else {
close(voidPromise()); // 直接關(guān)閉
}
} else {
inputClosedSeenErrorOnRead = true;
pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
}
}
?這段代碼正是Channel參數(shù)ALLOW_HALF_CLOSURE的意義描述,該參數(shù)為True時,會觸發(fā)用戶事件ChannelInputShutdownEvent,否則,直接關(guān)閉該Channel。拋出異常時,會調(diào)用handleReadException(pipeline, byteBuf, t, close)方法:
private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close,
RecvByteBufAllocator.Handle allocHandle) {
if (byteBuf != null) { // 已讀取到數(shù)據(jù)
if (byteBuf.isReadable()) { // 數(shù)據(jù)可讀
readPending = false;
pipeline.fireChannelRead(byteBuf);
} else { // 數(shù)據(jù)不可讀
byteBuf.release();
}
}
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
pipeline.fireExceptionCaught(cause);
if (close || cause instanceof IOException) {
closeOnRead(pipeline);
}
}
?可見,拋出異常時,如果讀取到可用數(shù)據(jù)和正常讀取一樣觸發(fā)ChannelRead事件,只是最后會統(tǒng)一觸發(fā)ExceptionCaught事件由用戶進(jìn)行處理。
?至此,read事件框架分析完畢,下面我們分析write事件的細(xì)節(jié)實現(xiàn)方法doWrite()。在此之前,先看filterOutboundMessage()方法對需要寫的數(shù)據(jù)進(jìn)行過濾。
@Override
protected final Object filterOutboundMessage(Object msg) {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
if (buf.isDirect()) {
return msg;
}
return newDirectBuffer(buf); // 非DirectBuf轉(zhuǎn)為DirectBuf
}
if (msg instanceof FileRegion) {
return msg;
}
throw new UnsupportedOperationException(
"unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
}
?可知,Netty支持的寫數(shù)據(jù)類型只有兩種:DirectBuffer和FileRegion。我們再看這些數(shù)據(jù)怎么寫到Channel上,也就是doWrite()方法:
/**
* Write objects to the OS.
* @param in the collection which contains objects to write.
* @return The value that should be decremented from the write quantum which starts at
* {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
* <ul>
* <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
* is encountered</li>
* <li>1 - if a single call to write data was made to the OS</li>
* <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but no
* data was accepted</li>
* </ul>
* @throws Exception if an I/O exception occurs during write.
*/
protected final int doWrite0(ChannelOutboundBuffer in) throws Exception {
Object msg = in.current();
if (msg == null) {
// Directly return here so incompleteWrite(...) is not called.
return 0;
}
return doWriteInternal(in, in.current());
}
private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
if (!buf.isReadable()) {
in.remove();
return 0;
}
final int localFlushedAmount = doWriteBytes(buf); // 模板方法,子類實現(xiàn)細(xì)節(jié)
if (localFlushedAmount > 0) {
in.progress(localFlushedAmount); // 記錄進(jìn)度
if (!buf.isReadable()) {
in.remove(); // 完成時,清理緩沖區(qū)
}
return 1; // 跳出循環(huán)執(zhí)行incompleteWrite()
}
} else if (msg instanceof FileRegion) {
FileRegion region = (FileRegion) msg;
if (region.transferred() >= region.count()) {
in.remove();
return 0; // 跳出循環(huán)執(zhí)行incompleteWrite()
}
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount > 0) {
in.progress(localFlushedAmount); // 記錄進(jìn)度
if (region.transferred() >= region.count()) {
in.remove();
}
return 1;
}
} else {
// Should not reach here.
throw new Error(); // 其他類型不支持
}
return WRITE_STATUS_SNDBUF_FULL;
}
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
int writeSpinCount = config().getWriteSpinCount();
do {
Object msg = in.current();
if (msg == null) { // 數(shù)據(jù)已全部寫完
// Wrote all messages.
clearOpWrite(); // 清除OP_WRITE事件
// Directly return here so incompleteWrite(...) is not called.
return;
}
writeSpinCount -= doWriteInternal(in, msg);
} while (writeSpinCount > 0);
incompleteWrite(writeSpinCount < 0);
}
@Override
protected final Object filterOutboundMessage(Object msg) {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
if (buf.isDirect()) {
return msg;
}
return newDirectBuffer(buf); // 非DirectBuf轉(zhuǎn)為DirectBuf
}
if (msg instanceof FileRegion) {
return msg;
}
throw new UnsupportedOperationException(
"unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
}
?代碼中省略了對FileRegion的處理,F(xiàn)ileRegion是Netty對NIO底層的FileChannel的封裝,負(fù)責(zé)將File中的數(shù)據(jù)寫入到WritableChannel中。FileRegion的默認(rèn)實現(xiàn)是DefaultFileRegion,如果你很感興趣它的實現(xiàn),可以自行查閱。
我們主要分析對ByteBuf的處理。doWrite的流程簡潔明了,核心操作是模板方法doWriteBytes(buf),將ByteBuf中的數(shù)據(jù)寫入到Channel,由于NIO底層的寫操作返回已寫入的數(shù)據(jù)量,在非阻塞模式下該值可能為0,此時會調(diào)用incompleteWrite()方法:
protected final void incompleteWrite(boolean setOpWrite) {
// Did not write completely.
if (setOpWrite) {
setOpWrite(); // 設(shè)置繼續(xù)關(guān)心OP_WRITE事件
} else {
// It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then
// use our write quantum. In this case we no longer want to set the write OP because the socket is still
// writable (as far as we know). We will find out next time we attempt to write if the socket is writable
// and set the write OP if necessary.
clearOpWrite();
// Schedule flush again later so other tasks can be picked up in the meantime
eventLoop().execute(flushTask); // 再次提交一個flush()任務(wù)
}
}
?該方法分兩種情況處理,在上文提到的第一種情況(實際寫0數(shù)據(jù))下,設(shè)置SelectionKey繼續(xù)關(guān)心OP_WRITE事件從而繼續(xù)進(jìn)行寫操作;第二種情況下,也就是寫操作進(jìn)行次數(shù)達(dá)到配置中的writeSpinCount值但尚未寫完,此時向EventLoop提交一個新的flush任務(wù),此時可以響應(yīng)其他請求,從而提交響應(yīng)速度。這樣的處理,不會使大數(shù)據(jù)的寫操作占用全部資源而使其他請求得不到響應(yīng),可見這是一個較為公平的處理。這里引出一個問題:使用Netty如何搭建高性能文件服務(wù)器?
至此,已分析完對于Byte數(shù)據(jù)的read事件和doWrite細(xì)節(jié)的處理,接下里,繼續(xù)分析NioSocketChannel,從而完善各事件框架的細(xì)節(jié)部分。
5.6 NioSocketChannel源碼分析
?NioSocketChannel作為Channel的最末端子類,實現(xiàn)了NioSocket相關(guān)的最底層細(xì)節(jié)實現(xiàn),首先看doBind():
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
doBind0(localAddress);
}
private void doBind0(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) { // JDK版本1.7以上
SocketUtils.bind(javaChannel(), localAddress);
} else {
SocketUtils.bind(javaChannel().socket(), localAddress);
}
}
?這部分代碼與NioServerSocketChannel中相同,委托給JDK的Channel進(jìn)行綁定操作。
接著再看doConnect()和doFinishConnect()方法:
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
// 設(shè)置關(guān)心OP_CONNECT事件,事件就緒時調(diào)用finishConnect()
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
@Override
protected void doFinishConnect() throws Exception {
if (!javaChannel().finishConnect()) {
throw new Error();
}
}
?JDK中的Channel在非阻塞模式下調(diào)用connect()方法時,會立即返回結(jié)果:成功建立連接返回True,操作還在進(jìn)行時返回False。返回False時,需要在底層OP_CONNECT事件就緒時,調(diào)用finishConnect()方法完成連接操作。
再看doDisconnect()和doClose()方法:
@Override
protected void doDisconnect() throws Exception {
doClose();
}
@Override
protected void doClose() throws Exception {
super.doClose(); // AbstractNioChannel中關(guān)于連接超時的處理
javaChannel().close();
}
?然后看核心的doReadBytes()和doWriteXXX()方法:
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
@Override
protected int doWriteBytes(ByteBuf buf) throws Exception {
final int expectedWrittenBytes = buf.readableBytes();
return buf.readBytes(javaChannel(), expectedWrittenBytes);
}
@Override
protected long doWriteFileRegion(FileRegion region) throws Exception {
final long position = region.transferred();
return region.transferTo(javaChannel(), position);
}
?對于read和write操作,委托給ByteBuf處理,我們將使用專門的一章,對這一部分細(xì)節(jié)進(jìn)行完善,將在后面介紹。
NioSocketChannel最重要的部分是覆蓋了父類的doWrite()方法,使用更高效的方式進(jìn)行寫操作,其代碼如下:
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
SocketChannel ch = javaChannel();
int writeSpinCount = config().getWriteSpinCount();
do {
if (in.isEmpty()) {
// All written so clear OP_WRITE
clearOpWrite(); // 所有數(shù)據(jù)已寫完,不再關(guān)心OP_WRITE事件
// Directly return here so incompleteWrite(...) is not called.
return;
}
// Ensure the pending writes are made of ByteBufs only.
int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
int nioBufferCnt = in.nioBufferCount();
// Always us nioBuffers() to workaround data-corruption.
// See https://github.com/netty/netty/issues/2761
switch (nioBufferCnt) {
case 0: // 沒有ByteBuffer,也就是只有FileRegion
// We have something else beside ByteBuffers to write so fallback to normal writes.
writeSpinCount -= doWrite0(in); // 使用父類方法進(jìn)行普通處理
break;
case 1: { // 只有一個ByteBuffer,此時的處理等效于父類方法的處理
// Only one ByteBuf so use non-gathering write
// Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
// to check if the total size of all the buffers is non-zero.
ByteBuffer buffer = nioBuffers[0];
int attemptedBytes = buffer.remaining();
final int localWrittenBytes = ch.write(buffer);
if (localWrittenBytes <= 0) {
incompleteWrite(true);
return;
}
adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
in.removeBytes(localWrittenBytes);
--writeSpinCount;
break;
}
default: { // 多個ByteBuffer,采用gathering方法處理
// Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
// to check if the total size of all the buffers is non-zero.
// We limit the max amount to int above so cast is safe
long attemptedBytes = in.nioBufferSize();
// gathering方法,此時一次寫多個ByteBuffer
final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
if (localWrittenBytes <= 0) {
incompleteWrite(true);
return;
}
// Casting to int is safe because we limit the total amount of data in the nioBuffers to int above.
adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes,
maxBytesPerGatheringWrite);
in.removeBytes(localWrittenBytes); // 清理緩沖區(qū)
--writeSpinCount;
break;
}
}
} while (writeSpinCount > 0);
incompleteWrite(writeSpinCount < 0);
}
?在明白了父類的doWrite方法后,這段代碼便容易理解,本段代碼做的優(yōu)化是:當(dāng)輸出緩沖區(qū)中有多個buffer時,采用Gathering Writes將數(shù)據(jù)從這些buffer寫入到同一個channel。
?在AbstractUnsafe對close事件框架的分析中,有一個prepareToClose()方法,進(jìn)行關(guān)閉的必要處理并在必要時返回一個Executor執(zhí)行doClose()操作,默認(rèn)方法返回null,NioSocketChannelUnsafe覆蓋了父類的實現(xiàn),代碼如下:
@Override
protected Executor prepareToClose() {
try {
if (javaChannel().isOpen() && config().getSoLinger() > 0) {
// We need to cancel this key of the channel so we may not end up in a eventloop spin
// because we try to read or write until the actual close happens which may be later due
// SO_LINGER handling.
// See https://github.com/netty/netty/issues/4449
doDeregister(); // 取消選擇鍵selectionKey
return GlobalEventExecutor.INSTANCE;
}
} catch (Throwable ignore) {
// Ignore the error as the underlying channel may be closed in the meantime and so
// getSoLinger() may produce an exception. In this case we just return null.
// See https://github.com/netty/netty/issues/4449
}
return null;
}
?SO_LINGER表示Socket關(guān)閉的延時時間,在此時間內(nèi),內(nèi)核將繼續(xù)把TCP緩沖區(qū)的數(shù)據(jù)發(fā)送給對端且執(zhí)行close操作的線程將阻塞直到數(shù)據(jù)發(fā)送完成。Netty的原則是I/O線程不能被阻塞,所以此時返回一個Executor用于執(zhí)行阻塞的doClose()操作。doDeregister()取消選擇鍵selectionKey是因為:延遲關(guān)閉期間, 如果selectionKey仍然關(guān)心OP_WRITE事件,而輸出緩沖區(qū)又為null,這樣write操作直接返回,不會再執(zhí)行clearOpWrite()操作取消關(guān)心OP_WRITE事件,而Channel一般是可寫的,這樣OP_WRITE事件會不斷就緒從而耗盡CPU,所以需要取消選擇鍵刪除注冊的事件。