Netty-Channel

Channel的常用操作(与网络通信相关)
  • Channel read()
    从当前的Channel中读取数据到第一个缓冲区中,如果数据被成功读取,触发ChannelHandler.channelRead()事件,读取操作调用完成后,紧接着会触发ChannelHandler.channelReadComplete事件,这样业务的ChannelHandler可以决定是否需要继续读取数据
  • Channel write(Object msg)
    请求将当前的msg通过ChannelPipeline写入到目标Channel中。注意,write操作只是将消息存入到消息发送环形数组中,并没有被真正发送,只有调用flush操作才会被写入到Channel中,发送给对方。
  • Channel write(Object msg,ChannelPromise promise)
    功能与write(Object msg)相同,多携带了ChannelPromise参数负责写入操作的结果
  • ChannelFuture writeAndFlush(Object msg,ChannelPromise promise)
    功能与上相同,不同之处在于会将消息写入到Channel中发送,等价于write + flush
  • Channel flush()
    将之前写入到发送环形数组中的消息全部写入到目标Channel中,发送给通信对方
  • ChannelFuture close(ChannelPromise promise)
    主动关闭当前连接,通过ChannelPromise设置操作结果并进行结果通知,无论是否操作成功,都可以通过ChannelPromise获取操作结果。该操作会级联触发ChannelPipeline中所有的ChannelHandler的close事件
  • ChannelFuture disconnect(ChannelPromise promise)
    请求断开与远程通信对端的链接并使用ChannelPromise获取操作结果的通知消息,级联触发ChannelHandler的disconnect事件
  • ChannelFuture connect(SocketAddress remoteAddress)
    客户端发起连接请求,如果连接请求因为超时而失败,ChannelFuture中的操作结果就是ConnectTimeoutException异常,如果连接被拒绝,就是ConnectException异常。该方法会级联触发ChannelHandler的connect事件
Channel的其他常用操作
  • EventLoop eventLoop()–获取Channel对应的eventLoop()
    Channel需要注册到EventLoop的多路复用器上,用于处理IO事件。EventLoop本质上就是处理网络读写事件的Reactor线程
  • ChannelMetadata metadata()–获取当前Chanel的TCP参数配置
Channel源码分析

服务端NioServerSocketChannel
在这里插入图片描述
客户端NioSocketChannel
在这里插入图片描述

NioServerSocketChannel
public class NioServerSocketChannel extends AbstractNioMessageServerChannel implements ServerSocketChannel {
    private static final ChannelMetadata METADATA = new ChannelMetadata(false);
    private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerSocketChannel.class);
    private final ServerSocketChannelConfig config = new DefaultServerSocketChannelConfig(this, this.javaChannel().socket());

    //静态方法调用jdk下的NIO生成ServerSocketChannel
    private static java.nio.channels.ServerSocketChannel newSocket() {
        try {
            return java.nio.channels.ServerSocketChannel.open();
        } catch (IOException var1) {
            throw new ChannelException("Failed to open a server socket.", var1);
        }
    }

    public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) {
        super((Channel)null, eventLoop, childGroup, newSocket(), 16);
    }

    public InetSocketAddress localAddress() {
        return (InetSocketAddress)super.localAddress();
    }

    public ChannelMetadata metadata() {
        return METADATA;
    }

    public ServerSocketChannelConfig config() {
        return this.config;
    }

    public boolean isActive() {
        return this.javaChannel().socket().isBound();
    }

    public InetSocketAddress remoteAddress() {
        return null;
    }

    protected java.nio.channels.ServerSocketChannel javaChannel() {
        return (java.nio.channels.ServerSocketChannel)super.javaChannel();
    }

    protected SocketAddress localAddress0() {
        return this.javaChannel().socket().getLocalSocketAddress();
    }

    protected void doBind(SocketAddress localAddress) throws Exception {
        this.javaChannel().socket().bind(localAddress, this.config.getBacklog());
    }

    protected void doClose() throws Exception {
        this.javaChannel().close();
    }

    protected int doReadMessages(List<Object> buf) throws Exception {
	    //接收新的客户端连接
        SocketChannel ch = this.javaChannel().accept();

        try {
            if (ch != null) {//SocketChannel不空,则利用当前的NioServerSocketChannel、EventLoop和SocketChannel创建新的NioSocketChannel,并加入List<Object> buf中,最后返回1
                buf.add(new NioSocketChannel(this, this.childEventLoopGroup().next(), ch));
                return 1;
            }
        } catch (Throwable var6) {
            logger.warn("Failed to create a new channel from an accepted socket.", var6);

            try {
                ch.close();
            } catch (Throwable var5) {
                logger.warn("Failed to close a socket.", var5);
            }
        }

        return 0;
    }
    //与服务端接口无关,如果误调则抛出异常
    protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        throw new UnsupportedOperationException();
    }

    protected void doFinishConnect() throws Exception {
        throw new UnsupportedOperationException();
    }

    protected SocketAddress remoteAddress0() {
        return null;
    }

    protected void doDisconnect() throws Exception {
        throw new UnsupportedOperationException();
    }

    protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception {
        throw new UnsupportedOperationException();
    }
}
NioSocketChannel

先分析连接操作

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (localAddress != null) {//如果本地Socket地址不为空,则调用jdk下的NIO  socket.bind()方法绑定本地地址
            this.javaChannel().socket().bind(localAddress);
        }

        boolean success = false;

        boolean var5;
        try {//如果绑定成功,发起TCP连接
            boolean connected = this.javaChannel().connect(remoteAddress);
            if (!connected) {//连接不成功
                this.selectionKey().interestOps(8);//连接失败则将NioSocketChannel中的selectionKet设置为OP_CONNECT
            }
             //连接成功
            success = true;
            var5 = connected;
        } finally {
            if (!success) {
                this.doClose();
            }

        }

        return var5;
    }

继续分析写操作

protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        while(true) {
		    //获取待发送的ByteBuf个数,如果小于等于1,则调用父类的doWrite()方法之后退出
            int msgCount = in.size();
            if (msgCount <= 1) {
                super.doWrite(in);
                return;
            }
			//批量发送缓冲区的消息之前,先对一系列的局部变量进行赋值
            ByteBuffer[] nioBuffers = in.nioBuffers();
            if (nioBuffers == null) {
                super.doWrite(in);
                return;
            }

            int nioBufferCnt = in.nioBufferCount();//获取需要发送的ByteBuffer数组个数
            long expectedWrittenBytes = in.nioBufferSize();//获取需要发送的总字节数
            java.nio.channels.SocketChannel ch = this.javaChannel();//从NioSocketChannel中获取NIO的SocketChannel
            long writtenBytes = 0L;
            boolean done = false;//将是否发送完成标识设置为false
            boolean setOpWrite = false;//将是否有写半包标识设置为false

            int i;
            for(i = this.config().getWriteSpinCount() - 1; i >= 0; --i) {
			    //三个参数分别是需要发送的ByteBuffer数组,数组的偏移量,待发送的ByteChannel个数,返回值写入是SocketChannel的个数
                long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                if (localWrittenBytes == 0L) {//如果写入字节为0,说明TCP发送缓冲区已满,此时很有可能无法再写进去
				   //将写半包设置为true,并跳出循环,用于向多路注册复用器注册写操作,告诉多路复用器有没发完的半包消息
				   //需要轮询出就绪的SocketChannel继续发送
                    setOpWrite = true;
                    break;
                }
				//发送完成后进行两个计算:
				//需要发送的字节数减去已经发送的字节数
				//发送的字节数 + 已经发送的字节数
                expectedWrittenBytes -= localWrittenBytes;
                writtenBytes += localWrittenBytes;
				//判断缓冲区中所有的消息是否已经发送完成
				//如果是,则把发送标识设置为true同时跳出循环
				//如果没有发送完成,则继续循环
                if (expectedWrittenBytes == 0L) {
                    done = true;
                    break;
                }
            }
			//从循环发送中退出之后,首先对发送完成标识done进行判断
			//如果发送成功,则循环释放已经发送的消息
            if (done) {
                for(i = msgCount; i > 0; --i) {
                    in.remove();
                }

                if (!in.isEmpty()) {
                    continue;
                }
		        //环形数组的发送缓冲区释放完成后,取消半包标识,告诉多路复用器已经全部发送完成
                this.clearOpWrite();
            } else {//缓冲区的消息没有发送完成,出现了"写半包"
                for(i = msgCount; i > 0; --i) {//循环遍历发送缓冲区,对消息的发送结果进行判断
                    ByteBuf buf = (ByteBuf)in.current();//从ChannelOutboundBuffer弹出第一条发送的ByteBuffer,获取其索引和字节数
                    int readerIndex = buf.readerIndex();//可读的字节数,这是不变的
                    int readableBytes = buf.writerIndex() - readerIndex;//发送的字节数,这是随着发送会增加的
                    if ((long)readableBytes >= writtenBytes) {
					    //如果可读的消息大于已经发送的总字节数,说明这条消息没有被完整地发送出去,出现了"写半包"
						//需要更新可读的索引为当前索引加上已经发送的总字节数
                        if ((long)readableBytes > writtenBytes) {
                            buf.readerIndex(readerIndex + (int)writtenBytes);
                            in.progress(writtenBytes);
                        } else {如果可读的消息等于已经发送的总字节数,说明最后一次发送的消息是个整包消息,没有剩余的半包消息要发送
                            in.progress((long)readableBytes);
                            in.remove();
                        }
                        break;
                    }
					//如果可读的消息小于已经发送的总字节数,说明当前的ByteBuf已经被完全发送出去了,将已经发送的ByteBuf删除,最后,发送的字节数要减去第一条发送的字节数,得到后续消息发送的总字节数,然后继续循环判断第二条消息...
                    in.progress((long)readableBytes);
                    in.remove();
                    writtenBytes -= (long)readableBytes;
                }

                this.incompleteWrite(setOpWrite);
            }

            return;
        }
    }
ChannelPipeline和ChannelHandler

Netty的ChannelPipeline和ChannelHandler机制类似于Servlet和Filter过滤器,这类拦截器实际上是职责链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的定制。
Netty将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有IO事件拦截器ChannelHandler的链表,由ChannelHandler对IO事件进行拦截和处理。

ChannelPipeline的事件处理
  1. 底层的SocketChannel read()方法读取ByteBuf,触发ChannelRead事件,由IO线程NioEventLoop调用ChannelPipeline的fireChannelRead(Object msg)方法,将消息(ByteBuf)传输到ChannelPipeline中
  2. 消息依次被HeadHandler、ChannelHandler…拦截和处理,任何ChannelHandler都可以中断当前的流程,结束消息的传递
  3. 调用ChannelHandlerContext的write方法发送消息,消息从TailHandler开始,途径…Handler,最终被添加到消息发送缓冲区

Netty中的事件分为inbound事件和outbound事件。inbound事件通常由IO线程触发,例如TCP链路建立事件、链路关闭事件、读事件、异常通知事件等;outbound事件通常是由用户主动发起的网络IO操作等。

构建Pipeline

使用ServerBootstrap或者Bootstrap启动服务端或者客户端时,Netty会为每个Channel连接创建一个独立的Pipeline

ChannelPipeline的主要特性
  • ChannelPipeline支持运行态动态地添加或者删除ChannelHandler
  • ChanelPipeline是线程安全的,这意味着N个业务线程可以并发地操作ChannelPipeline而不存在多线程并发问题。但是,ChannelHandler却不是线程安全的。
ChannelHandler

ChannelHander类似Servlet的Filter过滤器,负责对IO事件或者IO操作进行拦截和处理。

ChannelHandlerAdapter

Netty提供接口ChannelHandlerAdapter基类,它的所有接口实现都是透传,如果用户ChannelHandler关系某个事件,只需要覆盖ChannelHandlerAdapter对应的方法即可。

ByteToMessageDecoder

利用NIO进行网络编程时,往往需要将读取到的字节数或者字节缓冲区解码为业务可以使用的POJO对象。Netty提供了ByteToMessageDecoder抽象工具解码类。用户的解码器继承ByteToMessageDecoder,只需要实现decode()方法,即可完成ByteBuf到POJO对象的解码。
不过ByteToMessageDecoder没有考虑TCP粘包和组包等场景,读半包需要用户自己处理,因此我们可以继承更高级的解码器进行半包处理。

MessageToMessageDecoder

MessageToMessageDecoder实际上是Nety的二次解码器,从SocketChannel读取到的TCP数据报是ByteBuffer,先将解码为Java对象,再二次解码为POJO对象,因此称之为二次解码器。
以HTTP+XML协议栈为例,第一次解码是将字节数组解码成HttpRequest对象,然后对HttpRequest消息中的消息体字符串进行二次解码,将XML格式的字符串解码为POJO对象。
由于二次解码器是将一个POJO解码为另一个POJO,一般不涉及半包处理。

MessageToByteEncoder

MessageToByteEncoder负责将POJO对象编码成ByteBuf

MessageToMessageEncoder

将一个POJO对象编码成另一个对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2023-07-14 15:19:01.215 WARN 7308 --- [sson-netty-2-15] io.netty.util.concurrent.DefaultPromise : An exception was thrown by org.redisson.misc.RedissonPromise$$Lambda$888/0x00000008008f7440.operationComplete() java.lang.NullPointerException: null 2023-07-14 15:19:01.216 ERROR 7308 --- [sson-netty-2-15] o.r.c.SentinelConnectionManager : Can't execute SENTINEL commands on /172.24.107.11:26379 org.redisson.client.RedisException: ERR No such master with that name. channel: [id: 0x2d66827d, L:/172.23.9.103:46812 - R:/172.24.107.11:26379] command: (SENTINEL SLAVES), params: [mymaster] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:365) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:196) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:134) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:104) ~[redisson-3.13.3.jar:3.13.3] at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na] 解决方法
07-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值