Roger Chen's profileLearningBlogLists Tools Help

Blog


    4/30/2006

    Newbie

    到新公司已经一个礼拜了,很忙,也很充实。连Blog都没有时间上来写了:)
     
    我现在跟进的这个项目需要Unix下的C++编程技能,在这方面我纯粹是新手,任务的deadline也很紧,所以我只有不停的学习才能跟上进度。比较有意思的是这个项目在网络方面的代码比较多,而上个月我已经“顺便”看完了《Unix网络编程》这本书,刚好派上了用场。
     
    最近在当当上又买了近300块钱的书,五一除了陪来上海玩的同学,剩下的时间就是抓紧时间K书了。
     
    似乎有很长一段时间没有过这种紧张的感觉了,呵呵,还蛮享受的:)
    4/20/2006

    Cindy 3.0b1 released

     
    修改记录:
    • 添加了hello world示例
    • 添加了简单的tcp/ip服务示例(echo/discard/daytime/chargen)
    • 添加了telnet示例(tcp/udp)
    • 更新了http server示例,支持列表目录
    • 为DefaultPacket添加了两种构造函数:DefaultPacket(ByteBuffer)/DefaultPacket(byte[])
    • 添加了SessionType类,并为Session/SessionAcceptor接口添加了getSessionType方法返回Session类型
    • 为Configuration类添加了set方法
    • 在SessionFactory类中加入了createSession(SessionType)和createSessionAcceptor(SessionType)两个方法,原来的createSocketSession/createDatagramSession/createSocketSessionAcceptor方法被deprecated
    • 修正DirectDispatcher.block中的bug,原实现在判断是否为核心线程方面存在Bug
    • 一些小的更改

    终于发布beta1了,API结构已经稳定下来了,原来使用cindy 3.0 alpha版本的用户请尽快升级到beta版,以后的版本发布只会更新bug或增加功能,但是不会影响到API结构。

     

    User Guide也同步升级,谢谢wei_cheng提出的很多好问题!

    Msn Space被封

    好几天断断续续的访问不到,原来以为是Microsoft自己在做维护工作,直到昨天才发现直接访问不了时,通过tor就可以访问到,毫无疑义,肯定是被GFW给盯上了。
     
    DNS返回spaces.msn.com的IP是65.54.153.254,无法访问;不过65.54.153.250也是微软的服务器,没有被封,因此只要把spaces.msn.com指向到这里,访问就没有问题了。
     
    Windows的用户可以编辑windows\system32\drivers\etc\hosts文件,在下面加入:
    65.54.153.250 spaces.msn.com
    就能正常访问了。
    4/10/2006

    拿到HP的Offer了

    ,终于拿到HP的Offer了!
     
    收到邮件的那一瞬间,居然没有我想象的那么激动,不过倒是好好的舒了一口气,悬在心里的一块石头终于落地了:)
     
    我很清楚自己目前的强处和弱点所在:强处是技术上能力还不错,并且有很强的学习精神;弱点是外语相对较差以及缺乏大公司经验。我希望能够在下一家公司中继续加强我的强项并且有机会修正这些弱点,理想的情况是世界500强的IT企业。当然,工作内容也要是我喜欢的,为了钱而工作那是一点乐趣都没有的。虽然有很明确的目标,不过还是花了我半年的时间来等待合适的职位出现,不过总算功夫不负有心人啊:)
     
    非常感谢Sam给了我这个机会!也希望庄表伟和陈超在新的工作中一切顺利!
    4/8/2006

    Unix网络编程

    这本书是今年我阅读过的技术书籍中影响我最大的一本:)
     
    我买的是第三版,前两版我没有看过,无法评价。这一版是Bill Fenner和Andrew M.Rudoff执笔,对上一版进行全面修订,加入了IPv6的一些信息和SCTP协议,删除了X/Open传输接口的内容,加入的这些内容很具有实用性。虽然这一版作者的名气比不上前两版大名鼎鼎的W.Richard Stevens,但是我的阅读体验非常好(这既有Stevens的功劳,和译者的努力也是分不开的),内容非常匹配我当前的水平,所以阅读起来非常流畅。不夸张的说,通过阅读这本书,我才迈入了网络编程的大门。
     
    我认为这本书最有价值的地方不在于其描述的套接口编程函数(虽然这些对我这个Unix网络编程的新手来说也是很有价值的),而在于哪些分散在各个章节中对网络编程底层实现细节的注释。你了解各种I/O模型的异同么?知道在在调用某些套接口函数的背后它替你完成了什么工作么?知道SO_LINGER怎么样影响close的行为么?UDP的外出接口是怎么确定的?为什么有时connected udp会返回ICMP异常给客户程序,而普通udp则不会?IPv4和IPv6的服务端和客户端怎么协同工作?
     
    当我看到第八章对connected udp和普通udp的性能比较时,禁不住拍案叫绝(书217页):
    在一个未链接的UDP套接口上给两个数据报调用sendto函数于是涉及内核执行下列6个步骤:
    • 连接套接口;
    • 输出第一个数据报;
    • 断开套接口连接;
    • 连接套接口;
    • 输出第二个数据报;
    • 断开套接口连接。

    另一个考虑是搜索路由表的次数。……

    调用connect后调用两次write涉及内核执行如下步骤:

    • 连接套接口;
    • 输出第一个数据报;
    • 输出第二个数据报。

    在这种情况下,内核只拷贝一次含有宿IP地址和端口号的套接口地址结构,相反当调用两次sendto时,需拷贝两次。[Partridge和Pink 1993]指出,临时连接未连接的UDP套接口将消耗每个UDP传输三分之一的开销。

    目前我只读到第14章,但是我已经准备好在读完后再精读一遍。如果实在要说这本书有什么缺陷的话,那就是它的份量。你知道,长时间捧着一本这么重的书,实在是一件体力活:)

    4/4/2006

    CVS和Issue Tracker迁移到javaeye

    昨天抱怨了一通sourceforge的CVS服务后,Robbin非常热情的为我开放了javaeye的CVS服务,并且导入了我本地的CVS历史记录。同时javaeye还提供了ViewCVS、CVSTrac、JIRA以及Confluence,开发起来真是太方便了,非常感谢Robbin!(顺便抱怨一下,sourceforge提供的Issue Tracker系统的确不好用)
     
    我在googlegroup上也申请了一个Cindy的讨论组当作mailing lists,如果问题方便公开的话,建议大家还是发邮件到这里,这样方便知识共享。
     
    根据这些改动,Cindy的主页也做了相应更新。
    4/3/2006

    Sourceforge的稳定性还真有待提高

    因为以前版本都是我一个人开发,所以代码都是使用本地的CVS,一来稳定,二来速度快。现在庄表伟加入后,我想还是把代码check到sourceforge的CVS上,这样协同比较方便。

     

    可是自从我把Cindy 3.0a5版本的代码check到sourceforge上后,连续4天我都没有办法再连接上sourceforge的CVS服务(匿名用户的CVS服务一直可以,但是开发者的CVS一直没法连接上) :(发现没有CVS真是寸步难行。修改的地方越多担心就越大,无法提交,也无法恢复成CVS上的当前版本,慢慢的就不敢下手改代码了。

     

    实在没办法,等了它四天都没有修复,只能恢复成使用本地CVS了。将已经确认的改动提交进去后,就松了一口气,开始放心大胆的改代码了:)

    4/1/2006

    Selector.select(timeout) throws NullPointerException occasionally (win)

    今天本想对Cindy进行一些效率上的优化,尽量减少对Selector.select的调用以及传递给Selector的句柄数量,没想到意外又触发了nio的一个Bug:在Windows平台上,Selector.select调用可能抛出空指针异常。
     
    堆栈类似于:
    java.lang.NullPointerException
     at sun.nio.ch.WindowsSelectorImpl$FdMap.remove(WindowsSelectorImpl.java:76)
     at sun.nio.ch.WindowsSelectorImpl$FdMap.access$3000(WindowsSelectorImpl.java:66)
     at sun.nio.ch.WindowsSelectorImpl.implDereg(WindowsSelectorImpl.java:523)
     at sun.nio.ch.SelectorImpl.processDeregisterQueue(SelectorImpl.java:131)
     at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:121)
     at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)
     at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
    我机器上有四个Java版本,在Sun JDK 1.4.2_05、Sun JDK 1.5.0_02、JRockit 1.5.0_04上都不会出现问题,只有在Sun JDK 1.5.0_06上面能重现Bug。反编译sun.nio.ch.WindowsSelectorImpl,可以看到其FdMap.remove中忘记判断空了(红色代码部分):
     
          private MapEntry remove(SelectionKeyImpl selectionkeyimpl)
            {
                Integer integer = new Integer(selectionkeyimpl.channel.getFDVal());
                MapEntry mapentry = (MapEntry)get(integer);
                if(mapentry.ski.channel == selectionkeyimpl.channel)
                    return (MapEntry)remove(integer);
                else
                    return null;
            }
     
    到Sun的Bug database中一查,早已有人提交了该Bug,这个Bug目前只在mustang b75版本中得到了解决。
     
    我看了看Sun以前版本的实现,虽然不会出现该Bug,但是恐怕会有其他的问题。Sun可能是想修改代码来解决原来的一些Bug,没想到引入了一个新Bug:(虽然Sun bug database中已经有了解决方案,但是绝大部分的用户应该都不会自己去修改Sun的源码然后打包的。
     
    目前发布的这个Cindy版本不会受到这个Bug的影响。鉴于Sun目前还没有将该Bug的修正放到Java 5.0的升级包中,所以我也暂时取消对Cindy效率上的一些优化(由于这些优化才触发了NIO的Bug),等到它升级包发布出来后再另行考虑。
    3/27/2006

    Cindy 3.0a5 released

     
    修改记录:
    • 修正ant脚本中未使用fork加载http server的bug
    • 将内部BufferCache接口改名为BufferPool
    • 在Session接口中,使用getAttribute/setAttribute/removeAttribute代替原来的getAttachment/setAttachment方法(谢谢wei_cheng和Arbow)
    • 提高了DefaultBufferPool的实现效率,分配和释放时根据Buffer大小使用不同的锁
    • 重新设计了SessionAcceptor接口,统一了原来的SocketSessionAcceptor和ServerSocketChannelSession
    • 修正了AbstractChannelSession中的分发事件顺序可能不一致的Bug
    • 为ServerSocketChannelSession添加了多次accept行为
    • 添加了acceptor/session.socketSession/session.datagramSession的运行期配置
    • 重载了AbstractBuffer类的equals方法
    • 一些小的改进

    比较重要的几点改进:

    1. 为ServerSocketChannelSession添加了多次accept行为。原来是触发了OP_ACCEPT后只accept一次,然后继续监听OP_ACCEPT;而现在则是触发了OP_ACCEPT后一直accept,直到accept返回为null,再继续监听OP_ACCEPT。加入这一行为后,在未达到临界点前,系统的性能大约提高了10%左右。可参考HP上的一篇文档:Exploring the Performance of Select-based Internet Servers
    2. 重新设计了SessionAcceptor接口,统一了原来的SocketSessionAcceptor和ServerSocketChannelSession两种不同的模型。应用不再需要循环accept了,在默认情况下使用NonBlockingSessionAcceptor(即相当于Cindy 3.0a4版中基于session的acceptor),性能比原来有所提升。具体改进可见cindy中的示例代码。
    3. 修正了AbstractChannelSession中的分发事件顺序可能不一致的Bug。分发事件顺序的Bug一直在修正,老是解决了老问题后又发现了新问题:(,不过目前看来新问题出现的越来越少了:)

    比如如下代码:

    startFuture.setSucceed(true);
    getSessionFilterChain(false).sessionStarted();

    第一行代码使得Future完成事件被触发,第二行代码使得SessionFilterChain上的sessionStarted事件被触发。看上去没有任何问题,是吗?

    可是,如果加入这么一个FutureListener:

    startFuture.addFutureListener(new FutureListener() {
         public void futureCompleted(...) {
               session.close();
         }
    });

    在使用DirectDispatcher的情况下,事件的触发顺序就变成了: Start future completed(上面的第一行代码) --> session close --> session closed --> session started(上面的第二行代码),出现了明显的错误。

    所以这次Cindy 3.0a5中把所有这种顺序派发的事件全部改在Dispatcher内完成,比如上面的代码就改成了:

                // keep dispatch order
                dispatch(new Runnable() {

                    public void run() {
                        startFuture.setSucceeded(true);
                        getSessionFilterChain(false).sessionStarted();
                    }
                });

    可以参考DirectDispatcher中的代码,就能了解为什么这么做就能保持事件分发顺序。

    3/25/2006

    目标

    最近常被问到的一个问题是:做高效率的网络应用都是用C/C++,为什么要用Java?我也一直在思考这个问题,这牵涉到Cindy的定位,以及将来的发展方向。
     
    如果存在一个这样的假设:C/C++和Java在网络处理方面拥有接近的运行效率,你会选择用什么语言开发网络应用?我的答案会是Java。为什么?因为对于大部分普通程序员而言(某些C/C++高手除外),在业务逻辑的开发效率上,Java要超出C/C++许多,这也正是Java流行起来的原因。
     
    但是遗憾的是,这个假设目前还不存在:( JSR 203提议在Java中加入对操作系统本身异步IO的支持,但没有人推动;IBM推出过aio4j,不开源,而且在2004年就停止了更新。所以目前在你所看到的现实中,这类应用仍旧是C/C++的天下。
     
    虽然假设并不存在,但需求是切切实实存在的:大家对Java网络方面的效率有着更高的期望。比如Tomcat,在它的5.5版本中有基于ARP的JNI实现;比如Resin以及其他很多商业WEB服务器,都有JNI的实现。但是遗憾的是,这些JNI并不是一个独立的网络框架,所以虽然这些人都在自己发明轮子,但是其他人很难用上他们发明好的轮子。
     
    既然有需求,而且需求无法得到满足,那么这里就有机会。
     
    做为一个比较资深的Java程序员,做为一个Java网络框架的开发者,我所关心的是:能不能让这个假设成为现实?或者能不能缩小它们的差距,给Java这边加些砝码?这就是Cindy的长远目标。目前虽然我的能力离实现这个目标还有差距,不过只要目标清晰,一直往前走,总是能实现的。
     
    希望能有志同道合者和我一起来实现这个目标:)
    3/22/2006

    LDAP的一些用途

    没使用LDAP时没感觉到它的好处,现在才觉得以前那真是生活在原始社会中……
    • LDAP直接可以整合Linux平台的用户管理和授权
    • LDAP + Samba可以做Window域控制器
    • LDAP可以用来做PcAnyWhere的用户验证和授权
    • LDAP + JNDI可用来做Java程序的用户验证和授权(能被广泛应用在Java的各种用户验证和授权中,如JMS的权限控制、JMX的权限控制等等)
    • LDAP可以用来做单位员工黄页
    • ……

    你们是如何管理你们的服务器呢?

    3/14/2006

    Cindy 3.0a5计划加入Qos控制

    本来打算下一个版本就b1了,不过这两天发现一个非常值得改进的地方:发送的Qos控制,而且该改动会影响一些现有的API,如send(Object, int)/flush(Object,int)等等,所以还是下一个版本还会是alpha版本。

     

    在a4的实现中,Qos控制是由一个PriorityQueue来完成的,根据不同的优先级权重来调整发送顺序。但是该实现只是一种特定的Qos控制,我的计划是把Qos控制这部分分离出来,可以由用户来进行设置。

    比如Concurrent包中有Delayed接口和DelayQueue实现,DelayQueue能够让添加进去的Delayed对象在指定时间后才能被取出,这种Qos控制可以用于控制包发送间隔时间。对于类似WEB、FTP这类应用,发送速度是越快越好,Qos要求可能不明显;但对于流媒体这类应用,并不是发送速度越快越好,只要保证流媒体能够流畅观看即可,发送速度过快反而可能导致其他人无法流畅观看,这种Qos控制就非常有用。

     

    但是Concurrent包的Queue/BlockingQueue接口并不能直接用于Cindy的Qos控制,因为它们只提供了阻塞和轮询两种处理方式,这种开销对Cindy来说是不可接受的,所以需要实现队列的回调方法。另外可配置性的Qos控制怎么与现有的发送API、SessionFilter、Packet接口等结合起来,仍在考虑之中,希望能想出一个比较可行的解决方法来。

    3/11/2006

    Cindy 3.0a4 效率测试

    硬件环境:

    服务器和客户端为同一台机器:XEON 3.0G/2G/SUSE 10.0/Kernel 2.6.13-15-smp

    软件环境:

    Java虚拟机均采用BEA JRockit,版本号为R26.0.0-189-53463-1.5.0_04-20051122-2040-linux-ia32,运行时参数均指定为-Xms512m -Xmx512m。

    除Resin外,所有server的backlog都设为10000(未找到Resin中设置该选项的位置)。QuickServer虽然提供了file server,但是由于其发送完成后不关闭连接,用AB无法测试。

    • Echo web server
      • Cindy 2.4.4 http server
      • Cindy 3.0a4 http server (use SocketSessionAcceptor)
      • Cindy 3.0a4 http server (use ServerSocketChannelSession)
      • MINA 0.9.2 http server
      • QuickServer 1.4.7 echo web server (blocking mode, disable log and admin)
      • QuickServer 1.4.7 echo web server (non-blocking mode, disable log and admin)
    • File web server
      • cindy 3.0a4 http server (use SocketSessionAcceptor)
      • cindy 3.0a4 http server (use ServerSocketChannelSession)
      • tomcat 5.5.15 (disable log)
      • jetty 5.1.10 (disable log)
      • resin 3.0.18 (disable log)
      • apache 2.0.54
      • lighttpd 1.4.11 (disable log)
      • thttpd 2.25b (disable log)

    测试工具:

    Apache Benchmark,2.0.40-dev版本。

    先使用concurrent level 10,测试10w(大文件测试2k个)个请求热身,结果不记入统计。随后分别使用concurrent level 1/10/100/500/1000,测试20w(大文件测试1w个)个请求,对结果进行统计。

    测试结果

    表格中为Requests per second,数值越高越好。

    Echo web server:

    Concurrent  Cindy 2.4.4 Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) MINA 0.9.2 QS 1.4.7 (blocking) QS 1.4.7(non-blocking)
    1 3376 2760 3062 failed 2299 failed
    10 4882 4581 4561 failed 3244 1890
    100 4998 4903 4494 failed 1425 1890
    500 4891 4528 4847 failed 1338 1353
    1000 4546 4018 4552 failed 1192 1313

     

    echo

     

    File web server (small file 100 bytes):

    Concurrent Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) Tomcat 5.5.15 Jetty 5.1.10 Resin 3.0.18 Apache 2.0.54 lighttpd 1.4.11 thttpd 2.25b
    1 2444 2641 3294 2308 3809 2982 4540 3145
    10 3812 3823 4275 3177 5231 3563 6044 4156
    100 4193 3673 4578 3221 4257 3579 5849 4215
    500 3759 3737 3896 3145 4309 3385 4888 4104
    1000 3776 3773 3858 2987 4040 2685 4041 3986

     

    small

     

    File web server (small file 100 bytes, keep alive):

    Concurrent Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) Tomcat 5.5.15 Jetty 5.1.10 Resin 3.0.18 Apache 2.0.54 lighttpd 1.4.11 thttpd 2.25b
    1 5119 timeout 7023 4980 3720 5771 8824  -
    10 5275 timeout 6878 5289 3753 6602 13441  -
    100 6322 timeout 9609 3947 4041 6391 12951  -
    500 5237 timeout 5952 5234 4331 6292 12495  -
    1000 5612 timeout 5478 4318 4046 3367 11696  -

     

     

    <Psmallk

     

    File web server (large file 947111 bytes):

    Concurrent Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) Tomcat 5.5.15 Jetty 5.1.10 Resin 3.0.18 Apache 2.0.54 lighttpd 1.4.11 thttpd 2.25b
    1 299 280 163 128 251 385 280 340
    10 257 255 203 (62 failed) 213 253 366 318 365
    100 237 240 190 (9991 failed) 189 226 339 296 288
    500 218 214 183 (46 failed) 186 (1 failed) 211 305 281 286
    1000 196 182 167 (23 failed) 182 (43 failed) 177 258 238 240

     

    large

     

    总结

    先解释一下为什么Cindy 3.0a4的测试会有两种:一种是基于SocketSessionAcceptor的,一种是基于ServerSocketChannelSession的。在开发机器上测试时,这两种类型的测试结果有较大的差距,基于Acceptor的测试实际上同时有两个线程在运行,一个线程不停的accept,然后另一个核心线程做相应的处理;而基于Session的测试实际上只有一个线程在运行,就是核心线程既负责accept,也负责处理。在赛扬2.4的机器上,第一种的测试成绩大约为650#/sec,而第二种的测试成绩大约能到900#/sec。鉴于这两者在开发机器上的性能差距,所以在服务器上也做了这两种测试。

    另外,由于这种测试绝大部分时间都是消耗在网络I/O上面,业务逻辑的处理用时非常少,所以只有在使用DirectDisptcher时才拥有最好的响应时间,多开线程会使响应时间大幅下降。也正是这个原因,前两种类型在开发机上的测试结果有如此大的差距。不过由于这次测试机器的CPU是XEON,双核,所以这两种类型的测试结果差距不明显。

     

    在Echo web server的测试中,MINA 0.9.2在完成一个请求后就不再相应后续的请求了,可能是代码中存在的一些Bug所导致,所以这次测试对MINA来说其实稍微有些不公平,我会等它在后续的版本修正了这个问题后再进行一次测试;本来在开发机上QuickServer的测试结果和Cindy的测试结果非常接近,在blocking模式下,性能甚至稍微超出Cindy 3.0a4,没想到放到服务器上测试时会有这么大的差距;Cindy 2.4.4依旧傻快傻快,呵呵,不过它的模型没有Cindy 3.0这么容易扩充,而且Cindy 3.0在这种场景下和它的差距也是非常小的,所以还是推荐尽早升级到Cindy 3.0。

    基于小文件的测试,Resin和Tomat遥遥领先,阻塞I/O+线程池的效率充分发挥了出来。并且这种压力测试速度又快,一个连接非常短的时间就能被处理完,正好是阻塞I/O+线程池的优势所在。在concurrent较小的情况下,这种优势更加明显,在concurrent逐渐增大后,阻塞I/O和非阻塞I/O处理效率上就越来越接近了。在这个测试中Jetty的结果比较令人失望。

    Keep alive的连接测试中,基于Session的测试耗时太长被中途放弃,怀疑是由于单线程运行+Keep alive造成了始终在处理第一个连接所造成,这个还有待证实;Tomcat的表现更是突出,而在这个场景中本来应该是非阻塞I/O的强项所在。不过最大的concurrent也只到1000,在真实的环境中,如果concurrent持续增大,cindy的优势应该会更加突出才对。

    大文件的测试类似于Keep alive的测试,这种场景应该也是非阻塞I/O的优势场景。在该场景中,cindy总算体现了非阻塞I/O的优势,而tomcat的表现就有点失望了,失败数也太多了一点……我研究过Tomcat的源代码,没采用JNI的话它是基于阻塞I/O的实现,只能通过SO_TIMEOUt来中断对数据的读取和写入,所以它在运行时会通过计算服务器的负载来设置相应的SO_TIMEOUT,负载小于33%情况下采用默认的SO_TIMEOUT,33%到66%之间SO_TIMEOUT减半,66%到90%之间,SO_TIMEOUT变为默认的1/3,超过90%则急速降为默认的5%。我怀疑在大文件的测试当中,由于服务器线程负载太大,所以导致超时时间变为默认的5%,从而造成了这么多的失败数。

     

    总体来讲,在基于NIO的异步I/O框架中,我认为Cindy的效率应该算是很不错的了;阻塞I/O+线程池的做法在并发量少、连接时间短的应用中比NIO有着更高的效率,但是随着并发量的上升,两者的差距会逐渐缩小(再上升会不会反超?未经证实……)。但Java的网络层效率还是和C/C++的实现还是有着明显差距,高性能网络应用的首选还是C/C++。

    Cindy 3.0a4 released

    修改记录:
    • SSLFilter可以被用于任意基于流的Session
    • 支持分发线程池(-Dnet.sf.cindy.dispatcher.concurrent)
    • 添加了DirectDispatcher,在当前线程中分发,但是不支持在分发线程中进行阻塞操作(比如Future.complete)
    • 修正DefaultDispatcher中分发顺序可能与事件产生顺序不一致的Bug
    • 修正DefaultDispatcher中流量控制的Bug
    • 在Buffer接口中加入dump方法,方便调试时打印
    • 在Future接口中加入getSession方法
    • 增加了ServerSocketChannelSessionHandler接口
    • 将ExecutorFilter重命名为DispatcherFilter
    • 将HSHAReactor重命名为DefaultReactor(因为去掉了对Executor的支持后,和HSHA模式不匹配了)
    • 将CindyConstants重命名为Configuration,支持从配置文件中读取相应属性(-Dnet.sf.cindy.config=config file,默认为cindy.properties)
    • 将net.sf.cindy.message包拆分为net.sf.cindy.encoder和net.sf.cindy.decoder
    • 更新了HTTP Server示例,扩展成一个相对完成的Http Server
    • 一些小的改进和bug修正

    等待一段时间后准备进入beta阶段。

     

    Update: 发布后才发现http server的ant脚本有些问题。如果各位要测试http server,要么把ant脚本修改成fork一个新的java进程,要么命令行指定ant -Dparam=-acceptor http-server来运行。

    非常抱歉!

    3/7/2006

    又发现NIO的一个小Bug

    按照NIO的规范来说,对于非阻塞Channel,如果当前write方法无法发送任何字节,则应该返回0,而不是抛出异常。

    以前我就发现了它的一个Bug:在Java 1.4的虚拟机上,SocketChannel的write(ByteBuffer[])的JNI操作实现中没有检测某个返回值,抛出了java.io.IOException: A non-blocking socket operation could not be completed immediately异常。而这种情况下本应该返回0的。好在这个Bug不影响大局,一般只要绕过对该方法的调用即可,况且这个Bug已经在Java 5.0中得到了解决。

    不过今天又发现了它的一个Bug:SocketChannel的write(ByteBuffer)的JNI操作,在Window的实现上没有检测WSAENOBUFS的返回值,抛出了java.io.IOException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full异常。而这种情况下也本应该返回0的。

    应该很少会有应用会触发这个异常,我之所以发现了这个异常也是运气:) 在Cindy简单的HttpServer示例中,图个方便简单的使用了内存映射文件,而且不管文件大小都是全部映射(我是示例Cindy的用法,千万不要在生产系统中这么做)。晚上测试一个200多M文件的下载,我的内存肯定是足够的,所以内存映射没有出现任何问题,但是一次性通过write方法发送200多M的Direct ByteBuffer,呵呵,这个现象就能重现出来了:)

     

    实际应用中应该不会有人象我这么玩的,呵呵,所以这个Bug影响倒也不大。对于应用来说,一方面是不要一次性发送这么大的数据,另一方面是增大Socket的发送缓冲区大小。对于Cindy而言,要对应用屏蔽这个Bug,即使应用发送这么大的数据也不要触发这个异常,倒是有一个简单的方法:把传递给write方法的ByteBuffer控制在一定的字节数内。

    即使应用要Cindy发送一个200M的ByteBuffer,也可以控制成每次调用write方法时,只传给它一个1M大的ByteBuffer,分200次发送即可。这样就可以简单的对应用屏蔽这个Bug了。

    3/6/2006

    Cindy 3.0a4 configuration

     Global configuration

    • net.sf.cindy.config (default: config.properties)
          Cindy config file
    • net.sf.cindy.enableJmx (default: false)
          Enable jmx support
    • net.sf.cindy.disableInnerException (default: false) 
          Disable inner exception dispatch

    Buffer configuration

    • net.sf.cindy.bufferCache (default: net.sf.cindy.buffer.DefaultBufferCache)
          BufferCache class name
    • net.sf.cindy.useDirectBuffer (default: false)
          Use direct buffer instead of heap buffer
    • net.sf.cindy.useLinkedBuffer (default: false)
          Use linked buffer instead of memory copy   

    Dispatcher configuration

    • net.sf.cindy.dispatcher (default: net.sf.cindy.session.dispatcher.DefaultDispatcher)
          Dispatcher class name
    • net.sf.cindy.dispatcher.keepAliveTime (default: 3000)
          Dispatcher keep alive time
    • net.sf.cindy.dispatcher.capacity (default: 1000)
          Dispatcher capacity (flow control)

    Session configuration

    • net.sf.cindy.session.timeout (default: 0)
          Session timeout (like SO_TIMEOUT)
    • net.sf.cindy.session.recvBufferSize (default: -1)
          SO_RCVBUF
    • net.sf.cindy.session.sendBufferSize (default: -1)
          SO_SNDBUF
    • net.sf.cindy.session.reuseAddress (default: false)
          SO_REUSEADDR
    • net.sf.cindy.session.tcpNoDelay (default: true)
          TCP_NODELAY
    • net.sf.cindy.session.soLinger (default: -1)
          SO_LINGER
    • net.sf.cindy.session.readPacketSize (default: 8192)
          Session read packet size
    • net.sf.cindy.session.nio.reactor (default: net.sf.cindy.session.nio.reactor.DefaultReactor)
          NIO Reactor class name

    Acceptor configuration

    • net.sf.cindy.acceptor.backlog (default: 100)
          Acceptor backlog
    • net.sf.cindy.acceptor.reuseAddress (default: false)
          SO_REUSEADDR

    For example, to enable jmx, use direct buffer, set dispatcher keep alive time to 10s and enable acceptor reuse address, you can specify configuration in system environment :

    -Dnet.sf.cindy.enableJmx=true
    -Dnet.sf.cindy.useDirectBuffer=true
    -Dnet.sf.cindy.dispatcher.keepAliveTime=10000
    -Dnet.sf.cindy.acceptor.reuseAddress=true

    or you can put cindy.properties on your classpath:

    enableJmx=true
    useDirectBuffer=true
    dispatcher.keepAliveTime=10000
    acceptor.reuseAddress=true

     

    3/2/2006

    Cindy 3.0a3 user guide released

     
    User Guide中的示例都是非常简单的示例,起个入门的作用。有了这个引子,再看看cindy源代码example目录下的示例,应该很容易就能上手。
     
    希望各位多提意见,这样才好根据你们的意见来做改进:)
     
    a4的主要目标是提高Cindy的易用性,比如通过运行期参数配置的Dispatcher线程池大小等等。如无意外的话应该两个礼拜后会release出来。
     
    BTW:我这边好像无法访问sourceforge各个项目的主页,即xxx.sourceforge.net。难道又被封了?
     
    Update: 晚上将原示例中简单的Echo Http Server扩充成为一个相对完整的基于文件的Http Server,然后又测试了一下效率,效率上居然也没有太多的降低。原来Requests Per Second差不多是4200#/sec,现在Requests Per Second差不多是3600#/sec。看来等a4版发布后,可以做一个比较完整的评测了,不仅可以和MINA等比较,还可以和现有的一些HttpServer进行比较(当然,它们的实现要完整的多,对它们本身的效率会有些影响,这点要考虑进去)
    2/27/2006

    Cindy 3.0a3 released

    下载地址: http://sourceforge.net/projects/cindy

    修改记录:

    • 添加了SSLFilter,不过仅能运行在Java 5.0以及以后的版本上
    • 添加了net.sf.cindy.session.dispatcher包,应用可以加入自定义的Dispatcher实现(通过-Dnet.sf.cindy.dispatcher=Dispatcher实现类 来指定)
    • 按照相反的顺序分发xxxSend/xxxSent事件,即排在前面的Filter后处理该事件
    • 在Buffer接口中添加了release/isReleased/isPermanent/setPermanent方法
    • 移除了BufferFactory的release方法
    • 在BufferFactory类中加入了wrap(Buffer[])方法
    • 在BufferBuilder类中加入了release方法
    • 移除了SessionHandler接口中的packetReceived/packetSent事件
    • 在SerialEncoder类中,用writeUnshared方法代替write方法
    • 在SerialDecoder类中,检测流的头部,是否为序列化流
    • 提高了LinkedBuffer效率(仍处于试验性质)
    • 提高了DefaultBufferCache效率
    • 在SocketSessionAcceptor接口中添加了backlog/reuseAddress/receiveBufferSize属性
    • 一些小的改进和Bug修正

    最大的改进就是加入了SSLFilter以及反向分发xxxSend/Sent事件。

    SocketSessionAcceptor中所增加的属性也是在实现网络服务器时经常要用到的,至于SocketSession、DatagramSession中经常要设置的属性由于数量太多,还是通过暴露出来的Socket和DatagramSocket来设置比较方便一点。

     

    SSLFilter还有一个小缺陷,由于Session没有提供beforeClose的一个事件出来,因此在Session关闭前不能发送SSL的close message。这样服务器端可能会抛出一个异常:

    javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?

    这种实现不是太完美,不过完美的实现只能通过继承已有的SocketSession实现来解决,在close前发送close message,但这就缺乏扩展性。目前权衡下来暂时采用Filter的设计,如果应用真的在意这个close message,那么可以通过组合SSLFilter和SocketSession实现来加以解决。

    2/17/2006

    高性能网络编程邮件列表

    大宝(sodme)在google上建立了一个高性能网络编程邮件列表,非常不错:)
     
    订阅方法可见大宝的介绍

    SessionFilter顺序

    在实现Cindy 3.0a3 SSLFilter的过程中,发现一个有意思的问题:不同类型的SessionFilter对于过滤事件的顺序有着不同的要求。
     
    假设有这么一个场景,通过SSL连接接收和发送数据,并统计所有的网络流量。
     
    对于接收而言,SessionFilter的顺序应该是:
    Received Packet --> StatisticFilter  --> SSLFilter --> Application Packet
    这样统计到的是真实的接收流量,应用拿到的也是解码后的数据。
     
    对于发送而言,SessionFilter的顺序则应该是:
    Application Packet --> SSLFilter --> StatisticFilter --> Send Packet
    可以看到,对于发送和接收而言,这两个Filter的顺序是相反的。如果这个场景再复杂一些,数据发送前需要先进行压缩,接收到数据后再解压缩,那么从发送到接收的整个流程是(红色是发送,蓝色是接收):
    Application --> ZipFilter --> SSLFilter --> StatisticFilter --> Network --> StatisticFilter --> SSLFilter --> ZipFilter --> Application
    可以看到在这些场景中,Filter在处理发送和接收事件上顺序是相反的。而在以前Cindy的设计里面,对于所有事件的分发都是按照相同的顺序,这可能造成困惑。
     
    目前我的想法是,针对应用层的Filter,将所有的send/sent事件分发按照Filter添加的顺序反向分发,其他的事件则按照添加顺序分发。但这种做法缺乏灵活性,因为这暗示了所有Filter需要的就是这种顺序(虽然目前除了系统级的Filter外,我还没想到什么样的Filter处理发送和接收事件的顺序需要保持一致)。另一种做法可以把所有事件都拆分开,指定每个事件的处理顺序,但这种做法言应该会过于繁琐。
     
    目前Cindy内系统级Filter对发送和接收事件分发的顺序需要保持一致。如果在上面这个流程图中加入系统级Filter,那么整个顺序是这样的(红色代表系统级Filter)
    发送:Application --> ExecutorFilter --> ZipFilter --> SSLFilter --> StatisticFilter --> PacketDecoderFilter --> SessionHandlerFilter
     
    接收:Network --> ExecutorFilter --> StatisticFilter --> SSLFilter --> ZipFilter --> PacketDecoderFilter --> SessionHandlerFilter
    虽然这种全部反向分发接收事件的做法还有待商榷,但可以肯定的是,该做法比原来所有顺序一致的做法要好,因为反向分发至少覆盖了大部分的应用类型。
     
    如果你来设计,你会怎么做呢?