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...)