io.netty学习(三)Channel 概述

这篇具有很好参考价值的文章主要介绍了io.netty学习(三)Channel 概述。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

正文

Channel概述

Channel 特点

Channel 接口方法

ChannelOutboundInvoker

AttributeMap

总结


前言

前两篇文章我们已经对Netty进行了简单的了解和架构设计原理的剖析。

本篇文章我们就来开始对Netty源码的分析,首先我们来讲解 Netty 中Channel相关的功能和接口。

io.netty学习使用汇总

正文

Channel概述

Channel 顾名思义就是 管道,代表网络 Socket 或能够进行 I/O 操作的组件的关系。这些 I/O 操作包括读、写、连接和绑定。

简单的说,Channel 就是代表连接,实体之间的连接,程序之间的连接,文件之间的连接,设备之间的连接。同时它也是数据入站和出站的载体。

Netty 中的 Channel 为用户提供了如下功能:

  • 查询当前 Channel 的状态。例如,是打开还是已连接状态等。

  • 提供 Channel 的参数配置。例如,接收缓冲区大小。

  • 提供支持的 I/O 操作。例如,读、写、连接和绑定。

  • 提供 ChannelPipeline。ChannelPipeline 用户处理所有与 Channel 关联的 I/O 事件和请求。

Channel 特点

I/O 操作都是异步的

Channel中的所有 I/O 操作都是异步的,一经调用就马上返回,而不保证所请求的 I/O 操作在调用结束时已完成。相反,在调用时都将返回一个 ChannelFuture实例,用来代表未来的结果。该实例会在 I/O 操作真正完成后通知用户,然后就可以得到具体的 I/O操作的结果。

Channel 是分层的

一个 Channel会有一个对应的parent,该parent也是一个Channel。并且根据 Channel创建的不同,它的parent也会不一样。例如,在一个SocketChannel连接上ServerSocketChannel之后,该SocketChannelparent就会是该ServerSocketChannel。层次的结构的语义取决于Channel使用了何种传输实现方式。

向下转型以下访问特定于输出的操作

某些传输公开了特定于该传输的相关操作,因此可以将该Channel向下转换为子类型以调用此类操作。

释放资源

一旦Channel完成,调用ChannelOutboundInvoker.close()ChannelOutboundInvoker.close(ChannelPromise)来释放所有资源是非常重要的。这样可以确保以适当的方式(以文件句柄)释放所有的资源。

Channel 接口方法

以下是Channel接口的核心源码:

public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {

    //返回全局唯一的channel id
    ChannelId id();

    //返回该通道注册的事件轮询器
    EventLoop eventLoop();
    
    //返回该通道的父通道,如果是ServerSocketChannel实例则返回null,
    //SocketChannel实例则返回对应的ServerSocketChannel
    Channel parent();

    //返回该通道的配置参数
    ChannelConfig config();

    //端口是否处于open,通道默认一创建isOpen方法就会返回true,close方法被调用后该方法返回false
    boolean isOpen();

    //是否已注册到EventLoop
    boolean isRegistered();

    // 通道是否处于激活
    boolean isActive();

    // 返回channel的元数据
    ChannelMetadata metadata();

    // 服务器的ip地址
    SocketAddress localAddress();

    // remoteAddress 客户端的ip地址
    SocketAddress remoteAddress();

    //通道的关闭凭证(许可),这里是多线程编程一种典型的设计模式,一个channle返回一个固定的
    ChannelFuture closeFuture();

    //是否可写,如果通道的写缓冲区未满,即返回true,表示写操作可以立即 操作缓冲区,然后返回。
    boolean isWritable();

    long bytesBeforeUnwritable();

    long bytesBeforeWritable();

    Channel.Unsafe unsafe();

    // 返回管道
    ChannelPipeline pipeline();
       
    // 返回ByteBuf内存分配器
    ByteBufAllocator alloc();

    Channel read();

    Channel flush();

    public interface Unsafe {
        Handle recvBufAllocHandle();

        SocketAddress localAddress();

        SocketAddress remoteAddress();

        // 把channel注册进EventLoop
        void register(EventLoop var1, ChannelPromise var2);

        // 给channel绑定一个 adress,
        void bind(SocketAddress var1, ChannelPromise var2);

        // Netty客户端连接到服务端
        void connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);

        // 断开连接,但不会释放资源,该通道还可以再通过connect重新与服务器建立连接
        void disconnect(ChannelPromise var1);

        // 关闭通道,回收资源,该通道的生命周期完全结束
        void close(ChannelPromise var1);

        void closeForcibly();
        
        // 取消注册。
        void deregister(ChannelPromise var1);

        // 从channel中读取IO数据
        void beginRead();
        
        // 往channe写入数据
        void write(Object var1, ChannelPromise var2);

        void flush();

        ChannelPromise voidPromise();

        ChannelOutboundBuffer outboundBuffer();
    }
}

从上述源码可以看到Channel接口继承了AttributeMapChannelOutboundInvokerComparable<Channel>外,还提供了很多接口。

ChannelOutboundInvoker

ChannelOutboundInvoker接口声明了所有的出站的网络操作。

以下是ChannelOutboundInvoker接口的核心源码:

public interface ChannelOutboundInvoker {
    ChannelFuture bind(SocketAddress var1);

    ChannelFuture connect(SocketAddress var1);

    ChannelFuture connect(SocketAddress var1, SocketAddress var2);

    ChannelFuture disconnect();

    ChannelFuture close();

    ChannelFuture deregister();

    ChannelFuture bind(SocketAddress var1, ChannelPromise var2);

    ChannelFuture connect(SocketAddress var1, ChannelPromise var2);

    ChannelFuture connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);

    ChannelFuture disconnect(ChannelPromise var1);

    ChannelFuture close(ChannelPromise var1);

    ChannelFuture deregister(ChannelPromise var1);

    ChannelOutboundInvoker read();

    ChannelFuture write(Object var1);

    ChannelFuture write(Object var1, ChannelPromise var2);

    ChannelOutboundInvoker flush();

    ChannelFuture writeAndFlush(Object var1, ChannelPromise var2);

    ChannelFuture writeAndFlush(Object var1);

    ChannelPromise newPromise();

    ChannelProgressivePromise newProgressivePromise();

    ChannelFuture newSucceededFuture();

    ChannelFuture newFailedFuture(Throwable var1);

    ChannelPromise voidPromise();
}

从上述源码可以看到ChannelOutboundInvoker接口中大部分方法的返回值都是ChannelFutureChannelPromise。因此,ChannelOutboundInvoker接口的操作都是异步的。

ChannelFutureChannelPromise的差异在于,ChannelFuture用于获取异步的结果,而ChannelPromise则是对ChannelFuture进行扩展,支持写的操作。因此,ChannelPromise也被称为可写的ChannelFuture

AttributeMap

Channel接口继承了AttributeMap接口,那么AttributeMap接口的作用是什么呢?

以下是AttributeMap接口的核心源码:

public interface AttributeMap {
    <T> Attribute<T> attr(AttributeKey<T> var1);

    <T> boolean hasAttr(AttributeKey<T> var1);
}

从源码可以看到,AttributeMap其实就是类似于 Map 的键值对,而键就是AttributeKey类型,值就是Attribute类型。换言之,AttributeMap是用来存放属性的。

我们知道每一个ChannelHandlerContext都是ChannelHandlerChannelPipeline之间连接的桥梁,每一个ChannelHandlerContext都有属于自己的上下文,也就说每一个ChannelHandlerContext上如果有AttributeMap都是绑定上下文的,也就说如果A的ChannelHandlerContext中的AttributeMap,是无法被B的ChannelHandlerContext读取到的。

Netty 提供了AttributeMap的默认实现类DefaultAttributeMap。与 JDK 中 ConcurrentHashMap相比,在高并发下DefaultAttributeMap可以更加节省内存。

DefaultAttributeMap的核心源码如下:

public class DefaultAttributeMap implements AttributeMap {

        //以原子方式更新attributes变量的引用
        private static final AtomicReferenceFieldUpdater<DefaultAttributeMap, AtomicReferenceArray> updater = 		AtomicReferenceFieldUpdater.newUpdater(DefaultAttributeMap.class, AtomicReferenceArray.class, "attributes");

        //默认桶的大小
        private static final int BUCKET_SIZE = 4;

        //掩码 桶大小 3
        private static final int MASK = 3;

        //延迟初始化,节约内存
        private volatile AtomicReferenceArray<DefaultAttributeMap.DefaultAttribute<?>> attributes;
 
    
        public <T> Attribute<T> attr(AttributeKey<T> key) {
        ObjectUtil.checkNotNull(key, "key");
        AtomicReferenceArray<DefaultAttributeMap.DefaultAttribute<?>> attributes = this.attributes;
        if (attributes == null) {
            //当attributes为空时则创建它,默认数组长度4
            attributes = new AtomicReferenceArray(4);
            
            //原子方式更新attributes,如果attributes为null则把新创建的对象赋值
			//原子方式更新解决了并发赋值问题
            if (!updater.compareAndSet(this, (Object)null, attributes)) {
                attributes = this.attributes;
            }
        }

        //根据key计算下标
        int i = index(key);
        DefaultAttributeMap.DefaultAttribute<?> head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);
        if (head == null) {
            head = new DefaultAttributeMap.DefaultAttribute();
            DefaultAttributeMap.DefaultAttribute<T> attr = new DefaultAttributeMap.DefaultAttribute(head, key);
            head.next = attr;
            attr.prev = head;
            if (attributes.compareAndSet(i, (Object)null, head)) {
                return attr;
            }	
            
			//返回attributes数组中的第一个元素
            head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);
        }
		
        //对head同步加锁
        synchronized(head) {
            //curr 赋值为head    head为链表结构的第一个变量
            DefaultAttributeMap.DefaultAttribute curr = head;

            while(true) {
                //依次获取next
                DefaultAttributeMap.DefaultAttribute<?> next = curr.next;
                //next==null,说明curr为最后一个元素
                if (next == null) {
                    //创建一个新对象传入head和key
                    DefaultAttributeMap.DefaultAttribute<T> attr = new DefaultAttributeMap.DefaultAttribute(head, key);
                    //curr后面指向attr
                    curr.next = attr;
                    //attr的前面指向curr
                    attr.prev = curr;
                    //返回新对象
                    return attr;
                }
				//如果next的key等于传入的key,并且没有被移除
                if (next.key == key && !next.removed) {
                    //这直接返回next
                    return next;
                }
				//否则把curr变量指向下一个元素
                curr = next;
            }
        }
    }
    
        public <T> boolean hasAttr(AttributeKey<T> key) {
        ObjectUtil.checkNotNull(key, "key");
        //attributes为null直接返回false
        AtomicReferenceArray<DefaultAttributeMap.DefaultAttribute<?>> attributes = this.attributes;
        if (attributes == null) {
            return false;
        } else {
            //计算数组下标
            int i = index(key);
            //获取头 为空直接返回false
            DefaultAttributeMap.DefaultAttribute<?> head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);
            if (head == null) {
                return false;
            } else {
                //对head同步加锁
                synchronized(head) {
                    //从head的下一个开始,因为head不存储元素
                    for(DefaultAttributeMap.DefaultAttribute curr = head.next; curr != null; curr = curr.next) {
                        if (curr.key == key && !curr.removed) {
                            return true;
                        }
                    }

                    return false;
                }
            }
        }
    }
   
    private static int index(AttributeKey<?> key) {
		//与掩码&运算,数值肯定<=mask 正好是数组下标
        return key.id() & 3;
    }
}

从上述源码可以看出,DefaultAttributeMap使用了AtomicReferenceArraysynchronized等来保证并发的安全。

AtomicReferenceArray类提供了可以原子读取和写入的底层引用数组的操作,并且还包含高级原子操作。AtomicReferenceArray支持对底层引用数组变量的原子操作。它具有获取和设置的方法,如在变量上的读取和写入。compareAndSet()方法用于判断当前值是否等于预期值,如果是,则以原子方式将位置i的元素设置为给定的更新值。

总结

看完以上关于Channel的介绍,相信你对Channel应该有了一定的认识,后面我们继续分析Channel相关的接口和源码。文章来源地址https://www.toymoban.com/news/detail-492237.html

到了这里,关于io.netty学习(三)Channel 概述的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Netty源码学习3——Channel ,ChannelHandler,ChannelPipeline

    系列文章目录和关于我 在Netty源码学习2——NioEventLoop的执行中,我们学习了NioEventLoop是如何进行事件循环以及如何修复NIO 空轮询的bug的,但是没有深入了解IO事件在netty中是如何被处理的,下面我们以服务端demo代码为例子,看下和IO事件处理密切的Channel 如上在编写netty 服务

    2024年02月11日
    浏览(36)
  • FPGA学习实践之旅——前言及目录

    很早就有在博客中记录技术细节,分享一些自己体会的想法,拖着拖着也就到了现在。毕业至今已经半年有余,随着项目越来越深入,感觉可以慢慢进行总结工作了。趁着2024伊始,就先开个头吧,这篇博客暂时作为汇总篇,记录在这几个月以及之后从FPGA初学者到也算有一定

    2024年02月03日
    浏览(55)
  • 学习Opencv(蝴蝶书/C++)——1. 前言 和 第1章.概述

    注,整体学习过程参考的内容: 从零学习 OpenCV4 2022年唐宇迪新全【OpenCV入门到实战】课程分享!原来学习OpenCV可以这么简单,超级通俗易懂!(附配套学习资料)-人工智能图像处理计算机视觉 《OpenCV轻松入门面向python》 细致理解 OpenCV opencv的全名:Open Source Computer Vision

    2024年02月03日
    浏览(49)
  • 【C++初阶(一)】学习前言 命名空间与IO流

    本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C++ 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 C++是基于C语言而产生的,它既可以进行C语言的

    2024年02月08日
    浏览(95)
  • 设置word目录从正文开始记录页码,并解决word目录正常,但正文页脚处只显示第一页的页码

    问题详情1:如何设置目录从正文开始记录页码 问题详情2:word目录处的页码正常,但正文只有第一页的页脚处显示页码 在设置目录从正文开始记录页码时需在目录后插入分节符(相同的节可以把当前页的页脚链接到上一页,但是不同的节就不行,因此可以利用分节来从 正文

    2024年03月10日
    浏览(115)
  • io.netty学习(二)Netty 架构设计

    目录 前言 Selector 模型 SelectableChannel Channel 注册到 Selector SelectionKey 遍历 SelectionKey 事件驱动 Channel 回调 Future 事件及处理器 责任链模式 责任链模式的优缺点 ChannelPipeline 将事件传递给下一个处理器 总结 上一篇文章,我们对  Netty 做了一个基本的概述,知道什么是 Netty 以及

    2024年02月10日
    浏览(45)
  • Netty Channel 详解

    优质博文:IT-BLOG-CN 【1】创建服务端 Channel ; 【2】初始化服务端 Channel ; 【3】注册 Selector ; 【4】端口绑定:我们分析源码的入口从端口绑定开始, ServerBootstrap 的 bind(int inetPort) 方法,实际上是 AbstractBootstrap 的 bind(int inetPort) 方法。 ServerBootstrap 继承了 AbstractBootstrap 。 【

    2024年02月02日
    浏览(32)
  • io.netty学习(四)ChannelHandler

    目录 前言 正文 ChannelHandler ChannelInboundHandler ChannelOutboundHandler ChannelDuplexHandler 总结 先简略了解一下 ChannelPipeline 和 ChannelHandler 的概念。 想象一个流水线车间。当组件从流水线头部进入,穿越流水线,流水线上的工人按顺序对组件进行加工,到达流水线尾部时商品组装完成。

    2024年02月11日
    浏览(36)
  • 1、前言 2、概述

    了解Git基本概念 能够概述git工作流程 能够使用Git常用命令 熟悉Git代码托管服务 能够使用 Visual Studio/Rider/VSCode 操作git linux 基本命令 编程入门基础 简单的docker 基础(会安装容器即可) 在校大学生 初入社会的开发人员     Git是目前世界上最先进的分布式版本控制系统(没有之

    2024年02月04日
    浏览(46)
  • 文件IO,目录IO的学习

    用法:#include“head.h”    -     在当前目录下寻找头文件 主函数的传参中,argc是传参的个数  ,const char *argv[]是一个指针数组,存放的指针类型数据 argv【n】,n=1/2/3 分别代表三个指针参数 标准示例:     off_t lseek(int fd, off_t offset, int whence);        功能:            

    2024年02月20日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包