这两个类对Winsock API进行了封装,CAsyncSocket是一个异步非阻塞套接字类,CSocket是继承于CAsyncSocket的同步阻塞套接字类。使用这两个类编程无需自己处理Winsock的I/O模型。
CAsyncSocket类提供的唯一抽象就是将与套接字相连的windows消息以回调函数的形式完成,在创建程序时只需要重载这几个函数就可以实现Winsock的I/O操作。
异步模型效率更高,使用起来更灵活,当然也比较难,因为你要手动检测会显得错误或者阻塞的具体情况。当你需要通信的对端系统可能只允许你建立一个SOCKET连接时,就用CAsyncSocket。
CAsyncSocket用于在少量连接时,处理大批量无步骤依赖性的业务。CSocket用于处理步骤依赖性业务,或在可多连接时配合多线程使用。
CAsyncSocket事件处理
当你使用CAsyncSocket::Create创建一个指定兴趣事件的异步套接字时,这些消息究竟是怎么接收和处理的呢?
MFC定义了一个内部的类CSocketWnd,当调用Create函数创建一个套接字时,就会将该套接字连接到一个窗口(CSoketWnd的对象),并且使用WSAAsyncSelect(Winsock I/O模型)将套接字和此窗口对相关联。CAsyncSocket的DoCallBack函数为该窗口的回调函数。这样,当一个网络事件发生时,经过MFC的消息循环,DoCallBack函数会根据不同的事件调用相应的消息处理函数。MFC将这些消息处理函数定义为虚函数,在编程时必须重载需要的消息处理函数CAsyncSocket::OnReceive(),CAsyncSocket::OnSend(),CAsyncSocket::OnAccept(),CAsyncSocket::OnConnect(),CAsyncSocket::OnClose(),CAsyncSocket:OnOutOfBandData()。
客户方在使用CAsyncSocket::Connect()时,往往返回一个WSAEWOULDBLOCK的错误(其它的某些函数调用也如此),实际上这不应该算作一个错误,它是Socket提醒我们,由于你使用了非阻塞Socket方式,所以(连接)操作需要时间,不能瞬间建立。既然如此,我们可以等待呀,等它连接成功为止,于是许多程序员就在调用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或CAsyncSocket::GetLastError()查看Socket返回的错误,直到返回成功为止。这是一种错误的做法,断言,你不能达到预期目的。事实上,我们可以在Connect()调用之后等待CAsyncSocket::OnConnect()事件被触发,CAsyncSocket::OnConnect()是要表明Socket要么连接成功了,要么连接彻底失败了。至此,我们在CAsyncSocket::OnConnect()被调用之后就知道是否Socket连接成功了,还是失败了。
类似的,Send()如果返回WSAEWOULDBLOCK错误,我们在OnSend()处等待,Receive()如果返回WSAEWOULDBLOCK错误,我们在OnReceive()处等待,以此类推。
总之,尽量使用这些回调函数处理事件,而不要自己查询处理。
CSocket事件处理
主要是要了解他继承了CAsyncSocket之后,如何从异步非阻塞编程同步阻塞模式的。
CSocket在Connect()返回WSAEWOULDBLOCK错误时,不是在OnConnect(),OnReceive()这些事件终端函数里去等待。你先必须明白Socket事件是如何到达这些事件函数里的。这些事件处理函数是靠CSocketWnd窗口对象回调的,而窗口对象收到来自Socket的事件,又是靠线程消息队列分发过来的。总之,Socket事件首先是作为一个消息发给CSocketWnd窗口对象,这个消息肯定需要经过线程消息队列的分发,最终CSocketWnd窗口对象收到这些消息就调用相应的回调函数(OnConnect()等)。
所以,CSocket在调用Connect()之后,如果返回一个WSAEWOULDBLOCK错误时,它马上进入一个消息循环,就是从当前线程的消息队列里取关心的消息,如果取到了WM_PAINT消息,则刷新窗口,如果取到的是Socket发来的消息,则根据Socket是否有操作错误码,调用相应的回调函数(OnConnect()等)。
大致的简化代码为:
BOOL CSocket::Connect( ... )
{
if( !CAsyncSocket::Connect( ... ) )
{
if( WSAGetLastError() == WSAEWOULDBLOCK ) //由于异步操作需要时间,不能立即完成,所以Socket返回这个错误
{
//进入消息循环,以从线程消息队列里查看FD_CONNECT消息,直到收到FD_CONNECT消息,认为连接成功。
while( PumpMessages( FD_CONNECT ) );
}
}
}
CAsyncSocket、CSocket编程
在MFC中进行socket编程需要在应用程序类的Initlnstance中调用AfxSocketlnit初始化套接字。如果使用AppWizard创建应用程序的基本框架时,选中了“WindowsSockets”复选框,那么将自动添加初始化代码。
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
CSocket不需要bind(),在Create()时会调用bind()函数绑定此套接字。
CSocket一个重要的用处是可用于串行化技术,要结合CSocketFile 和 CArchive。
服务器端程序:
CSocketFile file(&sockRecv);
CArchive arin(&file,CArchive::load);
CArchive arout(&file,CArchive::load);
arin>>dwValue; //发送数据
arout <<dwValue;//接收数据
客户端程序:
CSocketFile file(&sockClient);
CArchive arin(&file,CArchive::load);
CArchive arout(&file,CArchive::load);
arin>>dwValue; //发送数据
arout<<dwValue;//接收数据