基于java nio的server设计

最近由于业务需要,写了一个 java server用来与我们的游戏服务器实现交互。 server只实现了最基本的功能: 维护多个长连接,发送与接收消息;连接建立及断开(主动与被动)。 在实现的过程中,最有意思的是java nio里的buffer,但我认为这是一个失败的设计。 java nio buffer三个主要的属性,position, limit, capacity. 通常来讲,传统的ringbuffer都会有一个read index和write index, 但buffer nio将两者统一为postion,然后弄出了读写模式切换的概念。 当从buffer里取出数据,即buffer.getXXX或者channel.write(buffer), 当往buffer里放入数据,即buffer.putXX或者channel.read(buffer), 的前后,经常要考虑模式的切换,比如调用flip,这样的设计增加了复杂性,对库的使用者带来困难(笔者才疏学浅,一直疑惑他们为什么这么设计,求高人指教),这本质上是因为position属性既要当作readindex来使用,又要当作writeindex来使用。 另外一点就是由于postion的大小不能超过limit和capacity, 导致使用的过程中经常需要compact, 每次compact会导致额外的内存拷贝;传统的ringbuffer会在writeindex < readindex在的时候才可能涉及到额外内存的拷贝。 也难怪,netty里抛弃了这种设计。

初识Erlang

最近读完了《Erlang程序设计》。 比较有感触的还是函数式语言程序设计。 (1) 变量不变。 在命令式编程的体系下,同一个函数,即便是在传入参数相同的情况下,在并行的环境的下很有可能产生不一样的输出。 变量不变给函数带来了确定性,这与数学中的函数确定性是一致的。这也就意味着函数式编程是无副作用的,因而极大的降低了并发编程的难度,我们平时提倡尽可能使用消息来通信而不是共享内存来通信其实也是这个道理。 Erlang通过CSP(Communicating Sequential Processes)来降低了并行程序的难度:执行次序的不确定性,共享变量引起的竞争条件。 某种程度上,其实也是一种actor模型吧。 (2)函数是First Class, 函数就是一切。 函数是First Class不仅仅是函数式语言才有的,但是这些非函数式的语言并没有把函数当作最重要的一个元素来看待。 在函数式语言中,函数作用机制非常强大,象程序的控制结构都可以用函数来实现。

type 与 RTTI

时常,我们在程序中经常出现这样的switch或者if-else分支: if (pobj->IsKindOf(Circle)) else if (pobj->IsKindOf(Rectangle)) 等。其中上面的IsKindOf也可能是我们自定义的GetType(),或者是C++标准中的dynamic_cast。 不管怎么样,这是十分不好的设计/风格。 原因很简单,要添加一个新的类型,所有这些条件判断的地方都需要做更改;反之,使用多态只需要添加一个SubClass(当然,也可以在类层次上作一些调整)。 因此,MFC的CObject::IsKindOf() 明确的指出Do not use this function extensively because it defeats the C++ polymorphism feature. Use virtual functions instead. 如何应对这个问题呢, MartinFlower在《重构》中明确的指出了几条细则,包括: Replace Type Code with Class; Replace Type Code with State/Strategy; Replace Type Code with Subclasses; Replace Conditional with Polymorphism 既然如此,为什么还需要IsKindOf,dynamic_cast这些玩意呢。 在BS的《C++语言的设计与演化》中的《强制》一章“为什么提供一种危险‘特征’”一节中,我们可以找到讲RTTI作为标准的一些理由。这里就不一一罗列。 (“危险”两个词用的好,o(∩_∩)o) 每一个有用的特征都可以被误用。。。

STL之争

以前,是否使用STL是有争议的,因此很多公司都实现了自己的一套STL。 其实STL有很多有意思的设计,比如实现了数据结构和算法的分离,这也是STL设计的核心思想。 在实现上,STL源代码也是能够给人一些启发的。比如sort算法的实现(这应该不是STL的开创),sort主要采用快速排序,对于初步有序的短系列,采用插入排序,对于快速排序的极端情况,还可能使用堆排序。 性能方面,STL作为通用库,一般来说效率还是比较高的。但是,在大型游戏领域和内存受限领域,以及由于STL在不同平台上的实现不同,其性能还需要改进。关于这一点,可以参考http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html这篇论文,论文还提供了一个性能对比图。 扩展性上,个人认为,仿函数,适配器还是有比较好的扩展性,Allocator(1998 ISO C++)则争议很大,上述论文和http://www.openstd.org/jtc1/sc22/wg21/docs/papers/2005/n1850.pdf这篇论文有比较详细的解释。 易用性上,STL同样备受争议。这主要表现在: (1)需要对库的实现有相当的理解,由于依赖于模版与泛型,然后又加上C++语言,滥用极可能带来性能的严重损失,难以发现的一些Bug等。 (2)调试的不方便,源码也缺乏文档和注释。 (3)一些局部的瑕疵, 比如缺少像树这种层级结构的容器,比如list的sort函数也违背了数据结构和算法的分离思想。 总而言之,作为通用库,STL还是比较不错的,而在某些对性能要求更高的领域,最好慎用。 对STL的改进和替换方面方面,可以参考: EASTL: EASTL依然沿用STL的基本思想,在实现上改进了STL,是一个性能更高,更易于调试的STL。 Doom3: 完全抛弃了迭代器,是一个比较简单,但是非常实用的STL。