添加 DWORD 注册表项，值为 1\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters\AllowInsecureGuestAuth
在调试数据库驱动时，我们偶尔需要对数据的时序进行分析，这就需要使用 wireshark 进行抓包。同时，我们还要防止本地开发环境的复杂性影响测试结果，我们需要在更简单的服务器上运行测试。为了实现这些测试，我们就需要找到对服务端抓包的方法。目前最主流的方案是使用 rpcapd 将服务端的抓包结果传输到本地，大部分文章对如何使用 rpcapd 抓包有较为详细的描述，本篇则不再重复，而是将重点放在其过时或错误的部分上——即 rpcapd 的安装。在 github 上有 rpcapd 项目。根据其描述，libpcap 自 v1.9.0 开始自带 rpcapd，但是需要在 configure 过程中添加 --enable-remote参数。经测试，在 Ubuntu 22.04 源中自带的 libpcap-dev 和 libpcap0.8 都没有 rpcapd 命令，而 Ubuntu 18.04 源的 libpcap 版本则直接是 v1.8.0。很明显，我们在 Ubuntu 上需要自己编译 rpcapd。编译 rpcapd 的步骤为：克隆 libpcap 项目执行autogen.sh脚本来生成 configure 脚本运行 configure 脚本，./configure --enable-remotemake在 configure 过程中，可能会提示缺少依赖。根据需求安装对应依赖即可。如果 make 的过程不出意外，那么 rpcapd 的可执行程序应该就放在 rpcapd 目录下了。我们只需要执行sudo rpcapd/rpcapd -4 -n就可以启动 rpcapd 服务了。默认监听的端口为 2002。
https://github.com/go-gitea/gitea/issues/374#issuecomment-423688581pip 安装 docutils修改app.ini[markup.restructuredtext] ENABLED = true FILE_EXTENSIONS = .rst RENDER_COMMAND = rst2html.py IS_INPUT_FILE = false修改模板文件。rst2html.py --help 中 --template 参数有写默认位置，或者用此参数自定义 template 位置。模板文件内容为：%(body_pre_docinfo)s %(docinfo)s %(body)s
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.*; /** * a linked list which based on array but not node object * * @param <E> contains element */ public class RowLinkedList<E> extends AbstractList<E> implements Deque<E>, Cloneable, java.io.Serializable { @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) private @interface UnsafeMethod { String reason() default ""; } @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) private @interface SafeMethod { } private interface NodeSetter { void set(@NotNull RowLinkedList<?> list, int node); } private static final int NULL_NODE = -1; private static final NodeSetter headSetter = (list, node) -> list.head = node; private static final NodeSetter tailSetter = (list, node) -> list.tail = node; private int[] nextArray; private int[] prevArray; private Object[] elements; private int head = NULL_NODE; private int tail = NULL_NODE; private int empty = NULL_NODE; private int used = 0; private int size = 0; @Override public void addFirst(@Nullable E element) { resize(); int node = insertNode(element, head, nextArray, prevArray); if (head == NULL_NODE) { tail = node; } head = node; } @Override public void addLast(@Nullable E element) { resize(); int node = insertNode(element, tail, prevArray, nextArray); if (tail == NULL_NODE) { head = node; } tail = node; } @Override public boolean offerFirst(E e) { addFirst(e); return true; } @Override public boolean offerLast(E e) { addLast(e); return true; } @Override public E removeFirst() { int node = removeFirstNode(); if (node == NULL_NODE) { throw new NoSuchElementException(); } else { return popElement(node); } } @UnsafeMethod private int insertNode(@Nullable E element, int tail, @NotNull int[] prevArray, @NotNull int[] nextArray) { int node = alloc(); elements[node] = element; nextArray[node] = NULL_NODE; if (tail != NULL_NODE) { nextArray[tail] = node; } prevArray[node] = tail; nextArray[node] = NULL_NODE; size++; return node; } @Override public E removeLast() { int node = removeLastNode(); if (node == NULL_NODE) { throw new NoSuchElementException(); } else { return popElement(node); } } @Override public E pollFirst() { int node = removeFirstNode(); if (node == NULL_NODE) { return null; } else { return popElement(node); } } @Override public E pollLast() { int node = removeLastNode(); if (node == NULL_NODE) { return null; } else { return popElement(node); } } @Override public E getFirst() { if (head == NULL_NODE) { throw new NoSuchElementException(); } else { //noinspection unchecked return (E) elements[head]; } } @Override public E getLast() { if (tail == NULL_NODE) { throw new NoSuchElementException(); } else { //noinspection unchecked return (E) elements[tail]; } } @Override public E peekFirst() { if (head == NULL_NODE) { return null; } else { //noinspection unchecked return (E) elements[head]; } } @Override public E peekLast() { if (tail == NULL_NODE) { return null; } else { //noinspection unchecked return (E) elements[tail]; } } @Override public boolean removeFirstOccurrence(Object o) { for (int node = head; node != NULL_NODE; node = nextArray[node]) { if (Objects.equals(elements[node], o)) { removeNode(node); elements[node] = null; return true; } } return false; } @Override public boolean removeLastOccurrence(Object o) { for (int node = tail; node != NULL_NODE; node = prevArray[node]) { if (Objects.equals(elements[node], o)) { removeNode(node); elements[node] = null; return true; } } return false; } @Override public int size() { return size; } @NotNull @Override public Iterator<E> descendingIterator() { return new DesItr<>(listIterator(size)); } @Override public Iterator<E> iterator() { return listIterator(); } @Override public ListIterator<E> listIterator(int index) { return new ListItr(index); } @Override public boolean add(E e) { return offerLast(e); } @Override public void add(int index, E element) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } int node = node(index); linkBefore(node, element); } @Override public E set(int index, E element) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } int node = node(index); Object old = elements[node]; elements[node] = element; //noinspection unchecked return (E) old; } @Override public E get(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } //noinspection unchecked return (E) elements[node(index)]; } @Override public E remove(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } int node = node(index); removeNode(node); return popElement(node); } @Override public boolean offer(E e) { return offerLast(e); } @Override public E remove() { return removeFirst(); } @Override public E poll() { return pollFirst(); } @Override public E element() { return getFirst(); } @Override public E peek() { return peekFirst(); } @Override public void push(E e) { addFirst(e); } @Override public E pop() { return removeFirst(); } @Override public boolean remove(Object o) { return removeFirstOccurrence(o); } @Override public void clear() { for (int i = 0; i < used; i++) { elements[i] = null; } head = tail = empty = NULL_NODE; size = used = 0; } @Override protected Object clone() throws CloneNotSupportedException { int size = 16; while (size < this.size) { size <<= 1; } //noinspection unchecked RowLinkedList<E> clone = (RowLinkedList<E>) super.clone(); clone.elements = new Object[size]; clone.prevArray = new int[size]; clone.nextArray = new int[size]; int node = head; for (int i = 0; i < this.size; i++) { clone.prevArray[i] = i - 1; clone.nextArray[i] = i + 1; clone.elements[i] = elements[node]; node = nextArray[node]; } clone.used = clone.size = this.size; if (this.size == 0) { clone.head = clone.tail = NULL_NODE; } else { clone.head = 0; clone.tail = this.size - 1; clone.nextArray[size - 1] = -1; } return clone; } @UnsafeMethod(reason = "node may be out of index") private void linkBefore(int node, E element) { // TODO test if (node == NULL_NODE) { addLast(element); return; } int newNode = alloc(element, prevArray[node], nextArray[node]); if (prevArray[node] == NULL_NODE) { head = newNode; } else { nextArray[prevArray[node]] = newNode; } prevArray[node] = newNode; } /** * @param node element node index * @return removed element */ @UnsafeMethod(reason = "uncheck node") private E popElement(int node) { Object element = elements[node]; elements[node] = null; //noinspection unchecked return (E) element; } /** * @return removed last node index, may be NULL_NODE */ @SafeMethod private int removeFirstNode() { return removeLastNode(head, nextArray, prevArray, tailSetter, headSetter); } @SafeMethod private int removeLastNode() { return removeLastNode(tail, prevArray, nextArray, headSetter, tailSetter); } @UnsafeMethod(reason = "array and setter must be careful") private int removeLastNode( int node, @NotNull int[] prevArray, @NotNull int[] nextArray, @NotNull NodeSetter headSetter, @NotNull NodeSetter tailSetter ) { // TODO test if (node == NULL_NODE) { return -1; } tailSetter.set(this, prevArray[node]); if (prevArray[node] == NULL_NODE) { headSetter.set(this, NULL_NODE); } else { nextArray[prevArray[node]] = NULL_NODE; } pushEmpty(node); size--; gc(); return node; } @UnsafeMethod(reason = "not removed element") private void removeNode(int node) { // TODO test if (node == NULL_NODE) { return; } // node.prev == null if (prevArray[node] == NULL_NODE) { // head = null head = NULL_NODE; } else { // node.prev.next = node.next nextArray[prevArray[node]] = nextArray[node]; } // node.next == null if (nextArray[node] == NULL_NODE) { // tail = null tail = NULL_NODE; } else { // node.next.prev = node.prev prevArray[nextArray[node]] = prevArray[node]; } pushEmpty(node); size--; gc(); } /** * push freed node to empty stack * * @param node freed node index */ @UnsafeMethod(reason = "node may be NULL_NODE") private void pushEmpty(int node) { nextArray[node] = empty; empty = node; } /** * unchecked method of [this.empty] * get a free node from empty stack * * @return node index pop from empty stack */ @UnsafeMethod(reason = "this.empty may be NULL_NODE") private int popEmpty() { int empty = this.empty; this.empty = nextArray[empty]; return empty; } /** * checked method * alloc free node from unused space or empty stack * * @return allocated node index */ @SafeMethod private int alloc() { if (empty == NULL_NODE) { resize(); return used++; } else { return popEmpty(); } } @SafeMethod private int alloc(E element, int prev, int next) { int node = alloc(); elements[node] = element; prevArray[node] = prev; nextArray[node] = next; return node; } @UnsafeMethod(reason = "unlimited increase memory usage") private void resize() { if (nextArray != null && used < nextArray.length) { return; } int newSize; if (nextArray == null) { prevArray = new int[16]; nextArray = new int[16]; elements = new Object[16]; return; } else if (nextArray.length < 16) { newSize = 16; } else { newSize = nextArray.length * 2; } prevArray = Arrays.copyOf(prevArray, newSize); nextArray = Arrays.copyOf(nextArray, newSize); elements = Arrays.copyOf(elements, newSize); } @SafeMethod private void gc() { if (size == 0) { empty = NULL_NODE; used = 0; return; } if (nextArray.length <= 16 || size >= used / 8) { return; } Object[] newElements = new Object[nextArray.length / 4]; int[] newPrev = new int[nextArray.length / 4]; int[] newNext = new int[nextArray.length / 4]; int node = this.head; for (int i = 0; node >= 0; i++) { newPrev[i] = i - 1; newNext[i] = i + 1; newElements[size - i - 1] = elements[node]; node = nextArray[node]; } newNext[size - 1] = -1; prevArray = newPrev; nextArray = newNext; elements = newElements; empty = -1; used = size; this.tail = used - 1; head = 0; } private int node(int index) { if (index < (size >> 1)) { int node = head; for (int i = 0; i < index; i++) node = nextArray[node]; return node; } else { int node = tail; for (int i = size - 1; i > index; i--) node = prevArray[node]; return node; } } private class ListItr implements ListIterator<E> { private int next; private int nextIndex; private int lastReturned = NULL_NODE; private ListItr(int index) { if (head == NULL_NODE || index == size) { next = NULL_NODE; return; } nextIndex = index; next = node(index); } @Override public boolean hasNext() { return next != NULL_NODE; } @Override public E next() { if (next == NULL_NODE) { throw new NoSuchElementException(); } lastReturned = next; next = nextArray[next]; //noinspection unchecked return (E) elements[lastReturned]; } @Override public boolean hasPrevious() { return nextIndex > 0; } @Override public E previous() { if (!hasPrevious()) { throw new NoSuchElementException(); } lastReturned = next = next == NULL_NODE ? tail : prevArray[next]; nextIndex--; //noinspection unchecked return (E) elements[lastReturned]; } @Override public int nextIndex() { return nextIndex; } @Override public int previousIndex() { return nextIndex - 1; } @Override public void remove() { if (lastReturned == NULL_NODE) throw new IllegalStateException(); if (next == lastReturned) { next = nextArray[lastReturned]; } else { nextIndex--; } removeNode(lastReturned); popElement(lastReturned); lastReturned = NULL_NODE; } @Override public void set(E e) { if (lastReturned == NULL_NODE) { throw new IllegalStateException(); } elements[lastReturned] = e; } @Override public void add(E e) { lastReturned = NULL_NODE; if (next == NULL_NODE) { addLast(e); } else { linkBefore(next, e); } nextIndex++; } } private static class DesItr<E> implements Iterator<E> { private final ListIterator<E> itr; private DesItr(ListIterator<E> itr) { this.itr = itr; } @Override public boolean hasNext() { return itr.hasPrevious(); } @Override public E next() { return itr.previous(); } } }
前言链表是一种由“节点”组成的数据结构，每个节点都储存着指向下一个节点的指针。通常来讲，指针都是存储的物理地址，因此在无法获取物理地址的语言里无法实现（比如BASIC）；不过，其实也有别的方法在这些语言里实现指针的功能——即使用数组，以对象在数组内的索引作为指针的值。传统的链表实现是定义节点对象的结构，用一个个独立的节点构建链表，不过这种做法在Java中空间效率并不是最高的。在64位Hotspot中，每个对象都有128位的对象头，里面包含了对象类型、对象锁、GC分代信息等。一个单向链表的节点对象最小需要两个引用，即16字节，加上对象头一共32字节，对象头的占比高达50%，这对内存而言是非常大的浪费。传统的关系型数据库是按行存储的，每条记录都存储在“行”中；后来发展出了列数据库，数据被存在“列”中。这种列式存储的思维可以帮助我们实现另一种形式的“列式”链表。基本思路以数组表达的列的形式储存节点的所有属性，我们实现的是一个单向链表，需要两个数组，分别是next数组和value数组。我们使用一个变量head表示列表头，head指向列表头，而被head指向的节点则指向它的下一个节点，以此类推。为了支持从队列尾添加数据，我们还需要一个tail变量用来缓存链表尾的索引。同时，我们还有一个缓存链表大小的size我们这个链表如果每次增删对象都要拷贝或者复制数组的话，运行效率会变得非常低（时间复杂度O(n)），因此我们在一个数组中实现了两个平行的链表，我将其命名为values链表（简称V）和empty链表（简称E），V用来存储数据，而E则用来暂存被删除的节点以待复用。从V中删除的节点都会同步加入E中，而当插入节点的时候也会优先取用E的节点。参考 ArrayList 的实现，为了防止频繁复制数组，我们在empty链表用尽，需要扩容的时候还会额外扩容一部分空间。由于前文定义的E链表与V链表的性质，我们可以推导出E与V的组合体储存在一片连续的空间中，我们用变量used来表示这一段内存的结尾。当我们需要新的节点时，我们直接取used所指向的节点即可。实现完备性证明push 函数push 函数把元素添加到链表头。数组容量校验过程略。push对象需要先获取一个节点，根据前面的定义，我们会优先从E链表中取节点，否则从used取节点。我们将这两种情况分开讨论。当E链表为空时当E链表不为空时未完待续源码import java.util.Arrays; public class RowLinkedStack<V> { private int[] nextArray; private Object[] values; private int head = -1; private int tail = -1; private int empty = -1; private int used = 0; private int size = 0; public void append(V obj) { if (nextArray == null || used >= nextArray.length) { resize(); } if (empty < 0) { if (tail < 0) { head = used; } else { nextArray[tail] = used; } nextArray[used] = -1; values[used] = obj; tail = used++; } else { int nextEmpty = nextArray[empty]; if (tail < 0) { head = empty; } else { nextArray[tail] = empty; } nextArray[empty] = -1; values[empty] = obj; tail = empty; empty = nextEmpty; } size++; } public void push(V obj) { if (nextArray == null || used >= nextArray.length) { resize(); } if (empty < 0) { nextArray[used] = head; values[used] = obj; head = used++; } else { int nextEmpty = nextArray[empty]; nextArray[empty] = head; values[empty] = obj; head = empty; empty = nextEmpty; } if (tail < 0) { tail = head; } size++; } public V pop() { if (head < 0) { return null; } V value = (V) values[head]; values[head] = null; int next = nextArray[head]; nextArray[head] = empty; empty = head; head = next; if (head < 0) { tail = -1; } size--; gc(); return value; } public V removeAt(int index) { if (index >= size) { throw new IndexOutOfBoundsException(); } if (index == 0) { return pop(); } int node = head; for (int i = 1; i < index; i++) { if (node == -1) { throw new IndexOutOfBoundsException(); } node = nextArray[node]; } int next = nextArray[node]; nextArray[node] = nextArray[next]; nextArray[next] = empty; empty = next; Object value = values[next]; values[next] = null; if (next == tail) { tail = node; } size--; gc(); return (V) value; } @Override public String toString() { return toString2(); } private String toString1() { return "RowLinkedStack(" + size + ")"; } private String toString2() { StringBuilder builder = new StringBuilder("["); int head = this.head; while (head >= 0) { if (builder.length() != 1) { builder.append(", "); } builder.append(values[head]); head = nextArray[head]; } builder.append("]"); return builder.toString(); } private void resize() { int newSize; if (nextArray == null) { nextArray = new int[16]; values = new Object[16]; return; } else if (nextArray.length < 16) { newSize = 16; } else { newSize = nextArray.length * 2; } nextArray = Arrays.copyOf(nextArray, newSize); values = Arrays.copyOf(values, newSize); } private void gc() { if (nextArray.length <= 16 || size >= used / 4) { return; } Object[] newValues = new Object[nextArray.length / 2]; int[] newNexts = new int[nextArray.length / 2]; int head = this.head; for (int i = 0; i < size; i++) { newNexts[i] = i - 1; newValues[size - i - 1] = values[head]; head = nextArray[head]; } nextArray = newNexts; values = newValues; empty = -1; used = size; this.head = used - 1; tail = this.head < 0 ? -1 : 0; } }
引言今天面试，面试官问了一道经典的问题：一个5L的杯子和一个3L的杯子，水无限，如何得到4L水？ 这个问题非常经典，经典到我下意识就能回忆起解法：将5L杯子装满水，然后倒满3L的杯子，此时5L杯子里有2L水将3L杯子清空将5L被子里剩余的2L水导倒入3L杯子中5L杯子取水，倒入3L杯，此时5L杯中就有4L水了我们可以用表格来表达每一步的状态：步骤操作3L杯内水的体积5L杯内水的体积15L杯取水0L5L25L杯倒入3L杯3L2L33L杯清空0L2L45L杯倒入3L杯2L0L55L杯取水2L5L65L杯倒入3L杯3L4L问题很简单，很好解决，接下来面试官问出了一个变种：杯子容量变成 7L 和 11L，取出 2L 水。解法其实也不变，大杯接水倒小杯。接下来的表格中我们会用（容量）L 代表杯子，-> 代表水的流动：步骤操作7L杯11L杯1-> 11L0L11L211L -> 7L7L4L37L ->0L4L411L -> 7L4L0L5-> 11L4L11L611L -> 7L7L9L77L ->0L9L811L -> 7L7L2L随后，面试官问出了终极难题：什么情况下这个问题无解？证明这个问题我在网上没有找到它的名字，我就将其命名为“双杯取水问题”好了。我们先只考虑我们目前的解法——即将大杯中的水倒入小杯，最后取得指定容量的水——这种情况下，会有那些情形是无解的。我们假设大杯容量为M，小杯容量为N，每次我们能取出的特定容量为：M - NM - (N - (M - N)) = 2(M - N)M - (N - 2(M - N)) = 3(M - N)M - (N - 3(M - N)) = 4(M - N)不难看出，每一步我们能取出的特定容量都为 n(M - N)，考虑每次我们还会将 N 的整数倍倒掉，最后的结果是 n(M-N) mod N。我的直觉告诉我，这个问题的解和 M - N 与 N 的最大公因数有关，如果最大公因数不为1，则会有无解的情况。要证明也不是很困难，我们画一个数轴就可以得到。假设最大公约数为a，M - N 为 b，则 nb 在数轴上一定会落到 a 的倍数上，而nN也一定会落到 a 的倍数上，所以 nb 减去 N 的整数倍后还是会落到 a 的整数倍上。在 a != 1 的情况下，a 的整数倍之间的空隙就是我们要找的无解的解。换个角度把 N 和 M 的整数倍都画在数轴上，也能很容易得出，用小杯往大杯倒水也是一样的效果，他们在数学上是等价的。这个问题的数学本质是在问 nN 与 mM 之间的差值。当然，我们也很容易看出，当 M 和 N 都是整数的时候，我们是取不出小数升的水的。
cglib 是 Spring 框架默认使用的动态代理库，其主要工作原理是使用 ASM 技术在运行现场制造出被代理对象的子类，并以子类的对象替换被代理对象。其与 Java 的动态的主要不同是其不受代理接口的限制，但是无法代理 final 类。研究方法与工具cglib 的代理类是由net.sf.cglib.proxy.Enhancer创建的，而 Enhancer 提供public generateClass(ClassVisitor v)方法将生成的类写入任意org.objectweb.asm.ClassVisitor的实现中；而 ClassVisitor 的实现org.objectweb.asm.ClassWriter的方法public byte[] toByteArray()则支持将写入其中的类转换为字节数组，这样我们就获得了代理类的字节码了，只需要将其写入文件就可以深入研究了。研究对象为了简化研究，我定义的类比较简单，他的源码如下：class Example { open class TestClass protected constructor() { open var a: Int = 0 } }获取字节码的源码为：@Test fun getClass() { val writer = ClassWriter(0) val enhancer = Proxy.newEnhancer(TestClass::class.java) val clazz = enhancer.createClass() CglibUtil.setStaticCallbacks(clazz, arrayOf(ProxyInterceptor())) enhancer.generateClass(writer) File("TestClass.class").writeBytes(writer.toByteArray()) }未完待续
bennyhuo 在新一集视频中谈到了 Kotlin 与 Java 的可见性修饰问题，这个问题不仅影响程序设计，有些细节也会影响反射最终落地的实现。我曾经参与过一个 Minecraft 模组 LightLand 的开发工作，其中用到了一个使用 ASM 技术实现高性能反射的库 reflectasm。这个库会用 ASM 技术在运行现场制造出一个与被反射类同包（java包除外）的类，并提供相应接口以反射形式访问被反射对象。该实现导致的限制是其对可见性敏感，无法访问被反射类的私有成员，这个特性对我印象深刻，因此我在设计反射与可见性的问题上总会想到这一点，同样的，我也可以用这个特性对类成员的可见性进行初步窥探。我一开始设计了一个涵盖 Kotlin 所有四个可见性修饰符（private、protected、internal 和 public）的类 FieldTestClass 并尝试用 reflectasm 访问他的成员变量：internal class FieldTestClass { val i1: Int = 0 protected val i2: Int = 0 private val i3: Int = 0 internal val i4: Int = 0 } // Class.methodAccessList 是我写的一个针对 reflectasm 的扩展函数 // 详见 https://github.com/tursom/TursomServer/tree/master/ts-core/ts-reflectasm FieldTestClass::class.java.fieldAccessList.forEach { println(it.fieldNames.asList()) }不出意外的出意外了，我忘记了 Kotlin 在 JVM 平台的所有成员变量都是 private 的，真实的可见性是 getter 和 setter 实现的。将对应的方法改成对成员方法的获取，得到了以下输出：[getI4$ts_reflectasm, getI1, getI2]i1 是 public 的，我们可以轻松获取到他的 getter，很正常i2 是 protected 的，同包可见i3 是 private 的，同包也不可见（查看字节码甚至可以看到没生成 getter）i4 比较有趣，他是原本 getter 的名字后面加了当前 module 的名字（因为这段测试我是在 ts-reflectasm 中运行的），目前原理未知以下是 FieldTestClass 字节码的反编译：@Metadata( mv = {1, 8, 0}, k = 1, d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\b\b\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002R\u0014\u0010\u0003\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u0014\u0010\u0007\u001a\u00020\u0004X\u0084D¢\u0006\b\n\u0000\u001a\u0004\b\b\u0010\u0006R\u000e\u0010\t\u001a\u00020\u0004X\u0082D¢\u0006\u0002\n\u0000R\u0014\u0010\n\u001a\u00020\u0004X\u0080D¢\u0006\b\n\u0000\u001a\u0004\b\u000b\u0010\u0006¨\u0006\f"}, d2 = {"Lcn/tursom/reflect/asm/ReflectAsmFieldTest$FieldTestClass;", "", "()V", "i1", "", "getI1", "()I", "i2", "getI2", "i3", "i4", "getI4$ts_reflectasm", "ts-reflectasm"} ) public static final class FieldTestClass { private final int i1; private final int i2; private final int i3; private final int i4; public final int getI1() { return this.i1; } protected final int getI2() { return this.i2; } public final int getI4$ts_reflectasm() { return this.i4; } }关于 ReflectASM 的一些细节查看 ReflectASM 的源码，在 MethodAccess.java 的 109-111 行中，我们可以注意到如果被反射对象所在的包是以“java.”开头的，则会加上前缀“reflectasm.”以逃避 JVM 对 java 包的类加载限制。kotlin internal 属性在 java 中的访问可以正常访问，以下 java 代码可以正常编译运行：package cn.tursom.reflect.asm; public class JavaTest { @Test public void test() { ReflectAsmFieldTest.FieldTestClass a = new ReflectAsmFieldTest.FieldTestClass(); System.out.println(a.getI4$ts_reflectasm()); } }
ts-socket 是如何用同步语法实现异步读写的
ts-socket 是我在大三时期做的一个研究向的产品，它的主要功能是利用Kotlin的协程技术与Java的NIO技术，为用户提供写法类似传统 Socket 的同步语法的异步读写。从核心流程上讲，首先创建对应的ServerChannel，注册到 NioThread 上进行监听。等到有新连接进入的时候，由 ts-socket 接收连接，启动协程，并调用操作者提供的回调函数。读写操作则是注册 NioThread 对应的读写事件，然后挂起操作者协程直到事件触发。由 ts-socket 进行读写操作并将操作结果返回给用户。在这整个过程中，用户的语法与传统的 BIO 高度相似，以下是使用 ts-socket 实现的 echo 服务器的例子：val handler: suspend BufferedAsyncSocket.() -> Unit = { while (open) { val read = read(timeout = 60 * 1000) write(read) } } fun main() { val server = BufferedNioServer(port = 12345, handler = handler) server.run() }NioThread接口 NioThread 所在的包cn.tursom.niothread包含了在独立线程上运行的，与业务逻辑无关的基础代码与接口。这个包的设计由需求驱动，因较复杂的需求而变得比较复杂。NioThread 的逻辑架构分成三层，每层代表了不同的抽象需求。按照调用关系为：NioThread->workLoop: (thread: NioThread, key: SelectionKey) -> Unit->NioProtocol。NioThread 代表执行 NIO 循环的线程，是最底层的部门。其负责调用selector.select，并将 select 出的每个 SelectionKey交给 workLoop 处理。NioThread 由实现原理可分为通过线程池实现的ThreadPoolNioThread与普通线程+while循环实现的WorkerLoopNioThread。workLoop 是 NioThread 在每个循环时都会调用的回调函数，负责解析处理每个被 select 出的 SelectionKey。主要实现有BossLoopHandler、WorkerLoopHandler与MultithreadingBossLoopHandler。NioProtocol 由 workLoop 调用，由用户提供，表示对应事件发生时的回调。
本文源码可查看 https://github.com/tursom/GoCollections/tree/master/lang/atomicGO 在1.18终于添加了泛型。有了泛型，我们对一些泛用接口就有了更好的约束手段，合理使用泛型可以更好的复用代码。而本文则是在泛型技术的基础上对GO的原子操作简化的尝试。我在atmoic包里定义了 Atmoic[T any] 接口，这个接口定义了针对泛型 T 的原子化实现，包括 Load、Store、Swap以及最重要的CompareAndSwap。我还定义了 Atomizer[T any]接口以提供操作不同类型的方法工厂，这个接口提供四个无参函数，分别返回操作其指定类型的四个原子化方法。默认提供的实现是atomizerImpl[T]，提供 (u)int32/64 以及引用的共五种 Atomizer。考虑到开发中几乎不会需要自定义 Atomizer，类型 atomizerImpl 并没有被导出。type ( Atomic[T any] interface { Swap(new T) (old T) CompareAndSwap(old, new T) (swapped bool) Load() (val T) Store(val T) } Atomizer[T any] interface { Swap() func(addr *T, new T) (old T) CompareAndSwap() func(addr *T, old, new T) (swapped bool) Load() func(addr *T) (val T) Store() func(addr *T, val T) } atomizerImpl[T any] struct { swap func(addr *T, new T) (old T) compareAndSwap func(addr *T, old, new T) (swapped bool) load func(addr *T) (val T) store func(addr *T, val T) } )全部五种 Atomizer 实现都提供了对应的获取方法，值得一提的是由于unsafe.Pointer与引用有完全相同的内存结构，所以你获取到的针对特定类型引用的 Atomizer 其实就是实现 unsafe.Pointer 原子化的pointerAtomizer。var ( int32Atomizer *atomizerImpl[int32] int64Atomizer *atomizerImpl[int64] uint32Atomizer *atomizerImpl[uint32] uint64Atomizer *atomizerImpl[uint64] pointerAtomizer *atomizerImpl[unsafe.Pointer] ) func GetAtomizer[T any]() Atomizer[*T] { // atomizerImpl[*T] 与 atomizerImpl[unsafe.Pointer] 有完全一致的内存结构 // 因此可以通过 unsafe 强制转换 return (*atomizerImpl[*T])(unsafe.Pointer(pointerAtomizer)) }对 GO 泛型的探索同样体现在函数CompareAndSwapBit上，其泛型类型被限定在了 int32/64 与 uint32/64 中。这个函数还要求用户提供对应类型的 cas 实现函数。在没有泛型的时代，这个函数至少需要为32位和64位类型分别提供两个函数。func CompareAndSwapBit[T int32 | int64 | uint32 | uint64](cas func(p *T, old, new T) bool, p *T, bit int, old, new bool) bool { location := T(1) << bit oldValue := *p if old { oldValue = oldValue | location } else { oldValue = oldValue & ^location } var newValue T if new { newValue = oldValue | location } else { newValue = oldValue & ^location } return cas(p, oldValue, newValue) }我还尽可能多的为基础类型定义了对应的原子类，比如 int8 的原子化类type Int8 int8。这样定义类可以方便对象在不同“描述”之间的转换，不过还是有例外，在我尝试实现对指针的原子化时出了意外：GO的编译器不接受指针类型作为接收器，我们无法为其定义成员函数。因此，我只能采用更传统的写法，定义一个新结构体Reference[T any]：// Reference atomizer type T reference Reference[T any] struct { lang.BaseObject p *T }lang.BaseObject是一个0长度的结构体，实现了一些通用的lang.Object的约束，不过这不是本文的重点。重点在于Reference[T]在内存层面上讲其实就是*T，只不过在语法上套了一层皮而已。Reference 所有的成员函数都使用引用接收器，在内存上讲本质就是**T，因此我们还可以将**T“解释” 为*Reference[T]，这个过程是纯粹的 unsafe 类型转换，不需要任何堆内存开销，这就是函数ReferenceOf的实现原理：// ReferenceOf cast **T to *Reference[T] func ReferenceOf[T any](reference **T) *Reference[T] { return unsafe2.ForceCast[Reference[T]](Pointer(reference)) }