Socket、CAsyncSocket、CSocket和CSocketFile

2012-04-11 20:34:27|?次阅读|上传:wustguangh【已有?条评论】发表评论

关键词:C/C++, 网络通信|来源:唯设编程网

类似的,Send()如果返回WSAEWOULDBLOCK错误,我们在OnSend()处等待,Receive()如果返回WSAEWOULDBLOCK错误,我们在OnReceive()处等待,以此类推。

  还有一点,也许是个难点,那就是在客户方调用Connect()连接服务方,那么服务方如何Accept(),以建立连接的问题。简单的做法就是在监听的Socket收到OnAccept()时,用一个新的CAsyncSocket对象去建立连接,例如:

void CMySocket::OnAccept( int ErrCode )
{
       CMySocket* pSocket = new CMySocket;
       Accept( *pSocket );
}

    于是,上面的pSocket和客户方建立了连接,以后的通信就是这个pSocket对象去和客户方进行,而监听的Socket仍然继续在监听,一旦又有一个客户方要连接服务方,则上面的OnAccept()又会被调用一次。当然pSocket是和客户方通信的服务方,它不会触发OnAccept()事件,因为它不是监听Socket。

三、CSocket

   CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类。它是如何又把CAsyncSocket变成同步的,而且还能响应同样的Socket事件呢?

  其实很简单,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( ... ) )
    {
        //由于异步操作需要时间,不能立即完成,所以Socket返回这个错误
        if( WSAGetLastError() == WSAEWOULDBLOCK )
        {
            //进入消息循环,以从线程消息队列里查看FD_CONNECT消息,
            //直到收到FD_CONNECT消息,认为连接成功。
            while( PumpMessages( FD_CONNECT ) );
        }
    }
}
BOOL CSocket::PumpMessages( UINT uEvent )
{
    CWinThread* pThread = AfxGetThread();
    //bBlocking仅仅是一个标志,看用户是否取消对Connect()的调用
    while( bBlocking )
    {
        MSG msg;
        if( PeekMessage( &msg, WM_SOCKET_NOTIFY ) )
        {
            if( msg.message == WM_SOCKET_NOTIFY
                && WSAGETSELECTEVENT(msg.lParam) == uStopFlag )
            {
                CAsyncSocket::DoCallBack( msg.wParam, msg.lParam );
                return TRUE;
            }    
        }
        else
        {
            //处理消息队列里的其它消息
            OnMessagePending();
            pThread->OnIdle(-1);
        }
    }
}
BOOL CSocket::OnMessagePending()
{
    MSG msg;
    if( PeekMessage( &msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE ) )
    {
        //这里仅关心WM_PAINT消息,以处理阻塞期间的主窗口重画
        ::DispatchMessage( &msg );
        return FALSE;
    }
    return FALSE;
}

   其它的CSocket函数,诸如Send(),Receive(),Accept()都在收到WSAEWOULDBLOCK错误时,进入PumpMessages()消息循环,这样一个原本异步的CAsyncSocket,到了派生类CSocket,就变成同步的了。

  明白之后,我们可以对CSocket应用自如了。比如有些程序员将CSocket的操作放入一个线程,以实现多线程的异步Socket(通常,同步+多线程 相似于 异步 )。

四、CSocketFile

  另外,进行Socket编程,不能不提到CSocketFile类,其实它并不是用来在Socket双方发送文件的,而是将需要序列化的数据,比如一些结构体数据,传给对方,这样,程序的CDocument()的序列化函数就完全可以和CSocketFile联系起来。例如你有一个CMyDocument实现了Serialize(),你可以这样来将你的文档数据传给Socket的另一方:

CSocketFile file( pSocket );
CArchive ar( &file, CArchive::store );
pDocument->Serialize( ar );
ar.Close();

   同样,接收一方可以只改变上面的代码为CArchive ar( &file, CArchive::load );即可。

   注意到,CSocketFile类虽然从CFile派生,但它屏蔽掉了CFile::Open()等函数,而函数里仅扔出一个例外。那么也就是说,你不能调用CSocketFile的Open函数来打开一个实实在在的文件,否则会导致例外,如果你需要利用CSocketFile来传送文件,你必须提供CSocketFile类的这些函数的实现。

  再一点,CArchive不支持在datagram的Socket连接上序列化数据。

<12>
发表评论0条 】
网友评论(共?条评论)..
Socket、CAsyncSocket、CSocket和CSocketFile