ACE技术论文集-第3章 高效可移植和灵活的网络编程的C++包装
发布: 2008-6-13 14:57 | 作者: Prashant Jain and D | 来源: 转载 | 查看: 597次
IPC SAP、ACE_SOCK和ACE_LSOCK类锚定继承层次,并使应用能够进行后续的派生和代码共享。这些类的对象不能被实例化,因为它们的构造器被声明在类定义的protected区域中。
IPC SAP:该类是进程间通信机制C++包装的IPC SAP层次的根。它提供所有IPC SAP(也就是,SOCK SAP、TLI SAP、SPIPE SAP和FIFO SAP)组件共有的机制。例如,它提供了方法,可将句柄设置为非阻塞模式,或者启用异步的、信号驱动的I/O。
SOCK:该类是SOCK SAP层次的根。它提供所有其他类共有的机制,比如打开和关闭本地通信端点,以及处理选项(像选择socket队列大小及启用组通信)。
LSOCK:该类提供的机制允许应用在本地主机(因而有前缀’L’)上的不相关进程间发送和接收已打开的文件句柄。注意系统V和BSD UNIX都支持这一特性,而Windows NT则不支持。其他类从ACE_LSOCK继承以获得这一功能。
SOCK SAP在网络地址格式和通信语义的基础上区分ACE_LSOCK* 和ACE_SOCK*。特别地,ACE_LSOCK* 类使用UNIX路径名作为地址,并且仅允许机器内的IPC。而另一方面,ACE_SOCK* 类使用Internet协议(IP)地址和端口号,并同时允许机器内和机器间的IPC。
客户和服务器间的不对称连接角色是通信软件的典型情况。通常,服务器被动地侦听客户主动发起的连接[20]。下面的面向连接的SOCK SAP类捕捉了被动/主动的连接建立的结构和数据传输关系:
ACE_SOCK_Acceptor和ACE_LSOCK_Acceptor:这两个类是被动地建立新通信端点、以响应主动连接请求的工厂[21]。两者分别生成ACE_SOCK_Stream和ACE_LSOCK_Stream连接端点对象。
ACE_SOCK_Connector和ACE_LSOCK_Connector:这两个类是主动地建立新通信端点的工厂。它们建立与远地端点的连接,并在连接建立时生成适当的*Stream对象。连接可以被同步地或异步地发起。两个工厂分别生成ACE_SOCK_Stream和ACE_LSOCK_Stream连接端点对象。
注意*Acceptor和Connector类不提供发送和接收数据的方法。相反,它们是生成下面描述的*Stream数据传输对象的工厂。使用强类型的工厂接口可以在编译时检测和防止本地和非本地*Stream对象的偶然误用。相反,socket接口仅能在运行时检测这些类型不匹配。
尽管建立连接需要区分主动和被动角色,一旦连接建立,数据就可以根据端点所用的协议以任意的顺序来进行交换。SOCK SAP在下面的类中隔离了数据传输行为:
ACE_SOCK_Stream和ACE_LSOCK_Stream:这些类由上面描述的*Acceptor或*Connector工厂创建。*Stream类为在两个进程间传输数据提供机制。ACE_LSOCK_Stream对象在同一主机上的进程间交换数据;ACE_SOCK_Stream对象在可驻留在不同主机上的进程间交换数据。
被重载的send和recv *Stream方法提供标准的UNIX write和read语义。因而,send或recv分别读或写的字节数可能会少于所请求的字节数。这些“短写”(short-writes)或“短读”(short-reads)之所以发生,是由于OS中的缓冲和传输协议中的流控制。为减少编程工作,*Stream类提供send_n和recv_n方法,允许传输和接收正好n个字节。另外还提供了“分散读”和“集中写”方法,以高效地同时发送和接收多个数据缓冲区。
本论文聚焦于面向连接的流通信,但是,socket接口也提供无连接的服务,它使用Internet协议组中的IP和UDP协议。IP和UDP是不可*的数据报服务,不保证特定的消息会到达它的目的地。无连接服务被用于那些可容忍一定程度的丢失的应用(比如rwho看守[6])。此外,IP和UDP还提供像TCP和Sun RPC这样的较高级可*协议的基础。
SOCK SAP socket包装通过下面的类来封装socket数据报通信:
ACE_SOCK_Dgram和ACE_LSOCK_Dgram:这两个类为在运行在本地和/或远地主机上的进程间交换数据报提供机制。不像下面描述的有连接数据报,每个send和recv操作都必须为发送或接收数据报提供服务地址。ACE_LSOCK_Dgram同时继承ACE_SOCK_Dgram和ACE_LSOCK的所有操作。它仅在同一主机上的进程间交换数据报。而ACE_SOCK_Dgram类可以在本地和/或远地主机上的进程间交换数据报。
ACE_SOCK_CODgram和ACE_LSOCK_CODgram:这两个类提供一种“有连接数据报”机制。不像上面所描述的无连接类,这两个类允许send和recv操作在交换数据报时省略服务地址。注意有连接数据报机制只是一种语法上的方便,因为没有其他的语义与数据传输相关联(也就是,数据递送还是不可*的)。ACE_SOCK_CODgram的机制从 ACE_SOCK基类继承。ACE_LSOCK_CODgram同时继承ACE_SOCK_CODgram和ACE_LSOCK(它提供传递文件句柄的能力)的机制。
标准的TCP和UDP通信是点对点的。但是,有些应用可从提供组通信的更为灵活的递送机制中获益。因此,下面的类封装了Internet协议组提供的多点传送和广播协议:
ACE_SOCK_Dgram_Mcast:该类提供的机制用于将UDP数据报多点传送给运行在本地子网中的本地和/或远地主机上的进程。该类的接口支持将数据报多点传送给特定的多点传送组。该类还将开发者与有效利用多点传送所需的低级细节屏蔽开来。
ACE_SOCK_Dgram_Bcast:该类提供的机制用于将UDP数据报广播给本地子网中的本地和/或远地主机。该类的接口支持将数据报广播给(1)所有与主机相连的网络接口,或是(2)一个特定的网络接口。该类还将开发者与有效利用广播所需的低级细节屏蔽开来。
ACE_SOCK_Dgram_Bcast类在下面用于将一个消息广播给LAN子网中在指定端口号上侦听的所有服务器:
int main (int argc, char *argv[])
{
ACE_SOCK_Dgram_Bcast b_sap (ACE_Addr::sap_any);
char *msg;
u_short b_port;
msg = argc > 1 ? argv[1] : "hello world\n";
b_port = argc > 2 ? atoi (argv[2]) : 12345;
if (b_sap.send (msg, strlen (msg), b_port) == -1)
perror ("can’t send broadcast");
return 0;
}
将这个简洁的例子与直接使用socket接口实现广播所需的成打的C源码行相比较富有启发意义。
设计一种高效而通用的网络寻址接口是困难的。困难源于用一种节省空间且统一的接口来表示不同的网络寻址格式的企图。不同的地址格式要存储以不同大小表示的不同类型的信息。
例如,Internet域的服务(比如ftp或telnet)用两个字段来标识:(1)一个四字节的IP地址(唯一地标识遍及Internet的远地主机),以及(2)一个两字节的端口号(用于将到来的协议数据单元多路分离给适当的客户或是在远地主机上的服务器进程)。相反,UNIX域的socket通过UNIX路径名(长度最多可以到108字节,并只在单个本地主机上有意义)来会合。
现有的由socket接口提供的基于sockaddr的网络寻址结构是麻烦而易错的。它要求开发者明确地把地址结构中的所有字节清零。相反,图3-6所示的SOCK SAP寻址类含有用于操作网络地址的机制。
Addr基类的构造器确保所有的字段被自动地正确初始化。而且,在不同的地址族间存在的不同的大小、格式和功能被封装在派生的地址子类中。这使得开发者更容易对网络寻址方案进行扩展、以包括新的通信域。例如,UNIX Addr子类与ACE_LSOCK*类相关联,ACE_INET_Addr子类与ACE_SOCK*和ACE_TLI*类相关联,还有SPIPE Addr子类与SPIPE SAP中的STREAM管道包装相关联。
TLI SAP类属提供系统V传输层接口(TLI)的C++接口。TLI的TLI SAP继承层次几乎与socket的SOCK SAP C++包装类相同。主要的差异是TLI和TLI SAP没有定义UNIX域协议族的接口。通过使C++特性(比如缺省参数值和模板)与tirdwr(read/write兼容性STREAM模块)相联合,开发可在编译时参数化、以在socket或TLI网络编程接口上正确运行的应用变得相对直截了当了。
下面的代码演示怎样应用模板来参数化应用所使用的IPC机制。该代码是从[22]描述的分布式日志工具中提取的。在下面的代码中,用一种特定类型的网络编程接口和相应的协议地址类对一个派生自Event Handler的子类进行了参数化:
// Logging_Handler header file.
template
class Logging_Handler : public Event_Handler
{
public:
Logging_Handler (void);
virtual ?Logging_Handler (void);
virtual int handle_close (int);
virtual int handle_input (int);
virtual int get_handle (void) const
{
return this->xport_sap.get_handle ();
}
protected:
PEER_STREAM xport_sap;
};
取决于底层OS平台(比如说是基于BSD的SunOS 4.x,还是基于系统V的SunOS 5.x)的特定属性,日志应用可以实例化Client Handler类,以使用SOCK SAP或TLI SAP。如下所示:
#if defined (MT_SAFE_SOCKETS)
typedef ACE_SOCK_Stream PEER_STREAM;
#else
typedef ACE_TLI_Stream PEER_STREAM;
// Logging application.
#endif // MT_SAFE_SOCKETS.
class Logging_Handler :
public Logging_Handler
{
// ...
};
在开发运行在多种OS平台上的可移植应用时,模板所提供的增强的灵活性是有用的。例如,在跨越SunOS平台的多种变种时,能使用网络编程接口来对应用进行参数化的能力是必需的。特别地,SunOS 5.2中的socket实现不是线程安全的,而SunOS 4.x中的TLI实现含有许多严重的缺陷。
TLI SAP还将应用与TLI接口的许多特性屏蔽开来。例如,在qlen > 1的并发服务器中,[5],ACE_TLI_Acceptor类的accept方法封装了处理t_listen和t_accept的非直观而又易错的行为所需的微妙的应用级代码。accept方法被动地建立客户连接请求。通过使用C++缺省参数值,对于基于TLI SAP和基于SOCK SAP的应用来说,调用accept方法的标准方法在语法上都是一样的。
SPIPE SAP类属为已安装STREAM管道和connld[17]提供一种C++包装接口。SPIPE SAP继承层次是SOCK SAP和TLI SAP所用的层次的镜像。它提供与SOCK SAP ACE_LSOCK* 类(它们封装的是UNIX域的socket)相类似的功能。但是,SPIPE SAP比ACE_LSOCK* 接口更灵活,因为它使STREAM模块可以分别被“压入”或“弹出”SPIPE SAP端点。SPIPE SAP还支持在运行在同一主机上的进程和/或线程间的字节流和按优先级排序的消息数据的双向递送[16]。
FIFO SAP类属封装UNIX FIFO机制。
