软件开发: 05 2010存档

我原先的client端代码流程如下:  

创建一个socket
设为异步socket(fcntl)
将socket加入epoll
connect到远端(此时connect调用返回非0,但errno为EINPROGRESS,表示正在建立连接中)
epoll_wait之
捕获到EPOLLOUT事件,此时便认为connect已经成功,client端开始发消息

这个过程通常能够运转,但是线上环境复杂多变,如果发生这种情况:server进程调用listen开始侦听后,被gdb或信号挂住了,此时异步connect会怎样?很遗憾,client端的epoll_wait依然返回EPOLLOUT,甚至往此socket里发消息都返回成功,只有当发的消息多得占完了server端的tcp缓冲以后(窗口收缩到很小),send调用才开始失败。这时候用 losf -i 看网络连接也很有趣,client端的机器显示连接建立了,server端的却显示没有这个连接。
仔细想想,OS这样做是正确的,毕竟connect的语义只是“连接”,当server挂住时,连接还是能成功的,但你能不能往里面发消息那就是另外一回事了。

所以对于应用来说,异步socket想要知道connect后连接是不是可以正常收发数据了,还是要靠应用层的一问一答才能知道。


====== 2010.5.14 ======

昨天同事朱照远给了一个更正确的解决方案,可参考之:
收到EPOLLOUT也不能认为是TCP层次上connect(2)已经成功,要调用getsockopt看SOL_SOCKET的SO_ERROR是否为0。若为0,才表明真正的TCP层次上connect成功。至于应用层次的server是否收/发数据,那是另一回事了。”

最近参与一个大项目,几十个开发人员,代码都放在一个trunk下,也就是说,这几十个人的代码都在一起编译,一起跑单元测试,一起在半夜跑自动化测试用例。反正我以前是从未加入过这么大的trunk(之前我参加的项目最多就四五个人在一起写代码而已),现在发现巨大的trunk造成了一些很郁闷的问题。

首先,编译越来越慢。项目用scons来编译(我暂时还没发现scons比Makefile好在哪里),一编译就耗干系统的内存(我们的系统是8G内存),整个trunk重编译一次是两个多小时,好在我负责的只是其中一部分,但是即使是这一部分,也要花十多分钟编译(我目前还不能说这是scons慢造成的,但是python占内存偏多,嫌疑很大),这在我调试阶段很费时间。

其次,互相干扰很严重。几十个程序员,就算一个人每一个月才出现一次代码错误,那加起来就是:每天都有人至少犯一次代码错误。虽然使用了ReviewBoard,但是review不可能洞察一切,还是会有单元测试频繁的不过。后来加了一个提交代码的工具,为每个提交代码的人单独编译并跑单元测试,通过了才真正交给subversion,但这样还是有问题:每次提交,我都发现有很多人排着队的等前面的人编译完,我有几次好不容易排上了,跟别人的提交一起编译,结果由于别人的提交编译不过,我还得重新排队......问题的根源在于:这个trunk上的提交太过频繁了。毕竟几十个人呢,几乎每十分钟就有新的code需要提交。

可能长期在互联网公司干,我对那种动辄几百万行code,几十兆可执行文件的大软件越来越反感。我觉得好的软件,一定是满足且刚好满足了某一类用户的某一个需要,而不是妄图去满足所有用户的很多需要。说白了:尽量做小巧的东西。如果项目真的是个大项目,那最好是能拆成多个小软件,好歹代码能分到不同的trunk下去,trunk小了,挤在一个trunk下的开发人员就会少很多,就不会有漫长的编译和频繁的提交了,然后,这些小软件一起运行,完成某个大工作。

说得有点理想化了,把大项目拆成小部分,这个道理谁都懂,但难度在于怎么拆?这种问题我回答不了,也许《unix编程艺术》能够回答:不要写一个巨大的进程,应该写多个,然后一起工作。进程都可以拆开,代码有什么不能拆开的?有人可能担心效率,但是相比于混乱的管理,这点效率损失我觉得还算值得。

以上是我的看法。实际中,反例也是有的,比如windows NT就是一堆人挤在一个代码trunk下做出来的,而且产品还很成功。但是从《观止》里可以看出,NT的开发人员也一样被频繁的build break和单元测试fail折磨得人不人鬼不鬼,也许这就是为什么他们要辛苦上6年才能开发完成,以及为什么操作系统的未来是属于微内核的(微内核巧妙的把OS拆开了)。


====== 2010.5.10 ======

补充一下,每次编译,除了内存,CPU也被占满,登录同一台服务器的其他人连vi都用不了,到了后来,我在哪台机上编译,哪台机上的开发人员就埋怨,真成“嫌人”了;编译完后,20G的硬盘空间就没了,想只编译一部分是不行的,因为没拆开,整个trunk必须一起编译。

更可怕的是,这么大的trunk,还要打分支,一个160G的硬盘,才能下载编译几个分支呢?