【编程】3.5 重叠I/O模型【以面向对象风格重写《windows网络与通信程序设计》书中案例】

最近在亚马逊上看到王艳平的《Windows网络与通信程序设计(第2版)》的书评得分很糟糕,其中一位甚至认认真真地码了几百字来嘲讽这本书。评论者说这本书对函数参数的解释不够详细,我无法认同。在我看来,王艳平解释问题的能力堪称精湛,一个变量/函数/参数如果会被用到,那关于它的讲解也一定就在前面或附近。

书评者以dwFlags的解释只有“标记”两字为由发难,碰巧我改写的这一章里就有这个dwFlags,这个参数是调用某些函数所必需的,但实际上书中的例子并不需要发挥这个dwFlags参数的功能,创建它只是因为函数要它,我们不得不创建,仅此而已。为这样一个变量,我们也要铺展开来介绍一番吗?这是一本涵盖范围非常广泛的入门教材,对函数介绍到何种程度即可是需要有所取舍的。王艳平在书中添加大量完整代码,省略入门中不会用到的函数的其他用法,随书光盘中有无需复杂环境设置即可编译运行的示例代码,这对我们这些彻底的网络新手们来说都是非常受用的,可谓极致体贴。相反,如果这本书把完整代码从书中删去,只保留核心代码,函数介绍到msdn手册一般,那这就不是入门教材了。你知道一个新手捧着一本高端宝典却被某个简单到作者觉得不值一提的环境配置和初始化问题搞得焦头烂额时的心情吗?

讽刺书评中我唯一不得不承认的就是王艳平的代码确实C风格严重,初学者要理清其中全部逻辑很花时间。我试着理解3.5节的重叠I/O模型,然后把它改写成了面向对象的C++风格,算上排错,一共花掉了我大概12个小时左右。其中完整理解代码花的时间最多,超过6小时吧。

这个速度是够慢的了,不过入门嘛,哪有一上来就NB的道理,这个速度我可以接受。

趁我还记得,接下来说一下我改写这一章的经验。

1. 用STL的list和map替换掉王艳平自己写的链表结构和映射查找和维护逻辑。

尤其是那个event和BufferObj之间的映射维护逻辑,RebuildArray函数在不确定的地点不定期地维护这个映射表,从设计上来看是很“丑”的,很影响代码的可读性,这个维护行为不应该这样暴露出来。

2. Accept、Receive和Send行为都需要缓冲区,都要持有一个SocketObj,都有post和handle动作,而且都和event对象有一个一一映射的表。在原代码中,王艳平让BUFFER_OBJ持有一个身份类型(type),然后用switch根据对象持有的身份来执行不同动作,这很像一种手工维护的多态行为。加上event到三种缓冲区的映射,可以用map< event的类, 三种行为的父类>这样管理,所以我决定把这里设计成三个子类继承一个父类BufferObj的样子。

3. 原代码中在一个socket上的来回读写行为是来回复用同一个struct的,这样就省去了创建和销毁的开销。可我这个面向对象化的工程里把一切都做成了对象,如果每传1KB信息都要创建销毁一次对象,那我的工程性能也未免比原版差太多了吧!这可不行!所以我添加了一个对象缓冲池,用过的对象不销毁,而是存起来。创建的时候优先从缓冲池里取对象翻新了拿来用,缓冲池空了才会创建新对象。

为了实现这个效果,我把构造函数和析构函数都隐藏了。这种设计我以前没做过,写完了也不知道这样到底好不好,说实话是有点过度设计的味道的。不过因为这个缓冲池的存在,我不用释放event对象了,也就不需要像原程序那样不定期地维护event数组到BufferObj对象的映射表了,也就是说,我的程序里是完全不需要RebuildArray那个不好理解的行为的!不管缓冲池最初是不是过度设计,反正从结果上来看,这个决定是非常正确的。

4. 我真是java程序写多了,对一个对象最后会死在哪里一点概念都没有。我在学校写的C++游戏一只有内存泄漏的问题,我管都管不过来。这次我硬着头皮检查了各个对象的生命周期,确保每个对象都可以正确析构了。嗯,理论上是这样的……但愿是这样的吧……Orz

5. 我在程序里滥用了几个throw、try catch什么的,不知道对效率有什么样的影响……

最后附上VS2010的工程压缩包。没有VS2010也不要紧,王艳平的代码的NB之处在于它不依赖其他环境。那几个源文件就是全部。就算没有工程,你新建一个,然后把源文件拖进工程里,F5直接就可以编译运行!王艳平啊王艳平,你简直是新人福音!我一定要拜读阁下的每一部作品~!(最新一本好象是图像识别的书?这个有点难度啊……)

附件:

改写后的面向对象版3.5节示例代码 VS2010可执行工程压缩包下载

Comments

  1. 你们学计算机的太烦人啦,老写这种让人看不懂的专业技术文章。。等着,我下一篇日志就写个关于实现某个压缩算法的MFC出来。。>_<

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注