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是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(通常,同步+多线程 相似于 异步 )。
另外,进行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连接上序列化数据。