对于大多数程序员而言,进程间通信(IPC)与使用Socket API是同义词。 套接字API最初是为UNIX®平台开发的,旨在在TCP / IP协议之上提供应用程序级别的接口。 它支持多种功能,其中一些功能如表1所示。
常用缩略语
API:应用程序编程接口 TCP / IP:传输控制协议/ Internet协议
表1.构成Socket API一部分的旧式C例程
C函数名称 提供的功能
socket 分配新的套接字句柄 bind 将套接字句柄与本地或远程地址相关联 listen 被动侦听传入的客户端连接请求的例程 connect 客户端计算机使用connect例程开始TCP握手; 服务器使用accept例程来接受连接请求 send 例行数据传输 recv 例行接收数据
使用本机Socket API存在几个问题。 首先,尽管API的遗留C函数几乎无处不在,但它们却不可移植。 例如,Windows®中的socket方法返回类型为SOCKET的句柄,而在UNIX中,相同的函数返回整数。 有些方法(如closesocket仅存在于Windows中,更不用说不兼容的标头了。 此外,本机API的许多错误仅在运行时出现,例如,地址或协议不匹配或未初始化的数据成员。
自适应通信环境(ACE)框架定义了解决这些问题的一组包装外观。 本文剖析了ACE在相同或不同主机之间为IPC提供的一些基于C++的,面向对象的类。
用于网络编程的ACE类
表2显示了ACE为TCP / IP连接定义的一些基本类。
表2. ACE中用于网络编程的类
ACE班 提供的功能
ACE_Addr ACE中的基类; 用于网络寻址 ACE_INET_Addr 源自ACE_Addr ; 用于Internet域名寻址 ACE_SOCK ACE套接字包装外观层次结构的基类: ACE_SOCK_Acceptor , ACE_SOCK_Connector等 ACE_SOCK_Acceptor 用于建立被动连接; 从概念上讲类似于Berkeley Software Distribution(BSD)的accept()和listen()例程 ACE_SOCK_Connector 在流对象和远程主机之间建立连接; 在概念上类似于BSD connect()例程 ACE_SOCK_Stream 用于处理面向TCP连接的数据传输的类
对于用户数据报协议(UDP)通信,使用ACE_SOCK_Dgram类及其变体。 声明这些对象的头文件位于您安装的$ ACE_ROOT / ace / include区域中。 相关标头被适当命名为SOCK_Acceptor.h,SOCK_Listener.h,SOCK_Stream.h,依此类推。
创建客户端连接
让我们首先剖析清单1中的代码。
清单1.从客户端建立连接的ACE API
ACE_INET_Addr server(458, “tintin.cstg.in”);
ACE_SOCK_Stream client_stream;
ACE_SOCK_Connector client_connector;
if (-1 == connector.connect(client_stream, server)) {
printf (“Failure to establish connection!\n”);
return;
}
client_stream.send_n(“Hello World”, 12, 0);
client_stream.close( ); // close when done
客户端需要两条信息:将要建立连接的主机名和端口号。 此详细信息封装在ACE_INET_Addr类中。 还有其他初始化ACE_INET_Addr类的方法: hostname: port no和hostip: port no是常见的选择。 例如,您可以将服务器声明为:
ACE_INET_Addr server ("tintin.cstg.in:458:)
要么:
ACE_INET_Addr server ("132.132.2.61:458")
下一个逻辑问题是在客户端数据流和服务器之间建立连接,这是ACE_SOCK_Connector类负责的。 最后, ACE_SOCK_Stream类代表客户端传递实际的消息。 ACE_SOCK_Stream类带有send_n和recv_n方法,用于将数据传输到远程计算机并从远程计算机接收信息。 请注意,send和receive方法不会部分传递消息:它总是整体完成或根本没有完成,在这种情况下,例程返回-1 。
使用ACE_SOCK_Connector更好的错误处理
connect方法可以返回-1的原因有多种。 服务器可能太忙于处理多个客户端,因此负载很高; 也许它刚刚被重新启动,不久就会开始接受请求; 或者服务器运行在较慢的操作系统或硬件上。 无论如何,在放弃之前继续尝试一段时间是很有意义的。 connect方法接受一个超时参数,如果连接在指定的时间内没有成功,则放弃。 清单2简要显示了此过程。
清单2.带有超时的客户端ACE API
…
ACE_SOCK_Connector client_connector;
ACE_Time_Value timeout(5);
if (-1 == connector.connect(client_stream, server, &timeout)) {
printf (“Failure to establish connection!\n”);
return;
}
…
建立服务器端连接
清单3显示了IPC服务器端的工作方式。
清单3.使用ACE API的服务器端连接
ACE_INET_Addr server(458);
ACE_SOCK_Acceptor client_responder(server);
ACE_SOCK_Stream client_stream;
ACE_Time_Value timeout(5);
ACE_INET_Addr client;
if (-1 == client_responder.accept(client_stream, &client, &timeout)) {
printf(“Connection not established with client!\n”);
return;
} else {
printf(“Client details: Host Name: %s Port Number: %d\n”,
client.get_host_name(), client.get_port_number());
}
// Reading 40 bytes of data from the client
char buffer[128];
if (-1 == client_stream.recv_n(buffer, 40, 0)) {
printf(“Error in reading client data!\n”);
return;
} else {
printf(“Client message: %s\n”, buffer);
}
client_stream.close();
在服务器端,您需要一个用于运行服务的端口号。 再次使用端口号创建ACE_INET_Addr对象(不需要主机名:默认情况下,它是当前主机)。 ACE_SOCK_Acceptor类类似于旧的BSD listen例程,旨在接受客户端请求以连接到服务器。 您以类似的方式使用ACE_SOCK_Stream :代表服务器将消息传递给客户端。 在对accept例程的调用中观察超时的用法- [client_responder.accept (client_stream, &client)] :服务器将永远阻止该调用。 但是,使用超时意味着,如果在指定的时间间隔内没有客户端连接到服务器,则服务器可以打印一条消息,说明该消息并挂起其引导程序。
最后, accept方法接受尚未提及的第四个参数。 清单4显示了ace / include / SOCK_Acceptor.h中的accept方法的声明。
清单4.接受方法的声明
/**
* Accept a new ACE_SOCK_Stream connection using the QoS
* information in @a qos_params. A @a timeout of 0 means block
* forever, a @a timeout of {0, 0} means poll. @a restart == true means
* "restart if interrupted," i.e., if errno == EINTR. Note that
* @a new_stream inherits the "blocking mode" of @c this
* ACE_SOCK_Acceptor, i.e., if @c this acceptor factory is in
* non-blocking mode, the @a new_stream will be in non-blocking mode
* and vice versa.
*/
int accept (ACE_SOCK_Stream &new_stream,
ACE_Accept_QoS_Params qos_params,
ACE_Addr *remote_addr = 0,
ACE_Time_Value *timeout = 0,
bool restart = true,
bool reset_new_handle = false) const;
如果restart标志设置为True(默认值),则由某些UNIX信号引起的例程中断将导致例程的重新启动。 您必须确定这是否是应用程序中的必需功能。
使用ACE的基于UDP的IPC
这是UDP的快速回顾。 UDP是面向数据报的协议,这意味着,如果客户端向服务器发送五个1KB的信息块,则服务器可能会收到介于零到五个块之间的任何位置。 但是,服务器接收到的任何块都将是完整的1KB块-要么全部接收,要么根本不接收。 UDP不保证数据的到达或到达顺序,因此第四个块可能在第二个之前到达。 同样,数据报在传输中可能会丢失; 但是,UDP在交付时会尽力而为。 清单5定义了一个基于UDP的基本客户端通信框架。
清单5.使用ACE的UDP连接的客户端代码
ACE_INET_Addr server(458, “tintin.cstg.in”);
ACE_INET_Addr client(9000);
ACE_SOCK_Dgram client_data(client);
char* message = “Hello World!\n”;
size_t sent_data_length = client_data.send(message,
strlen(message) + 1, server);
if (sent_data_length == -1)
printf(“Error in data transmission\n”);
client_data.close();
再次,为服务器-端口组合创建一个ACE_INET_Addr对象。 您还需要客户端的ACE_INET_Addr对象(与TCP不同),用于指定将发送和接收数据报的端口。 UDP的代码不需要任何连接器或接受器类。 您必须创建一个ACE_SOCK_Dgram类型的类,并在数据传输期间显式指定服务器地址,这使UDP样式连接的另一个有趣的方面暴露了:服务器地址不必唯一。 同一客户端实际上可以与多个远程对等方通信。 尽管基于TCP的连接是一对一连接,但是UDP具有三种不同的模式: 单播 ( 一对一连接), 广播 (将数据报发送到网络中的每个远程对等方)和组播 (仅将数据报发送到网络中远程对等方总数的一个子集)。
但是,在深入研究广播和多播主题之前,请看一个基于UDP的简单服务器框架(请参见清单6 )。
清单6.使用ACE的UDP连接的服务器代码
ACE_INET_Addr server(458);
ACE_SOCK_Dgram server_data(server);
ACE_INET_Addr client(“haddock.cstg.in”, 9000);
size_t sent_data_length = client_data.send(message,
strlen(message) + 1, server);
if (sent_data_length == -1)
printf(“Error in data transmission\n”);
server_data.close();
与TCP不同,客户端和服务器代码在基于UDP的通信中看起来非常相似。 最后,请注意, ACE_SOCK_Dgram的析构函数不会自动关闭基础UDP套接字。 必须显式调用close方法; 否则会导致物体泄漏。
面向连接的基于数据报的通信
对于总是在两个对等点之间进行的通信,重复提供服务器地址并不代表良好的编程习惯。 ACE提供了ACE_SOCK_CODgram类(其中COD代表面向连接的Datagram ),它消除了重复对等地址的需要。 在调用open方法期间,一次提供对等地址。 随后的send调用仅使用两个参数进行:字符缓冲区及其长度。 清单7显示了代码。
清单7.使用ACE的基于COD的连接的客户端代码
ACE_INET_Addr server(458, “tintin.cstg.in”);
ACE_INET_Addr client(9000);
ACE_SOCK_CODgram client_data(client);
if (0 != client_data.open(server)) {
printf(“Unable to establish connection with remote host\n”);
return;
}
char* message = “Hello World!\n”;
size_t sent_data_length = client_data.send(message,
strlen(message) + 1);
if (sent_data_length == -1)
printf(“Error in data transmission\n”);
message = “Initiating”;
client_data.send(message, strlen(message) + 1);
client_data.close();
使用ACE广播
对于对多个进程的广播send操作,请使用ACE_SOCK_Dgram_Bcast类(从ACE_SOCK_Dgram派生)。 无需明确指定IP广播地址ACE_SOCK_Dgram_Bcast类负责提供正确的IP地址-您只需要提供适当的远程主机端口号即可。 清单8中的代码与清单5类似,除了send例程现在只需要向其广播的端口号。
清单8.使用ACE广播的客户端代码
ACE_INET_Addr local_host(4158, “tintin.cstg.in”);
ACE_SOCK_Dgram_Bcast local_broadcast_dgram(local_host);
int remote_port = 7671;
char* message = “Hello World!\n”;
size_t sent_data_length = local_broadcast_dgram.send(
message, strlen(message) + 1, remote_port);
if (sent_data_length == -1)
printf(“Error in data transmission\n”);
local_broadcast_dgram.close();
侦听端口7671的所有远程计算机都应该能够使用ACE_SOCK_Dgram::recv处理消息。
使用ACE进行多播
对于多播操作,请使用ACE_SOCK_Dgram_Mcast类(再次从ACE_SOCK_Dgram派生)。 ACE_SOCK_Dgram_Mcast类提供了与特定对等计算机组之间收发消息的功能。 有兴趣成为该多播组一部分的对等计算机必须显式订阅该组。 清单9显示了如何订阅或取消订阅多播消息。
清单9.订阅从对等集接收多播消息的客户端代码
#define MULTICAST_ADDR “132.132.2.7”
ACE_INET_Addr multicast_addr(4158, MULTICAST_ADDR);
ACE_SOCK_Dgram_Mcast multicast_dgram;
// Subscribe
if (-1 == multicast_dgram.subscribe(multicast_addr)) {
printf(“Error subscribing to multicast address\n”);
return;
}
// Do Processing
…
// Unsubscribe
if (-1 == multicast_dgram.unsubscribe(multicast_addr)) {
printf(“Error unsubscribing to multicast address\n”);
return;
}
订阅了多播组的对等计算机将接收任何组件对等方发送给该组的所有消息。 但是,如果网络中的任何主机希望向该多播组发送消息,并且不希望接收响应,则使用ACE_SOCK_Dgram进行发送也就足够了。 清单10显示了主机如何使用多播数据报来接收和发送消息。 在现实生活中的应用程序中,接收/发送代码通常是某个循环的一部分,该循环不断发送/接收消息。 请注意recv方法中使用ACE_INET_Addr :这有助于捕获从其传输数据的对等计算机。
清单10.订阅从对等体集合发送/接收多播消息的客户端代码
#define MULTICAST_ADDR “132.132.2.7”
ACE_INET_Addr multicast_addr(4158, MULTICAST_ADDR);
ACE_SOCK_Dgram_Mcast multicast_dgram;
//.. Subscribe
// Do Processing for a 256 byte message
char buffer[1024];
if (-1 == multicast_dgram.send(buffer, 256, multicast_addr)) {
printf(“Failure in message transmission\n”);
}
ACE_INET_Addr peer_address;
if (-1 == multicast_dgram.recv(buffer, 256, peer_address)) {
printf(“Failure in message reception\n”);
} else {
printf(“Message %s received from Host %s : Port %d\n”,
buffer, peer_address.get_host_name(),
peer_address.get_port_number());
}
// .. Unsubscribe
结论
本文介绍了如何使用ACE框架实现基于TCP / IP和UDP的通信。 还有其他几种使用ACE来启动IPC的方法,例如共享内存或UNIX风格的套接字寻址( ACE_LSOCK*类组),本文不会解决。 有关更多高级信息的链接,请务必查看参考资料 。
翻译自: https://www.ibm.com/developerworks/aix/library/au-interprocess_ace/index.html
相关资源:ACE网络通信架构中文版