ACE技术论文集-第3章 高效可移植和灵活的网络编程的C++包装
发布: 2008-6-13 14:57 | 作者: Prashant Jain and D | 来源: 转载 | 查看: 597次
这一部分描述下列贯穿SOCK SAP类属所应用的设计原则:
-在编译时强制实现类型安全性
-允许受控的类型安全性违例
-为常见情况进行简化
-用层次类属替代一维的接口
-通过参数化类型增强可移植性
-内联性能关键的方法
-定义辅助类隐藏易错细节
尽管这些原则已广为人知,并被广泛应用于像图形用户接口这样的领域中,但在通信软件领域中它们还没有被那么广泛地应用。
在3.4讨论的socket的若干局限源于在其接口中缺乏类型安全性。为强制实现类型安全性,SOCK SAP确保它所有的对象都通过构造器适当地初始化。此外,为防止偶然的类型安全性违例,只允许对SOCK SAP对象进行合法的操作。后一点已在图3-14所示的echo_server的SOCK SAP修订版进行演示。该版本更正了图3-3中所标识出的的socket和C的问题。因为SOCK SAP类是强类型的,非法操作在编译时、而不是运行时被拒绝。例如,不可能在ACE_SOCK_Acceptor连接工厂上调用recv或send方法,因为这些方法不是其接口的一部分。同样地,返回值只用于传达操作的成功或失败,从而减少了在赋值表达式中误用的潜在可能性。
int echo_server (ACE_INET_Addr s_addr)
{
// Initialize the passive mode server.
ACE_SOCK_Acceptor acceptor (s_addr);
// Data transfer object.
ACE_SOCK_Stream peer_stream;
// Client remote address object.
ACE_INET_Addr peer_addr;
// Accept a new connection.
if (acceptor.accept (peer_stream, &peer_addr) != -1)
{
char buf[BUFSIZ];
for (size_t n; peer_stream.recv (buf, sizeof buf, n) > 0;)
// Handles "short-writes."
if (peer_stream.send_n (buf, n) != n)
// Remainder omitted.
}
}
图3-14 Echo服务器的SOCK SAP修订版
该原则通过IPC SAP根类所提供的get_handle和set_handle方法进行例示。这两个方法分别提取和指派底层的句柄。通过提供get_handle和set_handle,IPC SAP允许应用在必须与需要句柄的UNIX系统调用(比如select)协作时直接绕过IPC SAP的类型检查机制。陈述此原则的另一方式是“让SOCK SAP的正确使用更容易,不正确使用更困难,但不是不可能以类设计者没有预见到的方式来使用它。”
此原则以下列途径应用于ACE C++ socket包装类中:
为常用方法参数提供缺省值:例如,ACE_SOCK_Connector构造器有六个参数:
ACE_SOCK_Connector(ACE_SOCK_Stream &new_stream,
const ACE_SOCK_Addr &remote_sap,
ACE_Time_Value *timeout = 0,
const ACE_SOCK_Addr &local_sap = (ACE_SOCK_Addr &) Addr::sap_any,
int protocol_family = PF_INET,
int protocol = 0);
但是,在调用与调用间通常只有前两个是变化的:
ACE_SOCK_Stream stream;
// Compiler supplies default values.
ACE_SOCK_Connector con (stream, ACE_INET_Addr (port, host));
// ...
因此,为简化编程,在ACE_SOCK_Connector中给出了其他参数的缺省值,以使程序员无需每次都提供它们。
定义节俭的接口:此原则让使用一种特定抽象的代价局部化。IPC SAP限定应用开发者所必须记忆的细节数量。它为开发者提供群集的类,执行不同类型的通信(比如面向连接的 vs. 无连接的)和不同的连接角色(比如主动的 vs. 被动的)。为减少犯错的机会,ACE_SOCK_Acceptor类只允许为程序而应用的操作扮演被动角色,而ACE_SOCK_Connector只允许为程序而应用的操作扮演主动角色。此外,与使用高度通用的UNIX sendmsg/recvmsg函数相比,使用ACE_SOCK_SAP来发送和接收打开的文件句柄有着一个简单得多的接口。例如,使用ACE_LSOCK* 类来传递socket句柄是非常简洁的:
ACE_LSOCK_Stream stream;
ACE_LSOCK_Acceptor acceptor ("/tmp/foo");
// Accept connection.
acceptor.accept (stream);
// Pass the Socket handle back to caller.
stream.send_handle (stream.get_handle ());
与此相比较,使用socket接口来实现所需的代码:
int n_sd;
int u_sd;
sockaddr_un addr;
u_char a[2];
iovec iov;
msghdr send_msg;
u_sd = socket (PF_UNIX, SOCK_STREAM, 0);
memset ((void *) &addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
strcpy (addr.sun_path, "/tmp/foo");
bind (u_sd, &addr, sizeof addr.sun_family + strlen ("/tmp/foo"));
listen (u_sd, 5);
// Accept connection.
n_sd = accept (u_sd, 0, 0);
// Sanity check.
a[0] = 0xab; a[1] = 0xcd;
iov.iov_base = (char *) a;
iov.iov_len = sizeof a;
send_msg.msg_iov = &iov;
send_msg.msg_iovlen = 1;
send_msg.msg_name = (char *) 0;
send_msg.msg_namelen = 0;
send_msg.msg_accrights = (char *) &n_sd;
send_msg.msg_accrightslen = sizeof n_sd;
// Pass the Socket handle back to caller.
sendmsg (n_sd, &send_msg, 0);
将多个操作组合进单一操作:创建一个传统的被动模式socket需要多个调用:
int s_sd = socket (PF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
memset (&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons (port);
addr.sin_addr.s_addr = INADDR_ANY;
bind (s_sd, &addr, addr_len);
listen (s_sd);
// ...
相反,ACE_SOCK_Acceptor是一个用于被动连接建立的工厂,它的构造器执行创建被动模式侦听者端点所需的socket调用:socket、bind和listen。因此,应用只需简单地如下编写:
ACE_INET_Addr addr (port);
ACE_SOCK_Acceptor acceptor (addr);
此原则涉及使用层次相关的类属来重构现有的一维socket接口(如图3-9所示)。用于构造SOCK SAP类属的准则涉及标识、群集和封装相关的socket函数,以最大化复用和类组件的共享。
继承支持SOCK SAP类属的不同功能子集。例如,不是所有的操作系统都支持传递打开的文件句柄(例如,Windows NT)。因而,可以在继承层次中省略ACE_LSOCK类(在3.6.1中描述),而不会影响SOCK SAP设计中的其他类的接口。
继承还增强代码复用和改善模块性。基类表示类属组件间的相似性,而派生类表示差异性。例如,IPC SAP设计向着IPC SAP和SOCK SAP基类中的继承层次的“根”放置了共享的机制,包括用于打开/关闭和设置/取回底层socket句柄的操作,以及对所有派生的SOCK SAP类来说都通用的特定的选项管理函数。而位于继承层次“底部”的子类实现专门的操作,为所提供的通信类型(比如流 vs. 数据报通信,或本地 vs. 远地通信)而进行定制。这样的方法避免了不必要的代码重复,因为更为专门的派生类会复用继承层次的根上所提供的更为通用的机制。
template
int echo_server (ACCEPTOR::PEER_ADDR s_addr)
{
// Initialize the passive mode server.
ACCEPTOR acceptor (s_addr);
// Data transfer object.
ACCEPTOR::PEER_STREAM peer_stream;
// Remote address object.
ACCEPTOR::PEER_ADDR peer_addr;
// Accept a new connection.
if (acceptor.accept (peer_stream, &peer_addr) != -1)
{
char buf[BUFSIZ];
for (size_t n; peer_stream.recv (buf, sizeof buf, n) > 0;)
if (peer_stream.send_n (buf, n) != n)
// Remainder omitted.
}
}
图3-15 Echo服务器的模板版本
通过允许经由参数化类型来整个地替换网络编程接口,用C++类(而不是独立的C函数)包装socket有助于改善可移植性。参数化类型使应用与对特定的网络编程接口的依赖去耦合。图3-15通过将echo_server修改成为C++函数模板来演示这一技术。取决于底层OS平台的特定属性(比如它更为高效地实现了TLI还是socket),echo_server可以通过SOCK SAP 或TLI SAP类来实例化。如下所示:
// Conditionally select IPC mechanism.
#if defined (USE_SOCKETS)
typedef ACE_SOCK_Acceptor ACCEPTOR;
#else // USE_TLI
typedef ACE_TLI_Acceptor ACCEPTOR;
#endif // USE_SOCKETS.
const int PORT_NUM = 10000;
int main (void)
{
// ...
// Invoke the echo_server with appropriate
// network programming interfaces. Note the
// use of template traits for addr class.
ACCEPTOR::PEER_ADDR addr (PORT_NUM);
echo_server (addr);
}
一般而言,比起其他一些传统方法,比如实现多个版本或在源码中到处使用杂乱的条件编译指令,使用参数化类型要更没有侵入性并且更可扩展一点。
例如,SOCK SAP和TLI SAP类提供同样的OO接口(在图3-7中描述)。特定的OS平台可能拥有不同的底层网络编程接口,比如有socket,但没有TLI,或反之亦然。使用IPC SAP,应用可被透明地编写,通过SOCK SAP或TLI SAP来进行参数化。C++模板支持“类型一致”(type conformance)的一种松散形式,并不强迫接口包含所有可能的功能。相反,模板被用于参数化仔细设计的应用代码,用以调用不同通信抽象的通用方法子集(例如,open、close、send、recv,等等)。
模板提供的类型抽象改善了支持不同网络编程接口(比如socket或TLI)的平台间的可移植性。例如,对于开发跨越多种SunOS平台的应用来说,对网络编程接口的参数化是有用的。SunOS 5.2中的socket实现不是线程安全的,而SunOS 4.x中的TLI实现含有许多严重的缺陷。
为鼓励开发者用C++包装替换现有的低级网络编程接口,SOCK SAP实现必须高效地运作。为确保这一点,在关键的性能路径上的方法(比如ACE_SOCK_Stream recv和send方法)被指定为C++内联函数,以消除运行时函数调用开销。内联在时间和空间上都是高效的,因为这些方法非常短小(每个方法大约2或3行)。内联的使用意味着应该保守地使用虚函数,因为大多数当代的C++编译器不能充分地把虚函数开销优化掉。
socket寻址的C接口是难以使用和易错的。很容易忽略sockaddr_in的清零或把端口号转换到网络字节序。为使应用与这些低级细节相屏蔽,IPC SAP定义了Addr类层次(如图3-6所示)。该层次通过类型安全的C++接口支持若干不同的网络寻址格式。Addr层次消除了与直接使用基于C的struct sockaddr数据结构族相关联的常见编程错误。例如,ACE_INET_Addr的构造器自动将sockaddr寻址结构清零,并将端口号转换为网络字节序。如下所示:
class ACE_INET_Addr : public ACE_Addr
{
public:
ACE_INET_Addr::ACE_INET_Addr (u_short port, long ip_addr = 0)
{
memset (&this->inet_addr_, 0, sizeof this->inet_addr_);
this->inet_addr_.sin_family = AF_INET;
this->inet_addr_.sin_port = htons (port);
memcpy (&this->inet_addr_.sin_addr, &ip_addr, sizeof ip_addr);
}
private:
sockaddr_in inet_addr_;
};
IPC SAP提供一个OO C++包装族,封装在当代的操作系统上可用的标准本地和远地IPC机制。通过使编写正确、紧凑、可移植和高效的代码变得更为容易,这些封装的接口简化了通信软件的开发。此外,包装方法还便利了向C++的有组织的迁移,通过(1)逐步对开发者进行OO设计原理教学,以及(2)有效利用现有的非C++语言的代码库。本论文通过描述使用C++实现IPC SAP的若干优点和缺点、以及概述将来进一步对IPC SAP的使用进行探究的论文来作为结束。
使用C++的优点和缺点:用C++开发包装的主要优点包括:
-封装变种:类隐藏寻址格式中的差异,比如Internet vs. UNIX域寻址。此外,它们还在不同的类中封装不同的接口行为。例如,ACE_SOCK_Acceptor对象的接口为服务器操作而特别作了剪裁。
-增强功能子集划分:继承使定义功能子集变得更为容易。例如,ACE_LSOCK类可在不支持文件句柄传递的操作系统上被忽略。
-更高的可移植性:模板使得不同的IPC机制可被参数化进应用,从而改善了跨平台可移植性。
C++的一个缺点是它缺少可移植的异常处理。在适当使用时,C++异常处理有助于简化错误恢复,并改善类型安全性。例如,如果ACE_INET_Addr构造器因为远地地址没有对应到有效的主机而失败的话,就可以扔出一个异常。但是,如果没有C++异常处理,就有可能在没有对IPC SAP对象进行正常初始化的情况下开始使用它。该问题将在ANSI/ISO C++异常处理机制可用于大多数OS平台时得以解决。
当前状况和未来主题:IPC SAP可在ACE构架[2]中找到。ACE支持的OS平台包括Win32(使用MSVC++和Borland C++的Win2000、WinNT 3.5.x、4.x、Win95和WinCE)、大多数版本的UNIX(SunOS 4.x和5.x;SGI IRIX 5.x和6.x;HP-UX 9.x、10.x和11.x;DEC UNIX 3.x和4.x、AIX 3.x和4.x、DG, /UX、Linux、SCO、UnixWare、NetBSD和FreeBSD)、实时操作系统(VxWorks、Chorus、LynxOS和pSoS),以及MVS OpenEdition。
ACE已被用于许多大学和公司的研究和开发项目。例如,ACE已在波音被用于构建实时航空控制系统[23];在Bellcore[22]、爱立信[24]、摩托罗拉[25]和朗讯被用于电信系统;在西门子[26]和柯达[27]被用于医学成像系统;以及在SAIC/DARPA的分布式模拟系统。它还被广泛地用于研究项目和课堂教学。
本论文中描述的所有源代码都可以在http://www.cs.wustl.edu/~schmidt/ACE.html找到。http://www.cs.wustl.edu/~schmidt/ACE-users.html描述使用ACE的许多项目。此外,comp.soft-sys.ace是专用于ACE相关主题的USENET新闻组。
[1] D. C. Schmidt, T. H. Harrison, and E. Al-Shaer, “Object-Oriented Components for High-speed Network Programming,” in Proceedings of the 1st Conference on Object-Oriented Technologies and systems, (Monterey,CA), USENIX, June 1995.
[2] D. C. Schmidt, “ACE: an Object-Oriented Framework for Developing Distributed Applications,” in Proceedings of the 6th USENIX C++ Technical Conference, (Cambridge, Massachusetts), USENIX Association, April 1994.
[3] M. K. McKusick, K. Bostic, M. J. Karels, and J. S. Quarterman, The Design and Implementation of the 4.4BSD Operating System. Addison Wesley, 1996.
[4] Sun Microsystems, Network Interfaces Programmer’s Guide, Chapter 6 (TLI Interface) ed., 1992.
[5] S. Rago, UNIX System V Network Programming. Reading, MA: Addison-Wesley, 1993.
[6] W.R.Stevens,UNIX Network Programming, Second Edition. Englewood Cliffs, NJ: Prentice Hall, 1997.
[7] H. Custer, Inside Windows NT. Redmond, Washington: Microsoft Press, 1993.
[8] Object Management Group, The Common Object Request Broker: Architecture and Specification, 2.2 ed., Feb. 1998.
[9] D. Box, Essential COM. Addison-Wesley, Reading, MA, 1997.
[10] S. Mungee, N. Surendran, and D. C. Schmidt, “The Design and Performance of a CORBA Audio/Video Streaming Service,” in submitted to the Hawaiian International Conference on System Sciences, Jan. 1999.
[11] A. Gokhale and D. C. Schmidt, “Measuring the Performance of Communication Middleware on High-Speed Networks,” in Proceedings of SIGCOMM ’96, (Stanford, CA), pp. 306–317, ACM, August 1996.
[12] OSI Special Interest Group, Transport Provider Interface Specification, December 1992.
[13] OSI Special Interest Group, Data Link Provider Interface Specification, December 1992.
[14] D. Ritchie, “A Stream Input–Output System,” AT&T Bell Labs Technical Journal, vol. 63, pp. 311–324, Oct. 1984.
[15] D. C. Schmidt, “IPC SAP: An Object-Oriented Interface to Interprocess Communication Services,” C++ Report,vol.4, November/December 1992.
[16] W. R. Stevens, Advanced Programming in the UNIX Environment. Reading, Massachusetts: Addison Wesley, 1992.
[17] D. L. Presotto and D. M. Ritchie, “Interprocess Communication in the Ninth Edition UNIX System,” UNIX Research System Papers, Tenth Edition, vol. 2, no. 8, pp. 523–530, 1990.
[18] A. Stepanov and M. Lee, “The Standard Template Library,” Tech. Rep. HPL-94-34, Hewlett-Packard Laboratories, April 1994.
[19] G. Booch, Object Oriented Analysis and Design with Applications (2nd Edition). Redwood City, California: Benjamin/Cummings, 1993.
[20] D. C. Schmidt, “Acceptor and Connector: Design Patterns for Actively and Passively Initializing Network Services,” in Workshop on Pattern Languages of Object-Oriented Programs at ECOOP ’95, (Aarhus, Denmark), August 1995.
[21] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
[22] D. C. Schmidt, “Reactor: An Object Behavioral Pattern for Concurrent Event Demultiplexing and Event Handler Dispatching,” in Pattern Languages of Program Design (J. O. Coplien and D. C. Schmidt, eds.), pp. 529–545, Reading, MA: Addison-Wesley, 1995.
[23] T. H. Harrison, D. L. Levine, and D. C. Schmidt, “The Design and Performance of a Real-time CORBA Event Service,” in Proceedings of OOPSLA ’97, (Atlanta, GA), ACM, October 1997.
[24] D. C. Schmidt and P. Stephenson, “Experiences Using Design Patterns to Evolve System Software Across Diverse OS Platforms,” in Proceedings of the 9th European Conference on Object-Oriented Programming, (Aarhus, Denmark), ACM, August 1995.
[25] D. C. Schmidt, “A Family of Design Patterns for Application-level Gateways,” The Theory and Practice of Object Systems (Special Issue on Patterns and Pattern Languages),vol.2, no. 1, 1996.
[26] P. Jain and D. C. Schmidt, “Service Configurator: A Pattern for Dynamic Configuration of Services,” in Proceedings of the 3rd Conference on Object-Oriented Technologies and Systems, USENIX, June 1997.
[27] I. Pyarali, T. H. Harrison, and D. C. Schmidt, “Design and Performance of an Object-Oriented Framework for High-Performance Electronic Medical Imaging,” USENIX Computing Systems, vol. 9, November/December 1996.
