Netty学习笔记七-Channel分析

Channel介绍

Channel是JDK 的NIO类库中的重要组成部分,我们在之前的代码中也经常用到io.netty.channel.socket.nio.NioSocketChannel和io.netty.channel.socket.nio.NioServerSocketChannel用于阻塞性IO操作
NioServerSocketChannel类的继承图


Netty学习笔记七-Channel分析_第1张图片
image.png

从上可以看出,Channel是最顶层的抽象,一个Channel抽象为网络socket的连接或者读写,连接,绑定IO的一个组件。
一个Channel需要提供给用户:
1、channel当前的状态(打开还是已连接?)
2、channel的配置参数(channelConfig)
3、channel支持哪些IO操作
4、处理channel IO操作的事件
在Netty中所有的IO操作都是异步的,也就意味着IO的请求会立刻返回,并不能保证请求是否已完成。在Netty中IO请求会返回ChannelFuture实例。
Channel是有层级的,例如,对于服务端而言,父channel为空,对于客户端NioSocketChannel,它的父Channel就是创建它的ServerSocketChannel.

Channel主要API方法

下面对我们对Channel的主要API做简单介绍
Channel read() 从channel中读取数据到第一个buffer,如果数据被成功读取触发channelRead事件,如果数据被读取完成会触发channelReadComplete事件,如果有读操作被挂起,那么后续读操作会取消。
ChannelFuture write(Object msg) 将当前msg通过ChannelPipeLine写入到Channel,这个方法不会真正执行flush操作,所以当写入完成后要执行flush()方法才能将数据真正发送出去。
write(Object msg, ChannelPromise promise) 跟write功能一样,只是写入完成后会回调promise。
Channel flush() 将channel中所有缓存消息全部写入到目标channel,发送给通信对方。
ChannelFuture writeAndFlush(Object msg) 作用等于write+flush
ChannelFuture disconnect() 请求与远程通信对端断开连接,这个操作会级联触发ChannelHandler.disconnect(ChannelHandlerContext, ChannelPromise)事件
ChannelFuture connect(SocketAddress remoteAddress) 使用指定的remoteAddress发起连接请求,并且返回ChannelFuture对象,如果连接超时会ChannelFuture返回操作结果是ConnectTimeoutException,如果连接被拒绝操作结果是ConnectException,该操作会触发ChannelHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)事件。
EventLoop eventLoop() 返回channel注册的eventLoop
ChannelConfig config() 返回channel的配置
ChannelMetadata metadata() 返回当前channel的元数据信息,比如TCP参数配置。
SocketAddress localAddress() 返回channel绑定的本地地址
SocketAddress remoteAddress() 返回channel通信的远程地址
Channel parent() 返回父级channel

AbstractChannel分析

AbstractChannel是Channel的基本实现类,它采用聚合的方式封装了各种功能,从成员变量聚合了以下内容:
private final Channel parent :父级channel
private final ChannelId id = DefaultChannelId.newInstance() 全局唯一id
private final Unsafe unsafe : unsafe示例
private final DefaultChannelPipeline pipeline; 当前channle对应的DefaultChannelPipeline
private final EventLoop eventLoop; 当前channel注册的EventLoop
前面提到channel的IO操作会触发会产生对应的IO事件,然后事件在ChannelPipeLine中传播,并由对应的ChannleHandler处理。
AbstractChannel的IO操作直接调用DefaultChannelPipeline的相关方法由DefaultChannelPipeline处理相关逻辑

@Override
    public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
        return pipeline.connect(remoteAddress, localAddress);
    }
 @Override
    public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return pipeline.bind(localAddress, promise);
    }

    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return pipeline.connect(remoteAddress, promise);
    }
……

AbstractNioChannel分析

AbstractChannel是Channel的基本实现类,那么AbstractNioChannel就是Channel基于选择器的实现类,它实现了核心的将Channel注册到Selector的功能。
首先看下成员变量

private static final InternalLogger logger =
            InternalLoggerFactory.getInstance(AbstractNioChannel.class);

    private final SelectableChannel ch;//JDK NIO SelectableChannel
    protected final int readInterestOp;//JDK selectionKey 的OP_READ
    private volatile SelectionKey selectionKey;
    private volatile boolean inputShutdown;

    /**
     * The future of the current connection attempt.  If not null, subsequent
     * connection attempts will fail.
     */
    private ChannelPromise connectPromise;
    private ScheduledFuture connectTimeoutFuture;
    private SocketAddress requestedRemoteAddress;

核心注册功能源码:

protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 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.
                    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;
                }
            }
        }
    }

定义布尔类型变量selected标识是否注册成功,调用SelectableChannel的register将当前channel注册到EventLoop的多路复用器Selector上。
其中SelectableChannel的注册方法定义如下:

public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;

第二个参数是 指定监听的网络操作位,表示channel对哪几类网络事件感兴趣,具体定义如下:

    public static final int OP_READ = 1 << 0; //读事件
    public static final int OP_WRITE = 1 << 2;//写事件
    public static final int OP_CONNECT = 1 << 3;//客户端连接服务端事件
    public static final int OP_ACCEPT = 1 << 4;//服务端接收客户端连接事件

其中注册时传的ops等于0,表示不对任何事件感兴趣,只是完成注册操作,注册成功之后返回selectionKey,通过selectionKey可以获取注册的channel.
如果注册返回的selectionKey被取消,则抛出CancelledKeyException异常,如果是第一次抛异常则调用selectNow将取消的selectionKey删除并将selected置为true并再次注册,如果仍未成功则抛出CancelledKeyException异常。
处理读操作之前设置网络操作位为读

protected void doBeginRead() throws Exception {
        if (inputShutdown) {
            return;
        }

        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

先判断Channel是否关闭,如果处于关闭则直接返回,然后获取当前的SelectKey的操作位与读操作位位于,如果结果为0标识没有设置过读操作位,最后通过或设置读操作位

AbstractNioByteChannel分析

AbstractNioByteChannel是Channel操作字节的实现,核心代码是protected void doWrite(ChannelOutboundBuffer in)

protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        int writeSpinCount = -1;

        for (;;) {
            Object msg = in.current(true);
            if (msg == null) {
                // Wrote all messages.
                clearOpWrite();
                break;
            }

首先从ChannelOutboundBuffer环形数组中读取数据,如果数据为空,调用clearOpWrite清除读标志位

if (msg instanceof ByteBuf) {
                ByteBuf buf = (ByteBuf) msg;
                int readableBytes = buf.readableBytes();
                if (readableBytes == 0) {
                    in.remove();
                    continue;
                }

如果数据不为空,判断是否是ByteBuf类型,若是强制转换为ByteBuf,并判断ByteBuf中是否有可读字节,如果没有则将该消息从数组中删除,并继续处理其他消息。

boolean setOpWrite = false;
                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()) {
                        done = true;
                        break;
                    }
                }

设置半包标志setOpWrite,消息是否全部发送标识done,发送总消息数flushedAmount,对循环发送次数writeSpinCount判断,如果为-1从channel配置重获取循环发送次数,调用doWriteBytes进行循环发送,如果本次发送的字节数为0那么说明TCP缓冲区已满,所以讲写半包标识置为true并跳出循环。

如果发送字节数大于0那么对发送字节数进行累加,如果当前buf没有可读字节数了则标识buf写入完成,设置消息发送标识为true并跳出循环。

in.progress(flushedAmount);

                if (done) {
                    in.remove();
                } else {
                    incompleteWrite(setOpWrite);
                    break;
                }

调用progress更新进度信息,如果消息已完全发送则将该buf从环形数组中移除,否则调用incompleteWrite方法

protected final void incompleteWrite(boolean setOpWrite) {
        // Did not write completely.
        if (setOpWrite) {
            setOpWrite();
        } else {
            // 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);
        }
    }

如果写半包标识setOpWrite为true,调用setOpWrite()重新设置写操作位setOpWrite()

 protected final void setOpWrite() {
        final SelectionKey key = selectionKey();
        final int interestOps = key.interestOps();
        if ((interestOps & SelectionKey.OP_WRITE) == 0) {
            key.interestOps(interestOps | SelectionKey.OP_WRITE);
        }
    }

SelectKey设置为OP_WRITE后,Selector会不断轮询对应的Channel处理没有发送完成的半包消息,直到清除OP_WRITE标志为止。
如果没有设置半包标识,则需要新起一个线程来处理。

你可能感兴趣的:(Netty学习笔记七-Channel分析)