C++主要是通过socket APIs 进行网络编程的, socket相关的API接口函数可谓洋洋大观, 多且繁杂, 稍不注意,就有可能误用
用C++类对它们进行适当的封装是不错的主意, 在ACE中对此进行了比较优雅的封装
ACE 的Socket 包装类可分为三类
1. 被动和主动连接工厂类Passive and active conneciton factories:
ACE_SOCK_Acceptor,
ACE_SOCK_Connector
2.数据流类Streaming classes:
ACE_SOCK_Stream , ACE_SOCK_IO
3.地址类Addressing classes:
ACE_Addr, ACE_INET_Addr
以一个简单的HTTP客户端程序为例
#include
"
ace/INET_Addr.h
"
#include
"
ace/SOCK_Connector.h
"
#include
"
ace/SOCK_Stream.h
"
#include
<
iostream
>
using
namespace
std;
int
main(
int
argc,
char
*
argv[])
...
{
const char * pathname = argc > 1 ? argv[ 1 ]: " index.html " ;
const char * server_hostname = argc > 2 ? argv[ 2 ]: " www.w3c.org " ;
printf( " %s %s %s... " ,argv[ 0 ],pathname,server_hostname);
ACE_SOCK_Connector connector;
ACE_SOCK_Stream peer;
ACE_INET_Addr peer_addr;
if (peer_addr. set ( 80 ,server_hostname) ==- 1 )
... {
return 1 ;
}
ACE_Time_Value timeout( 20 );
if (connector.connect(peer,peer_addr, & timeout) ==- 1 )
... {
if (errno == ETIME)
cout << " connet time out " << endl;
return 1 ;
}
/**/ /*
struct iovec{
u_long iov_len
char *iov_base
}
*/
char buf[ 4096 ];
iovec iov[ 3 ];
iov[ 0 ].iov_base = const_cast < char *> ( " GET " );
iov[ 0 ].iov_len = 4 ;
iov[ 1 ].iov_base = const_cast < char *> (pathname);
iov[ 1 ].iov_len = strlen(pathname);
iov[ 2 ].iov_base = const_cast < char *> ( " HTTP/1.0 " );
iov[ 2 ].iov_len = 13 ;
if (peer.sendv_n(iov, 3 ) ==- 1 )
return 1 ;
for (ssize_t n;(n = peer.recv(buf, sizeof buf)) > 0 ;)
... {
ACE::write_n(ACE_STDOUT,buf,n);
}
return peer.close() ==- 1 ? 1 : 0 ;
}
编译
g++ -o connectortest connectortest.cpp -I/home/walter/ACE_wrappers -I./ -L/home/walter/ACE_wrappers/ace -lACE -ldl -lnsl
代码非常简单清晰, 看不到任何复杂的socket函数调用,其中主要用到了
ACE_SOCK_Connector,ACE_SOCK_Stream,ACE_INET_Addr类
传统上,假如我们直接调用系统的API连接一个网络服务,我们会这样写
1. 创建socket
such as: socket(AF_INET, SOCK_STREAM, 0)
2. 设置socket option
such as: setsockopt(nSock, SOL_SOCKET, SO_REUSEADDR, &socket_option_value, sizeof(socket_option_value));
3. 设置socket address/port
主要是设置 struct sockaddr_in的sin_addr,sin_port为相应的网络地址和端口
4. 进行连接connect
such as: connect(nSock,(struct sockaddr *)&saddr, sizeof(saddr));
5. 进行数据传输send/recv
ACE的connector基本上也是这个步骤,不过它进行了一些巧妙的封装, 并加上了超时和错误的处理,提供了跨平台的实现, 它的connect方法只是依次调用了
1. ACE_SOCK_Connector::shared_open
2. ACE_SOCK_Connector::shared_connect_start
3. ACE_OS::connect
4. ACE_SOCK_Connector::shared_connect_finish
int
ACE_SOCK_Connector::connect (ACE_SOCK_Stream
&
new_stream,
const
ACE_Addr
&
remote_sap,
const
ACE_Time_Value
*
timeout,
const
ACE_Addr
&
local_sap,
int
reuse_addr,
int
/**/
/* flags */
,
int
/**/
/* perms */
,
int
protocol)
...
{
ACE_TRACE ( " ACE_SOCK_Connector::connect " );
if ( this -> shared_open (new_stream,
remote_sap.get_type (),
protocol,
reuse_addr) == - 1 )
return - 1 ;
else if ( this -> shared_connect_start (new_stream,
timeout,
local_sap) == - 1 )
return - 1 ;
int result = ACE_OS::connect (new_stream.get_handle (),
reinterpret_cast < sockaddr *> (remote_sap.get_addr ()),
remote_sap.get_size ());
return this -> shared_connect_finish (new_stream,
timeout,
result);
}
shared_open方法其实是调用ACE_SOCK_Stream的open方法
类ACE_SOCK_Stream的继承关系如下
ACE_SOCK_Stream--->ACE_SOCK_IO-->ACE_SOCK-->ACE_IPC_SAP
而ACE_SOCK_Stream的open方法是从ACE_SOCK继承下来的,就是对socket, setsockopt调用的封装
int
ACE_SOCK::open (
int
type,
int
protocol_family,
int
protocol,
int
reuse_addr)
...
{
ACE_TRACE ( " ACE_SOCK::open " );
int one = 1 ;
this -> set_handle (ACE_OS::socket (protocol_family,
type,
protocol));
if ( this -> get_handle () == ACE_INVALID_HANDLE)
return - 1 ;
else if (protocol_family != PF_UNIX
&& reuse_addr
&& this -> set_option (SOL_SOCKET,
SO_REUSEADDR,
& one,
sizeof one) == - 1 )
... {
this -> close ();
return - 1 ;
}
return 0 ;
}
(To be continued...)