MORE INFO AT : WWW.LEEHAO.ORG
___________________________________________________________________________________________________________________________________
为了使用这些功能boost在其上面包装了一个io_service壳,从而屏蔽了关于完成端口所实现的一些细节性的描述,而只需要关注io_service即可并通过它将socket或者是串口等提供io服务的一些对象进行关联。这些对于端口的关联均在task_io_serivce和win_icop_io_service完成。在io_service.hpp文件中我们可以看到如下的语句:
#ifdefined(BOOST_ASIO_HAS_IOCP)
namespacedetail { typedef win_iocp_io_serviceio_service_impl;}
#else
namespacedetail { typedef task_io_service io_service_impl;}
#endif
….
private:
typedefdetail::io_service_impl impl_type;
…
impl_type& impl_
至此,io_service将提供一个接口,可将socket handle,以及serial handle等与完成端口进行相关联。
l op_queue
其中op_queue为相关操作的队列,系统所产生或者获得相关操作均由此队列中取得相关操作句柄。而其数据结构的定义如下:
l Endpoint 对象:
下面我们就以一个具体的例子来说明使用asio进行同步方式下的网络通信。在详细介绍之前,我们首先要阐述一个概念:服务接入点---tcp::endpoint。其位于boost::asio::ip::tcp的名字空间中。其主要的作用就是:使用一个指定的端口或者IP地址构建endpoint对象,并将其绑定到socket对象上。
tcp::endpoint (tcp::v4(), 13) 使用端口13及ipv4协议在本地主机上创建一个服务接入点[服务器模式]。tcp::endpoint(“ip.address”,port)客服端模式。
我们由endpoint的实现中可以看出其对于posixsocket编程中对于socket对象中参数设置的封装。
endpoint::endpoint()
: data_()
{
data_.v4.sin_family = AF_INET;
data_.v4.sin_port = 0;
data_.v4.sin_addr.s_addr =INADDR_ANY;
}
endpoint::endpoint(intfamily, unsigned short port_num)
: data_()
{
using namespace std; // For memcpy.
if (family == PF_INET)
{
data_.v4.sin_family = AF_INET;
data_.v4.sin_port =
boost::asio::detail::socket_ops::host_to_network_short(port_num);
data_.v4.sin_addr.s_addr = INADDR_ANY;
}
else
{
data_.v6.sin6_family = AF_INET6;
data_.v6.sin6_port =
boost::asio::detail::socket_ops::host_to_network_short(port_num);
data_.v6.sin6_flowinfo = 0;
boost::asio::detail::in6_addr_type tmp_addr= IN6ADDR_ANY_INIT;
data_.v6.sin6_addr = tmp_addr;
data_.v6.sin6_scope_id = 0;
}
}
上式中host_to_network_short其作用是完成主机字节序到网络字节序的转换。其上封装了posixsocket编程下的htons(value),这样封装的目的是以更加易于理解的方式进行程序的编写。
其中data_的数据类型为:
union data_union
{
boost::asio::detail::socket_addr_typebase;
boost::asio::detail::sockaddr_storage_typestorage;
boost::asio::detail::sockaddr_in4_typev4;
boost::asio::detail::sockaddr_in6_typev6;
} data_;
而data_.v4的类型为:sockaddr_in类型[其位于detail/socket_types.hpp中typedefsockaddr_insockaddr_in4_type;],由此我们可以看出经过层层的封装,我们使用endpoint时候,无需再过度的关注底层的细节设置,而使我们能够更加关注于系统的业务逻辑方面的设计。
l socket
下面我们就对socket类进行一些初步的分析。在ip/tcp.hpp文件中我们可以看到如下语句:typedefbasic_stream_socket<tcp> socket; 可以看出socket其真面目为:basic_stream_socket<tcp>下面就让我们走进 basic_stream_socket<tcp>一探究竟。我们在basic_stream_socket.hpp文中我们可以得到basic_stream_socket类的定义。其定义如下:
template<typename Protocol,
typename StreamSocketService =stream_socket_service<Protocol> >
classbasic_stream_socket
: publicbasic_socket<Protocol, StreamSocketService>
其继承了socket基类 basic_socket,而对basic_stream_socket进行初始化之前,首先会完成其基的初始化工作。经过这层由socketà=basic_stream_socket->basic_socket的主线,现在我们将目光转向更底层的类:basic_socket,来看看它为我们提供了什么样的服
务?在asio目录下的basic_socket.hpp文件中,我们找到了该类得定义,其形式如下:
template<typename Protocol, typename SocketService>
classbasic_socket
: publicbasic_io_object<SocketService>,
public socket_base
而basic_io_object<SocketService>和socket_base分别在asio目录下的basic_io_object.hpp文件和socket_base.hpp文件中给出他们的定义。他们之间的关系可以用如下来表示:
这些socket通过namesspace socket_ops下的函数具体完成socket地址的bind,listen,accept,complete_iocp_accept,connect等功能的实现。下面我们就到命名空间socket_ops中一探究竟。
l socket_ops
该命名空间位于detail/socket_ops.hpp及detail/impl/socket_ops.ipp文件中,其主要的作用就是完成关于socket通讯的系统api层得操作。例如:我们在上层应用中写了如下语句时:
io_service ios;
ip::tcp::endpoint endpoint(tcp::v4(), 80);
ip::tcp::acceptor acceptor(ios, endpoint);
在底层所作的工作便有使用socket_ops空间内的bind函数将一个已设置地址与端口的地址结构struct sockaddr* addr与一个socket句柄进行绑定。而这些涉及socket与OS底层发生关系的操作均可以在socket_ops命名空间中寻得。关于其空间中所涉及的具体函数在这里不在赘述,可具体参考其源码。
l boost::bind
在我们使用asio库的时候,经常看见boost::bind函数,那么该函数的主要作用是什么呢?我们用boost::bind函数又能做哪些工作呢?下面我们就来探究一下该函数。总体来说,其位于boost命名空间中,而它的主要的作用描述为:其是标准函数std::bind1st 和std::bind2nd的泛化形式。其支持任意函数对象,函数,函数指针,成员函数指针。其可以将一个指定的值绑定到任意一个参数上,或者将输入参数重新绑定到参数中的其它任何位置。例如,对于如下的代码:
int f(int a, int b)
{
return a + b;
}
int g(int a, int b, int c)
{
return a + b + c;
}
bind(f, 1, 2)其作用如同f(1, 2)一样,bind(g, 1, 2, 3)() 则等价于g(1, 2, 3).
l acceptor
在创建 tcp::acceptor时候其定义为 在tcp.hpp文件中。
typedefbasic_socket_acceptor<tcp> acceptor; 而 basic_socket_acceptor的定义如下:
template<typename Protocol,
typename SocketAcceptorService =socket_acceptor_service<Protocol> > class basic_socket_acceptor
而在对acceptor进行实例化时候对其 Protocol参数为tcp,而SocketAcceptorService为socket_acceptor_service<Protocol>,因此其为basic_socket_acceptor所提供的模板参数为socket_acceptor_service<tcp>。因此我们在调用 tcp::socket::acceptor 进程对象的初始化时候我们获得的acceptor对象其实是使用basic_acceptor_service<tcp,socket_acceptor_serivce<tcp> > 对对象进行初始化工作。其中basic_acceptor_service的定义为template <typename Protocol,typenameSocketAcceptorService = socket_acceptor_service<Protocol> >。
下面我们就对进行分析basic_acceptor_serivce <tcp,socket_acceptor_serivce<tcp> >。从socket_acceptor_service.hpp文件中对于其定义可以看出其具有如下形式的继承方式。
template <typename Protocol>
class socket_acceptor_service
#if defined(GENERATING_DOCUMENTATION)
: public boost::asio::io_service::service
#else
: publicboost::asio::detail::service_base<socket_acceptor_service<Protocol>>
#endif
l 综述
从上述的描述中我们可以看出boost::asio对于传统的posix socket编程进行了有效的封装。例如:posixsocket编程中所使用的socket, listen, connect 等函数。 基本的逻辑概念描述:所有的IO操作均由io_service来充当中间层的功能,其主要负责上层程序与OS底层所提供的IO服务沟通,其ASIO中的所有操作均通过io_service与操作系统所提的服务进行关联。ASIO提供了io操作的一些核心功能。其所在的namespace路径如下
boost::asio::xxx
其下包括了如下的异步IO对象。
(1)ip::tcp::socket 网络套接字类,提供tcp方式的网络套接字
主要的目的:完成网络套接字对象的初始化工作。
(2)ip::tcp::acceptor 连接接受类,其完成posixsocket api中的accept函数的功能,接受客户端的连接请求。
(3) ip::udp::socket 提供udp方式的网络套接字
(4) deadline_time 提供定时操作。为了能够完成网络的超时控制,可以由其中的定时对象来完成异步方式的定时操作。而在POSIX下其timer所提供的时间控制精度不足,无法满足毫秒或者更为精细的时间控制。为了获得更高的定时精度.我们通常采用select等方式。
而在POSIX下其测试网络连通状态的通常做法是由一个timer在指定的时间内发送一个heartbeat报文,用以测试网络的状态,我们也可在所发的heartbeat报文上打上timestamp用以观察网络的延迟。该种方式普遍应用在多种协议中:例如移动的CMPP,联通的SGIP,以及银联的POS交易协议。
detail/push_options.hpp中对相关编译器参数的识别,及不同编译器中所使用参数的的配置。
detail/socket_types.hpp 中则重新定义/更名了Xnix/windows平台下socket编程所有的各种数据结构.例如:微软平台:_MSC_VER , 塞班系统:__SYMBIAN32__,HPUnix: __hpux ,SUN: __sun, 及对于select的支持情况。
l POSIX下的做法:
为了满足高性能的socket io 操作。我们使用select/poll/epoll等函数来完成存在着大规模并发io的网络通信。
服务端代码:下面是一个传统的POSIX socket开发的例子,在完成socket对象的建立后,
inth_socket= socket (PF_INET, SOCK_STREAM , 0);
…
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET; //设置好协议族类型
serveraddr.sin_addr.s_addr =inet_addr (szListenIP) ; //设置监听地址
serveraddr.sin_port = htons ((short)iListenPort); //设置监听端口
bind(h_socket,(structsockaddr*)&serveraddr ,sizeof(serveraddr)); //地址信息的绑定
…
listen (h_socket,1024); //开始监听。
…
FD_ZERO (…)
FD_SET (…)
Iret=select(1024,&readset,NULL,NULL ,NULL); //poll, epoll 等高效的复用i/o
if (iret) {
iRecvedSkt_hd = accept (h_socket,(struct sockaddr*)&csin_addr,&len) ; //接收客户端请求
…
pthread_create (&accptedThread, NULL ,StartBuinessThread ,(void*)&iRecvedSkt_hd);//启动一个线程对其进行服务。
}
static void* StartBuinessThread (void*strThreadParams)
{ …}
l 参考文件:
1: Douglas C. SchmidtProactor Pattern .
2: Proactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handlersfor Asynchronous Events
3: MSDN online: http://msdn.microsoft.com/en-us/library/aa365198.aspx
l 附录1:
一个简单的Reactor示例,我们以IO操作中的读操作为例。Reactor具有以下行为方式。
- 注册读就绪事件和相应的事件处理器
- 事件分离器等待事件
- 事件到来,激活分离器,分离器调用事件对应的处理器。
- 事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
相应的sample code如下所示:
class handler
{
public:
virtual void onRead() = 0;
virtual void onWrite() = 0;
virtual void onAccept() = 0;
};
class dispatch
{
public:
void poll()
{
// add fd in the set.
//
// poll every fd
int c = select( 0, &read_fd,&write_fd, 0, 0 );
if( c > 0 )
{
for each fdin the read_fd_set
{ if fd can read
_handler->onRead();
if fd can accept
_handler->onAccept();
}
for each fd in the write_fd_set
{
if fd can write
_handler->onWrite();
}
}
}
void setHandler( handler *_h )
{
_handler = _h;
}
private:
handler *_handler;
};
/// application
class MyHandler : public handler
{
public:
void onRead()
{
}
void onWrite()
{
}
voidonAccept()
{ }
};
l 附录2:
完成端口(IOC)是用来实现高性能服务的一种常用方法,主要是通过操作系统提供的异步调用功能实现IO操作,可以通过很少的线程数实现高性能的并发服务。一个完成端口读写的例子,如下:
#define _WIN32_WINNT 0x0500
#include <cstdlib>
#include <clocale>
#include <ctime>
#include <iostream>
#include <vector>
#include <algorithm>
#include <winsock2.h>
#include <mswsock.h>
using namespace std;
#pragmacomment(lib,"ws2_32.lib")
#pragmacomment(lib,"mswsock.lib")
const intMAX_BUFFER_SIZE=1024;
const intPRE_SEND_SIZE=1024;
const intQUIT_TIME_OUT=3000;
constintPRE_DOT_TIMER=QUIT_TIME_OUT/80;
typedefenum{IoTransFile,IoSend,IoRecv,IoQuit} IO_TYPE;
typedef struct
{
SOCKEThSocket;
SOCKADDR_IN ClientAddr;
}PRE_SOCKET_DATA,*PPRE_SOCKET_DATA;
typedef struct
{
OVERLAPPED oa;
WSABUF DataBuf;
char Buffer[MAX_BUFFER_SIZE];
IO_TYPE IoType;
}PRE_IO_DATA,*PPRE_IO_DATA;
typedefvector<PPRE_SOCKET_DATA> SocketDataVector;
typedefvector<PPRE_IO_DATA> IoDataVector;
SocketDataVector gSockDataVec;
IoDataVector gIoDataVec;
CRITICAL_SECTION csProtection;
char* TimeNow(void)
{
time_tt=time(NULL);
tm*localtm=localtime(&t);
static chartimemsg[512]={0};
strftime(timemsg,512,"%Z: %B %d %X,%Y",localtm);
return timemsg;
}
BOOLTransFile(PPRE_IO_DATApIoData,PPRE_SOCKET_DATApSocketData,DWORDdwNameLen)
{
pIoData->Buffer[dwNameLen-1]='\0';
HANDLEhFile=CreateFile(pIoData->Buffer,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
BOOLbRet=FALSE;
if(hFile!=INVALID_HANDLE_VALUE)
{
cout<<"Transmit File "<<pIoData->Buffer<<"to client"<<endl;
pIoData->IoType=IoTransFile;
memset(&pIoData->oa,0,sizeof(OVERLAPPED));
*reinterpret_cast<HANDLE*>(pIoData->Buffer)=hFile;
TransmitFile(pSocketData->hSocket,hFile,GetFileSize(hFile,NULL),PRE_SEND_SIZE,reinterpret_cast<LPOVERLAPPED>(pIoData),NULL,TF_USE_SYSTEM_THREAD);
bRet=WSAGetLastError()==WSA_IO_PENDING;
}
else
cout<<"Transmit File"<<"Error:"<<GetLastError()<<endl;
return bRet;
}
DWORDWINAPIThreadProc(LPVOIDIocpHandle)
{
DWORDdwRecv=0;
DWORDdwFlags=0;
HANDLEhIocp=reinterpret_cast<HANDLE>(IocpHandle);
DWORDdwTransCount=0;
PPRE_IO_DATApPreIoData=NULL;
PPRE_SOCKET_DATApPreHandleData=NULL;
while(TRUE)
{
if(GetQueuedCompletionStatus(hIocp,&dwTransCount,
reinterpret_cast<LPDWORD>(&pPreHandleData),
reinterpret_cast<LPOVERLAPPED*>(&pPreIoData),INFINITE))
{
if(0==dwTransCount&&IoQuit!=pPreIoData->IoType)
{
cout<<"Client:"
<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
<<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
<<" is closed"<<endl;
closesocket(pPreHandleData->hSocket);
EnterCriticalSection(&csProtection);
IoDataVector::iteratoritrIoDelete=find(gIoDataVec.begin(),gIoDataVec.end(),pPreIoData);
SocketDataVector::iteratoritrSockDelete=find(gSockDataVec.begin(),gSockDataVec.end(),pPreHandleData);
delete *itrIoDelete;
delete *itrSockDelete;
gIoDataVec.erase(itrIoDelete);
gSockDataVec.erase(itrSockDelete);
LeaveCriticalSection(&csProtection);
continue;
}
switch(pPreIoData->IoType){
case IoTransFile:
cout<<"Client:"
<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
<<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
<<" Transmit finished"<<endl;
CloseHandle(*reinterpret_cast<HANDLE*>(pPreIoData->Buffer));
goto LRERECV;
case IoSend:
cout<<"Client:"
<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
<<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
<<" Send finished"<<endl;
LRERECV:
pPreIoData->IoType=IoRecv;
pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;
memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));
WSARecv(pPreHandleData->hSocket,&pPreIoData->DataBuf,1,
&dwRecv,&dwFlags,
reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData),NULL);
break;
case IoRecv:
cout<<"Client:"
<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
<<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
<<"recv finished"<<endl;
pPreIoData->IoType=IoSend;
if(!TransFile(pPreIoData,pPreHandleData,dwTransCount))
{
memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));
strcpy(pPreIoData->DataBuf.buf,"File transmit error!\r\n");
pPreIoData->DataBuf.len=strlen(pPreIoData->DataBuf.buf);
WSASend(pPreHandleData->hSocket,&pPreIoData->DataBuf,1,
&dwRecv,dwFlags,
reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData),NULL);
}
break;
caseIoQuit:
goto LQUIT;
default:
;
}
}
}
LQUIT:
return 0;
}
HANDLEhIocp=NULL;
SOCKEThListen=NULL;
BOOLWINAPIShutdownHandler(DWORDdwCtrlType)
{
PRE_SOCKET_DATAPreSockData={0};
PRE_IO_DATAPreIoData={0};
PreIoData.IoType=IoQuit;
if(hIocp)
{
PostQueuedCompletionStatus(hIocp,1,
reinterpret_cast<ULONG_PTR>(&PreSockData),
reinterpret_cast<LPOVERLAPPED>(&PreIoData));
cout<<"Shutdown at "<<TimeNow()<<endl<<"waitfor a moment please"<<endl;
for(intt=0;t<80;t+=1)
{
Sleep(PRE_DOT_TIMER);
cout<<".";
}
CloseHandle(hIocp);
}
inti=0;
for(;i<gSockDataVec.size();i++)
{
PPRE_SOCKET_DATApSockData=gSockDataVec[i];
closesocket(pSockData->hSocket);
delete pSockData;
}
for(i=0;i<gIoDataVec.size();i++)
{
PPRE_IO_DATApIoData=gIoDataVec[i];
delete pIoData;
}
DeleteCriticalSection(&csProtection);
if(hListen)
closesocket(hListen);
WSACleanup();
exit(0);
return TRUE;
}
LONGWINAPIMyExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
ShutdownHandler(0);
return EXCEPTION_EXECUTE_HANDLER;
}
u_shortDefPort=8182;
intmain(intargc,char **argv)
{
if(argc==2)
DefPort=atoi(argv[1]);
InitializeCriticalSection(&csProtection);
SetUnhandledExceptionFilter(MyExceptionFilter);
SetConsoleCtrlHandler(ShutdownHandler,TRUE);
hIocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
WSADATA data={0};
WSAStartup(0x0202,&data);
hListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET==hListen)
{
ShutdownHandler(0);
}
SOCKADDR_IN addr={0};
addr.sin_family=AF_INET;
addr.sin_port=htons(DefPort);
if(bind(hListen,reinterpret_cast<PSOCKADDR>(&addr),
sizeof(addr))==SOCKET_ERROR)
{
ShutdownHandler(0);
}
if(listen(hListen,256)==SOCKET_ERROR)
ShutdownHandler(0);
SYSTEM_INFO si={0};
GetSystemInfo(&si);
si.dwNumberOfProcessors<<=1;
for(inti=0;i<si.dwNumberOfProcessors;i++)
{
QueueUserWorkItem(ThreadProc,hIocp,WT_EXECUTELONGFUNCTION);
}
cout<<"Startup at "<<TimeNow()<<endl
<<"work on port "<<DefPort<<endl
<<"press CTRL+C toshutdown"<<endl<<endl<<endl;
while(TRUE)
{
intnamelen=sizeof(addr);
memset(&addr,0,sizeof(addr));
SOCKEThAccept=accept(hListen,reinterpret_cast<PSOCKADDR>(&addr),&namelen);
if(hAccept!=INVALID_SOCKET)
{
cout<<"accept aclient:"<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<endl;
PPRE_SOCKET_DATApPreHandleData=newPRE_SOCKET_DATA;
pPreHandleData->hSocket=hAccept;
memcpy(&pPreHandleData->ClientAddr,&addr,sizeof(addr));
CreateIoCompletionPort(reinterpret_cast<HANDLE>(hAccept),hIocp,reinterpret_cast<DWORD>(pPreHandleData),0);
PPRE_IO_DATApPreIoData=new(nothrow) PRE_IO_DATA;
if(pPreIoData)
{
EnterCriticalSection(&csProtection);
gSockDataVec.push_back(pPreHandleData);
gIoDataVec.push_back(pPreIoData);
LeaveCriticalSection(&csProtection);
memset(pPreIoData,0,sizeof(PRE_IO_DATA));
pPreIoData->IoType=IoRecv;
pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;
pPreIoData->DataBuf.buf=pPreIoData->Buffer;
DWORDdwRecv=0;
DWORDdwFlags=0;
WSARecv(hAccept,&pPreIoData->DataBuf,1,&dwRecv,&dwFlags,reinterpret_cast<WSAOVERLAPPED*>(pPreIoData),NULL);
}
else
{
delete pPreHandleData;
closesocket(hAccept);
}
}
}
return 0;
}
l 附录3:
一个proactor模式的示例。
classAsyIOProcessor
{
public:
void do_read()
{
//send read operation to OS
// read io finished.and dispatchnotification
_proactor->dispatch_read();
}
private:
Proactor *_proactor;
};
class Proactor
{
public:
void dispatch_read()
{
_handlerMgr->onRead();
}
private:
HandlerManager *_handlerMgr;
};
classHandlerManager
{
public:
typedef std::list<Handler*>HandlerList;
public:
void onRead()
{
// notify all the handlers.
std::for_each( _handlers.begin(),_handlers.end(), onRead );
}
private:
HandlerList *_handlers;
};
class Handler
{
public:
virtual void onRead() = 0;
};
// applicationlevel handler.
class MyHandler :public Handler
{
public:
void onRead()
{
}
};