驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
Netty小技巧:通道上下文
/  

Netty小技巧:通道上下文

开篇

当 Netty 用作长连接的时候,比如websocket的时候,如果有一个类似ThreadLocal的功能,能够将相关参数绑定到这个Channel上的时候,是不是会省下很多功夫?

最初遇到类似的问题,我是自己在那里维护一个Channelid和对应值的一个Map,然后在这个通道关闭的时候,移除相关映射,这种办法肯定是可以的,不过该方法需要自己维护 Map,并监听通道关闭事件,不够简单和方便,经过学习这里介绍一种Netty 原生自带的且更加简单的方法。

代码

//1、定义一个 属性 Key,其中 String 表示该 key 映射的是什么类型的值
val ATTR_KEY: AttributeKey<String> = AttributeKey.valueOf("mt-user-info")
//2.赋值操作
ctx.channel().attr(ATTR_KEY).set("ok")
//3.取值操作
val msg = ctx.channel().attr(ATTR_KEY).get()

核心代码就这 3 句,这样就可以随时随地的从channel上线文获取到对应的值,这个和ThreadLocal有点类似吧。

同时当channel关闭的时候,netty会自动帮我们销毁对应的映射,我们不需要担心这点。

源码

最后我们来简单看看netty对于的实现,我本来以为它内部维护的是一个Map,这个想法其实不够正确,我们看下面的代码:

 private volatile AtomicReferenceArray<DefaultAttribute<?>> attributes;   
 public <T> Attribute<T> attr(AttributeKey<T> key) {
        ObjectUtil.checkNotNull(key, "key");
        AtomicReferenceArray<DefaultAttribute<?>> attributes = this.attributes;
        if (attributes == null) {
            // Not using ConcurrentHashMap due to high memory consumption.
            attributes = new AtomicReferenceArray<DefaultAttribute<?>>(BUCKET_SIZE);

            if (!updater.compareAndSet(this, null, attributes)) {
                attributes = this.attributes;
            }
        }

        int i = index(key);
        DefaultAttribute<?> head = attributes.get(i);
   	//为空的时候,表示要添加头
        if (head == null) {
            head = new DefaultAttribute();
            DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
            head.next = attr;
            attr.prev = head;
            if (attributes.compareAndSet(i, null, head)) {
                // we were able to add it so return the attr right away
                return attr;
            } else {
                head = attributes.get(i);
            }
        }
				
   			//不为空的时候,构建链表结构
        synchronized (head) {
            DefaultAttribute<?> curr = head;
            for (;;) {
                DefaultAttribute<?> next = curr.next;
                if (next == null) {
                    DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
                    curr.next = attr;
                    attr.prev = curr;
                    return attr;
                }

                if (next.key == key && !next.removed) {
                    return (Attribute<T>) next;
                }
                curr = next;
            }
        }
    }
  • 重点注释看文中的 2 处注释。
  • 核心参数attributes这个类型是AtomicReferenceArray,你可以简单理解为这个是一个并发安全的数组
  • DefaultAttribute是什么样子的了?
    private static final class DefaultAttribute<T> extends AtomicReference<T> implements Attribute<T> {

        private static final long serialVersionUID = -2661411462200283011L;

        // The head of the linked-list this attribute belongs to
        private final DefaultAttribute<?> head;
        private final AttributeKey<T> key;

        // Double-linked list to prev and next node to allow fast removal
        private DefaultAttribute<?> prev;
        private DefaultAttribute<?> next;

        // Will be set to true one the attribute is removed via getAndRemove() or remove()
        private volatile boolean removed;

        DefaultAttribute(DefaultAttribute<?> head, AttributeKey<T> key) {
            this.head = head;
            this.key = key;
        }
    }
  • prevnext这不是就是链表

所以说本质来讲,netty内部也是维护了自己的一套数组和链表的结构,这个和map本质上是类似的。

不同之处在于netty可能觉得你的参数不会特别多,所以没有搞链表过长的时候,转为红黑树这样的复杂结构。

至于为什么不用 本来平台就有的Map了,我个人觉得可能是netty为了不依赖某个版本的 JDK,毕竟JDK7JDK8Map内部实现上是有差异的!

积土成山,风雨兴焉。积水成渊,蛟龙生焉。