本文同步自游戏人生
o *__ 序 __* o
在阅读ACE代码和C++NPv1, v2, APG的时候,我意识到一个问题:虽然稍有C++和网络基础的同学都可以读懂ACE,但如果你对OS(五大管理模块都包含在内)、TCP/IP、C++、Design Patterns了解越多,你就越能体会ACE为什么需要这么庞杂,虽然它不够完美(但至少我还没有资格来批评这一点,我现在最常想做的一个动作就是五体投地)。
而且我隐约感觉到,我现在所写的很多东西在以后(对于有些人或许就是现在)看来会相当不深刻、相当不严谨,但对于一段学习历程,这个过程是必然的、必需的。
在C++NPv1中,Douglas C. Schmidt把原始socket及其API的缺陷有些妖魔化了,比如一段加上注释、空行在内的35行的代码,被指出有10处错误之多。这就像很多其他语言的倡导者或反传统C/C++指针者在批评指针时的说法一样。长期使用原始socket和指针的同学对此感觉很不舒服,何况socket API提供了大量错误检测的接口,至多是不够友好罢了。你好就好了,没必要抓住别人一顿痛批吧,『本是同根生,相煎何太急』。
虽然Solaris、Linux的很多版本及Windows对起源于Berkeley的socket API进行了重写,但不可否认,由于历史原因和POSIX标准的存在,对于使用者而言,我们可以无视这些API的实现差异。只是一旦我们从socket通信扩展到其他IPC通信的话,就需要正视各种I/O细节的差异了。
由于UNIX中,对于socket, file, pipe, device的大多数操作,描述符都是通用的(这一点,OS上面讲的更清楚些)。而Windows中,句柄大多不能互换(socket对于MS来说是舶来品)。系统和标准的不一致导致地址、协议和API的混杂甚至混乱。
UNIX下的描述符和Windows的句柄可以看作是同一个概念,只是应用环境不一样,所描述的内容也时常不一样,再简单了说,它们都是一个整型的ID。
ACE的源码中使用了大量预处理指令,尤其在跨平台/编译环境的部分更加明显。鉴于C/C++标准的博大胸怀,有些指令需要阅读相关编译器提供的帮助文档:
o #pragma: GCC, MSVC
o #define (#, #@, ##) : GCC, MSVC
其中有若干代码文件以.inl为后缀,里面是对部分函数的内联实现,以使代码结构看上去更加简洁。如果确定使用内联函数的话,*.inl将被包含于*.h的最后,如果不使用,则像*.h一样,包含于*.cpp的头部。
ACE采用doxygen输出文档,在阅读代码注释时能够感受到差异,但基本不会影响阅读。
o * __ 关于第3章(C++NPv1)__ * o
ACE抽象的地址类ACE_Addr拥有ACE_DEV_Addr, ACE_FILE_Addr, ACE_INET_Addr, ACE_SPIPE_Addr, ACE_UNIX_Addr五个子类。对于狭义上的网络通信(TCP/IP)而言,ACE_INET_Addr对应于我们熟悉的sockaddr_in。
ACE_IPC_SAP是IPC(interprocess communication)I/O操作类的root类。
从编码的角度看,这个类漂亮的地方在于示例了抽象类的另一种实现方式。
一提到抽象类,大多数人的第一反应是pure virtual function。当一个基类确定需要使用virtual function时,这是一个不错的选择。但我们都知道虚拟函数有开销。而且对于一个结构简单的抽象基类和其继承子类(尤其是大量使用时),一个虚函数表带来的开销会让整个设计显得十分蹩脚。
我们都知道如何强制让一个类无法使用default constructor(protected)。如果对基类使用该方法,仅使子类具有public的default constructor,这就达到了定义抽象基类的效果。
virtual destructor的意义在于防止delete父类指针(指向子类对象)时未调用子类destructor。在此例中,为避免这种情况,同样将destructor声明为protected即可。
从设计实现的角度看,相较于socket API,ACE_IPC_SAP的子类ACE_SOCK提供了编译时对句柄合法性的检测。
从逻辑功能层面划分,socket有三种角色:
o active connection role (connector):主动连接
o passive connection role (acceptor):被动连接
o communication role (stream):数据通信
但socket API毕竟不是OOD出来的,对于一个socket描述符,也完全没有必要去限制其担负的功能,更不可能搞成三种不同的socket。而OOD的ACE则可以轻易实现对socket对象及其操作的封装。
工厂类ACE_SOCK_Connector是一个主动创建通信端的工厂类。socket API中的connect接口只是为一个socket建立与其它peer的网络连接,而不产生新的socket实例,也不依赖于任何其它socket。同样,ACE_SOCK_Connector只是为一个ACE_SOCK_Stream对象(对用于数据通信的socket的封装)连接到ACE_Addr(对struct sockaddr的封装)提供接口,也不含对ACE_SOCK_Stream对象的其它操作。
工厂类ACE_SOCK_Acceptor是一个被动创建通信端的工厂类。当监听到新的网络连接后,为该连接初始化一个ACE_SOCK_Stream对象。和connector不同的是,acceptor依赖于一个已经存在的充当监听功能的socket句柄(ACE_SOCK),因此,ACE_SOCK_Acceptor是ACE_SOCK的一个子类。
ACE_SOCK_Stream是只负有通信传输功能的socket,对应connection-oriented的TCP通信格式stream,和UDP的CE_SOCK_CODgram相呼应。ACE_SOCK_Stream只是socket的通信载体,在两个工厂ACE_SOCK_Connector和ACE_SOCK_Acceptor中初始化。这样一个类除支持最基本的数据发送(send)和接收(recv)和阻塞(blocking)、非阻塞(nonblocking)及定时(timed)的I/O模式外,还支持分散读取(scatter-read)和集中写入(gather-write)。
对于一个简单的『网络课程作业:写一个有连接的IM小程序』,上面这些内容已经足够了。当然即使使用对应的几个socket API也已经足够了。但我们显然更加关心如此庞大的一个库,是如何解决复杂的网络应用的,我尤其关心的是多线程并发如何更好的处理。
所以,我准备跑到第8、9章了。