ACE网络编程:IPC SAP、ACE_SOCKET和TCP/IP通信实例
socket、TLI、STREAM管道和FIFO为访问局部和全局IPC机制提供广泛的接口。但是,有许多问题与这些不统一的接口有关联。比如类型安全的缺乏和多维度的复杂性会导致成问题的和易错的编程。ACE的IPC SAP类属提供了统一的层次类属,对那些麻烦而易错的接口进行封装。在保持高性能的同时,IPC SAP被设计用于改善通信软件的正确性、易学性、可移植性和可复用性。
IPC SAP类属
根据底层使用的不同IPC接口,IPC SAP类被划分为四种主要的类属,图2-1描述了这一划分。ACE_IPC_SAP类提供的一些函数是所有IPC接口公有的。有四个不同的类由此类派生而出,每个类各自代表ACE包含的一种IPC SAP包装类属。这些类封装适用于特定IPC接口的功能。例如,ACE_SOCK类包含的功能适用于BSD socket编程接口,而ACE_TLI包装TLI编程接口。
在这四个类的每一个类下面都有一整层次的包装类,它们完整地包装底层接口,并提供高度可复用、模块化、安全和易用的包装类。下面重点介绍一下最常用的socket类属。
socket类属(ACE_SOCK)
该类属中的类都位于ACE_SOCK之下;它提供使用BSD socket编程接口的Internet域和UNIX域协议族的接口。这个类属中的类被进一步划分为:
Dgram类和Stream类:Dgram类基于UDP数据报协议,提供不可靠的无连接消息传递功能。另一方面,Stream类基于TCP协议,提供面向连接的消息传递。
Acceptor、Connector类和Stream类:Acceptor和Connector类分别用于被动和主动地建立连接。Acceptor类封装BSD accept()调用,而Connector封装BSD connect()调用。Stream类用于在连接建立之后提供双向的数据流,并包含有发送和接收方法。
下表详细描述了该类属中的类以及它们的职责:
类名 |
职责 |
ACE_SOCK_Acceptor |
用于被动的连接建立,基于BSD accept()和listen()调用。 |
ACE_SOCK_Connector |
用于主动的连接建立,基于BSD connect()调用。 |
ACE_SOCK_Dgram |
用于提供基于UDP(用户数据报协议)的无连接消息传递服务。封装了sendto()和receivefrom()等调用,并提供了简单的send()和recv()接口。 |
ACE_SOCK_IO |
用于提供面向连接的消息传递服务。封装了send()、recv()和write()等调用。该类是ACE_SOCK_Stream和ACE_SOCK_CODgram类的基类。 |
ACE_SOCK_Stream |
用于提供基于TCP(传输控制协议)的面向连接的消息传递服务。派生自ACE_SOCK_IO,并提供了更多的包装方法。 |
ACE_SOCK_CODgram |
用于提供有连接数据报(connected datagram)抽象。派生自ACE_SOCK_IO;它包含的open()方法使用bind()来绑定到指定的本地地址,并使用UDP连接到远地地址。 |
ACE_SOCK_Dgram_Mcast |
用于提供基于数据报的多点传送(multicast)抽象。包括预订多点传送组,以及发送和接收消息的方法 |
ACE_SOCK_Dgram_Bcast |
用于提供基于数据报的广播(broadcast)抽象。包括在子网中向所有接口广播数据报消息的方法
|
在下面的部分,我们将要演示怎样将IPC_SAP包装类直接用于处理进程间通信。
使用ACE的流
ACE中的流包装提供面向连接的通信。流数据传输包装类包括ACE_SOCK_Stream和ACE_LSOCK_Stream,它们分别包装TCP/IP和UNIX域socket协议数据传输功能。连接建立类包括针对TCP/IP的ACE_SOCK_Connector和ACE_SOCK_Acceptor,以及针对UNIX域socket的ACE_LSOCK_Connector和ACE_LSOCK_Acceptor。
Acceptor类用于被动地接受连接(使用BSD accept()调用),而Connector类用于主动地建立连接(使用BSD connect()调用)。
下面的例子演示接收器和连接器是怎样用于建立连接的。该连接随后将用于使用流数据传输类来传输数据。
#include "ace/SOCK_Acceptor.h" #include "ace/SOCK_Stream.h" #include "ace/Log_Msg.h" #define SIZE_DATA 18 #define SIZE_BUF 1024 #define NO_ITERATIONS 5 class Server { public: ///初始化 Server(int port): server_addr_(port),peer_acceptor_(server_addr_) { data_buf_= new char[SIZE_BUF]; } ///建立监听 int accept_connections() { if (peer_acceptor_.get_local_addr (server_addr_) == -1) { ACE_ERROR_RETURN ((LM_ERROR,"%p\n","Error in get_local_addr"),1); ACE_DEBUG ((LM_DEBUG,"Starting server at port %d\n", server_addr_.get_port_number ())); } while(1) { ACE_Time_Value timeout (ACE_DEFAULT_TIMEOUT); if (peer_acceptor_.accept (new_stream_, &client_addr_, &timeout)== -1) { ACE_ERROR ((LM_ERROR, "%p\n", "accept")); continue; } else { ACE_DEBUG((LM_DEBUG, "Connection established with remote %s:%d\n", client_addr_.get_host_name(),client_addr_.get_port_number())); handle_read(); } } } ///读取流内容 int handle_read() { for(int i=0;i<NO_ITERATIONS;i++) { int byte_count=0; if( (byte_count=new_stream_.recv_n (data_buf_, SIZE_DATA, 0))==-1) { ACE_ERROR ((LM_ERROR, "%p\n", "Error in recv")); } else { data_buf_[byte_count]=0; ACE_DEBUG((LM_DEBUG,"Server received %s \n",data_buf_)); } } if (new_stream_.close () == -1) { ACE_ERROR ((LM_ERROR, "%p\n", "close")); } return 0; } private: char *data_buf_; ACE_INET_Addr server_addr_;//地址 ACE_INET_Addr client_addr_; ACE_SOCK_Acceptor peer_acceptor_; ACE_SOCK_Stream new_stream_;//流 }; int ACE_TMAIN (int, ACE_TCHAR *[]) { Server server(8002); server.accept_connections(); return 0; };
上面的例子创建了一个被动服务器,侦听到来的客户连接。在连接建立后,服务器接收来自客户的数据,然后关闭连接。Server类表示该服务器。
Server类包含的accept_connections()方法使用接受器(也就是ACE_SOCK_Acceptor)来将连接接受“进”ACE_SOCK_Stream new_stream_。该操作是这样来完成的:调用接受器上的accept(),并将流作为参数传入其中;我们想要接受器将连接接受进这个流。一旦连接已建立进流中,流的包装方法send()和recv()就可以用来在新建立的链路上发送和接收数据。还有一个空的ACE_INET_Addr client_addr_也被传入接受器的accept()方法,并在其中被设定为发起连接的远地机器的地址。
在连接建立后,服务器调用handle_read()方法,它开始从客户那里读取一个预先知道的单词,然后将流关闭。对于要处理多个客户的服务器来说,这也许并不是很实际的情况。在现实世界的情况中可能发生的是,连接在单独的线程或进程中被处理。在后续章节中将反复演示怎样完成这样的多线程和多进程类型的处理。
连接关闭通过调用流上的close()方法来完成,该方法会释放所有的socket资源并终止连接。
下面的例子演示怎样与前面例子中演示的接受器协同使用连接器。
#include "ace/SOCK_Connector.h" #include "ace/INET_Addr.h" #include "ace/Log_Msg.h" #define SIZE_BUF 128 #define NO_ITERATIONS 5 class Client { public: Client(char *hostname, int port):remote_addr_(port,hostname) { data_buf_="Hello from Client"; } int connect_to_server() { ACE_DEBUG ((LM_DEBUG, "(%P|%t) Starting connect to %s:%d\n", remote_addr_.get_host_name(),remote_addr_.get_port_number())); if (connector_.connect (client_stream_, remote_addr_) == -1) { ACE_ERROR_RETURN ((LM_ERROR,"(%P|%t) %p\n","connection failed"),-1); } else { ACE_DEBUG ((LM_DEBUG,"(%P|%t) connected to %s\n", remote_addr_.get_host_name ())); } return 0; } int send_to_server() { for(int i=0;i<NO_ITERATIONS; i++) { if (client_stream_.send_n (data_buf_, ACE_OS::strlen(data_buf_)+1, 0) == -1) { ACE_ERROR_RETURN ((LM_ERROR,"(%P|%t) %p\n","send_n"),0); break; } } close(); } int close() { if (client_stream_.close () == -1) { ACE_ERROR_RETURN ((LM_ERROR,"(%P|%t) %p\n","close"),-1); } else { return 0; } } private: ACE_SOCK_Stream client_stream_; ACE_INET_Addr remote_addr_; ACE_SOCK_Connector connector_; char *data_buf_; }; int ACE_TMAIN (int, ACE_TCHAR *[]) { Client client("127.0.0.1", 8002); client.connect_to_server(); client.send_to_server(); return 0; };
上面的例子演示的客户主动连接到服务器。在建立连接后,客户将单个字符串发送若干次到服务器,并关闭连接。
客户由单个Client类表示。Client含有connect_to_server()和send_to_server()方法。
Connect_to_server()方法使用类型为ACE_SOCK_Connector的连接器(connector_)来主动地建立连接。连接的设置通过调用连接器connector_上的connect()方法来完成:传入的参数为我们想要连接的机器的地址remote_addr_,以及用于在其中建立连接的空ACE_SOCK_Stream client_stream_。远地机器在例子的运行时参数中指定。一旦connect()方法成功返回,通过使用ACE_SOCK_Stream封装类中的send()和recv()方法族,流就可以用于在新建立的链路上发送和接收数据了。
在此例中,一旦连接建立好,send_to_server()方法就会被调用,以将一个字符串发送NO_ITERATIONS次到服务器。如前面所提到的,这是通过使用流包装类的send()方法来完成的。