分类: C/C++
2010-03-11 18:05:26
使用ACE进行Socket编程,需要使用到下面几个类:
ACE_SOCK_Connector:连接器,主动建立连接,用于Socket Client;
ACE_SOCK_Acceptor:接受器,被动建立连接,用于Socket Server;
ACE_SOCK_Stream:传输数据的流,用于传输数据;
ACE_INET_Addr:用于表示通信端点的地址;
ace/INET_Addr.h文件中定义了一些有用的ACE_INET_Addr构造函数,用于创建通信端点的地址;一旦构造好一个通信端点的ACE_INET_Addr信息,那么,就可以使用这个地址去连接服务器了;ACE中使用ACE_SOCK_Stream类的对象来表示已经连接成功的TCP Socket;之所以这样命名,是因为TCP连接代表的是面向连接的虚连接,或者是"字节流";
短写问题:当你试图把一些字节写往远程主机时,由于网络缓冲区溢出、拥塞,或其它任何原因,导致你的字节没有被全部送出去,那么随后,你必须移动你的数据指针,发送剩余的数据;你必须持续地做这样的发送操作,直到把原来所有的自己全部都发送出去为;止.这样的问题在网络编程中发生的非常频繁,ACE的send_n()方法调用封装了这些操作,它会把所有这些重试操作都变成了它自己内部的事务,这样,只有把指定的字节全部都发送完,或者时发送时遇到错误,它才返回;
短读问题:当你试图从远程主机接收一些数据的时候,由于网络的拥塞、延迟等原因,会导致你不能一次性地接收到全部的数据;这个时候,你就必须通过计算已经接收到的数据的字节数,来接收剩余的数据;recv()方法,它将从对端读取最多n个字节的数据,并把这n个自己的数据放到自己的接收缓冲区中;当然,如果你确切地知道需要接收的数据的字节数,那么就必须处理"短读"问题;ACE提供了recv_n()方法调用为你解决了"短读"问题,它与send_n()方法一样,你必须告诉它需要读取的确切的字节数,它会在调用返回之前接收到你所指定的全部字节数的数据;
ACE_INET_Addr::set():这个方法比较灵活,使用它,可以修改地址对象的各个属性,这样可以重复使用一个地址对象,而不用创建多个地址对象;与ACE_INET_Addr的构造函数一样灵活;当set()调用失败的时候,set()方法返回-1,可以使用ACE_OS::last_error()检查错误码;Unix或类Unix中,ACE_OS::last_error()只是简单地返回errno的值,但是一在Windows中,它会调用GetLastError()函数来返回错误码;
ACE_SOCK_Connector::connet():主动连接服务器;连接失败,返回-1;连接成功,返回0;大多数情况下,你会让操作系统为你选择本地端口,ACE使用ACE_Addr::sap_any来表示这个值;但是在很多情况下,你可能想自己选择本地端口值;也就时说,我们在作为客户而主动连接服务器的时候,可以选择客户使用的本地端口;这是不很安全的保护应用的一种做法,但是可以在防止欺骗方面发挥作用;我们可以在我们的连接上设置服务质量参数,甚至是启动阻塞与非阻塞连接操作;对于Socket上面的操作,它们都支持为长时间运行的操作设置超时时间;与ACE_SOCK_Connector的connect方法一样,我们需要根据我们所需要的超时时间提供一个ACE_Time_Value类的对象;比如:send_n()、recv_n()等操作都可以接收一个ACE_Time_Value对象作为超时时间参数;
readv()、writev()分别与read()和write()的区别:
readv()和writev()与read()和write()的功能一样,都是系统调用,都是IO的读写系统调用,但是read()和write()必须用于连续的数据区域,而readv()和writev()则是可以用于不连续的数据区域或数据块;可以使用结构体iovec的数组来定义不连续的数据区;readv()和writev()以及结构iovec都是在BSD4.3操作系统中引入的,最常用于需要使用非连续的缓冲区来接收和发送数据的情况下;常见的例子就是,发送一个包头和一个与这个包头相关联的、存放在另外一个缓冲区中的数据;如果使用标准的系统调用write()的话,你必须连续两次调用write()来分别发送头和体,这样的操作比较麻烦,而且效率也跟不上;如果你能把头和体合在一起能按照一种原子方式写出去,或者是需要避开Nagle算法, 进行两次调用是不可接受的;如果你把头和体都复制到一个更大的缓冲区中,那么这在内存需求上不太现实;那么这个时候可以选择使用系统调用writev()和结构体iovec的数组来解决这个问题;writev()和ACE_SOCK_Stream::sendv()这两个方法会按照原子的方式把结构体iovec的数组中的所有条目都一一地发送出去;而readv()和ACE_SOCK_Stream::recvv()则与writev()和ACE_SOCK_Stream::sendv()相反,它们两个则是把接收到的数据依次填充到结构体iovec的数组中的每个条目所标记的不连续的缓冲区中,然后写指针移向下一个不连续的缓冲区的起始位置处;
typedef char* caddr_t; /* ?
struct iovec
{
int iov_len;
caddr_t iov_base;
};
成员iov_base可以指向内存映射文件区域、共享内存段或者是其它某个有意义的地方;你可以自己指定接收缓冲区地址,也可以让recvv()方法自动为你分配接收缓冲区,并用指针和缓冲区的长度来填充iovec结构,recvv()方法会计算到底有多少数据要接收,并分配一个大小刚好与要接收的数据的大小相同的缓冲区;如果你不清楚对方到底有多少数据要发送给你,但你相当地清楚,这些数据全都能放进一个尺寸合理的空间中,而且你希望这块空间是连续的,那么这个功能就可以使用上了;但是,一定要注意,由于是recvv()方法帮你分配的接收缓冲区,所以,在你使用完这些缓冲区之后,一定要记着释放这些内存空间,以避免内存泄露;
ACE_const_cast(type, variable);
ACE_static_cast(type, variable);
ACE_reinterpret_cast(type, variable);
这三个函数都是执行变量类型转换的,它们把变量variable的类型转换成type类型;
构建一个服务器:
要创建一个服务器,首先需要创建一个ACE_INET_Addr类的对象,以定义你想要用于侦听连接请求的端口.随后,需要使用一个ACE_SOCK_Acceptor对象在该端口上打开一个侦听器;ACE_SOCK_Acceptor::accept()方法会照管低层的细节,包括bind()、listen()、accept()等动作;如果accept()方法调用成功,那么会通过accept()的参数返回一个已经初始化成功的有效对端对象,它代表与客户端通讯的连接;
值得一提的是:在默认情况下,如果accept()方法被某一个UNIX信号中断了,那么它将会重启自身.对你的应用而言,这可能是合适的,也可能是不合适的;我们可以通过accept()方法的第四个参数来指定是否需要重启accept()方法自身,如果该参数的值为0,那么就表示,当accept()方法被信号中断之后,accept()方法不需要重启自身,而是返回-1,表示出错;如果该参数的值是1,则表示accept()方法被信号中断之后,accept()方法需要重启自身;
如果accept()方法调用成功,并返回了一个客户连接,那么在同时,它也会把已经连接上来的客户端的地址信息填充到一个ACE_INET_Addr对象中;ACE_INET_Addr::addr_to_string(),这个方法用于把IP地址转换成字符穿的形式来表示;
CLIENT例子代码:
#include "ace/Log_Msg.h"
#include "ace/INET_Addr.h"
#include "ace/SOCK_Stream.h"
#include "ace/SOCK_Connector.h"
#include "ace/SString.h"
int ACE_TMAIN(int argc, ACE_TCHAR** argv)
{
ACE_INET_Addr svr(5000, ACE_LOCALHOST);
ACE_SOCK_Stream peer;
ACE_SOCK_Connector connector;
ACE_TCHAR strBuffer[1024];
ACE_SString strSend;
ssize_t bytes_received = 0;
if(connector.connect(peer, svr) == -1)
{
ACE_DEBUG( (LM_ERROR, ACE_TEXT("--> %p|%t connect failed\n"), ACE_TEXT("connect")) );
return -1;
}
ACE_DEBUG( (LM_INFO, ACE_TEXT("connect server ok\n")) );
strSend = "hello, ace server";
peer.send_n(strSend.c_str(), strSend.length());
ACE_OS::memset(strBuffer, 0, sizeof(strBuffer));
bytes_received = peer.recv(strBuffer, sizeof(strBuffer));
ACE_DEBUG( (LM_INFO, ACE_TEXT("receive from server: %s\n"), strBuffer) );
peer.close();
return 0;
}
SERVER例子代码:
#include "ace/Log_Msg.h"
#include "ace/INET_Addr.h"
#include "ace/SOCK_Stream.h"
#include "ace/SOCK_Acceptor.h"
#include "ace/SString.h"
int ACE_TMAIN(int argc, ACE_TCHAR** argv)
{
ACE_INET_Addr port_to_listen(5000, ACE_LOCALHOST), client_addr;
ACE_SOCK_Acceptor acceptor;
ACE_SOCK_Stream client;
ACE_TCHAR strClientAddr[64], strBuffer[1024];
ssize_t bytes_received = 0;
if(acceptor.open(port_to_listen, 1) == -1)
{
ACE_DEBUG( (LM_ERROR, ACE_TEXT("--->%p\n"), ACE_TEXT("acceptor.open")) );
return -100;
}
while(1)
{
if(acceptor.accept(client, &client_addr) == -1)
{
ACE_DEBUG( (LM_ERROR, ACE_TEXT("(%p | %t) failed to accept\n"), ACE_TEXT("client connection")) );
acceptor.close();
break;
}
client_addr.addr_to_string(strClientAddr, sizeof(strClientAddr));
ACE_DEBUG( (LM_INFO, ACE_TEXT("client %s connected\n"), strClientAddr) );
ACE_OS::memset(strBuffer, 0, sizeof(strBuffer));
if(bytes_received = client.recv(strBuffer, sizeof(strBuffer)) == -1)
{
ACE_DEBUG( (LM_ERROR, ACE_TEXT("%p | %t received failed\n"), ACE_TEXT("cleint.recv")) );
client.close();
break;
}
ACE_DEBUG( (LM_INFO, ACE_TEXT("receive from client: [%d]%s\n"), bytes_received, strBuffer ) );
client.send_n(strBuffer, ACE_OS::strlen(strBuffer));
client.close();
}
acceptor.close();
return 0;
}