MFC CSocket类 通信原理

前些天被问到了项目中怎么处理多个socket的io请求,因为项目在做的时候工期比较赶,只是找到了解决方案,并没有细想原理。后来在学习过程中知道了Linux IO多路复用的原理,但是MFC具体怎么处理Socket请求确实是不太清楚,只是当时直接使用了Csocket类,覆盖了OnReceive的方法,至于这个类怎么处理IO的细节并没有太多考虑。趁着今天上午的空闲时间赶紧来充电,看了下CSocket的源码,恍然大悟,又找了些相关的资料,觉得学到了不少新知识,想记录下来,但是总觉得没人家讲的好,不能误导大家,所以把文章贴在了下面:


原文地址:http://blog.csdn.net/flyfish1986/article/details/43155141


继承关系



class CSocket : public CAsyncSocket

class CAsyncSocket : public CObject

class CSocketWnd : public CWnd


TCP服务器流程
socket()
bind()
listen()
accept()
receive() / send()
close()


CSocket::Create

调用的是父类CAsyncSocket::Create,Create函数中调用了bind



[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. CAsyncSocket::Create(nSocketPort, nSocketType, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT |   
  2.   
  3. FD_CLOSE, lpszSocketAddress);  
  4.   
  5. BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType,  
  6.     long lEvent, LPCTSTR lpszSocketAddress)  
  7. {  
  8.     if (Socket(nSocketType, lEvent))  
  9.     {  
  10.         if (Bind(nSocketPort,lpszSocketAddress))  
  11.             return TRUE;  
  12.         int nResult = GetLastError();  
  13.         Close();  
  14.         WSASetLastError(nResult);  
  15.     }  
  16.     return FALSE;  
  17. }  


[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. BOOL CAsyncSocket::Socket(int nSocketType, long lEvent,  
  2.     int nProtocolType, int nAddressFormat)  
  3. {  
  4.     ASSERT(m_hSocket == INVALID_SOCKET);  
  5.   
  6.     m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType);  
  7.     if (m_hSocket != INVALID_SOCKET)  
  8.     {  
  9.         CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);  
  10.         return AsyncSelect(lEvent);  
  11.     }  
  12.     return FALSE;  
  13. }  



创建一个不可见的窗口CSocketWnd 

第一步new 一个C++对象
第二步调用CWnd的成员函数Create创建真正的Windows对象
管理windows窗口对象是通过句柄完成的


Attach是将C++对象与WINDOWS对象关联
detach是分离关联


所以多线程使用CAsyncSocket 要么attach和detach操作,要么利用窗口句柄向窗口发送消息


windows程序的运行的本质就是 以消息为基础(Message Based),事件驱动(Event Driven)。
把socket的消息映射到windows窗口的消息循环,很符合windows自身的运作模式

它与windows消息集成在一起,实现异步套接字 不必采用多线程或者管理同步对象,可以通过windows消息或者执行回调函数来接收一个操作完成的通知。


[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void PASCAL CAsyncSocket::AttachHandle(  
  2.     SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)  
  3. {  
  4.     _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;  
  5.   
  6.     BOOL bEnable = AfxEnableMemoryTracking(FALSE);  
  7.   
  8.     TRY   
  9.     {  
  10.         if (!bDead)  
  11.         {  
  12.             ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);  
  13.             if (pState->m_pmapSocketHandle->IsEmpty())  
  14.             {  
  15.                 ASSERT(pState->m_pmapDeadSockets->IsEmpty());  
  16.                 ASSERT(pState->m_hSocketWindow == NULL);  
  17.   
  18.                 CSocketWnd* pWnd = new CSocketWnd;  
  19.                 pWnd->m_hWnd = NULL;  
  20.   
  21.                 if (!pWnd->CreateEx(0, AfxRegisterWndClass(0),  
  22.                     _T("Socket Notification Sink"),  
  23.                     WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))  
  24.                 {  
  25.                     TRACE(traceSocket, 0, "Warning: unable to create socket notify window!\n");  
  26.                     delete pWnd;  
  27.                     AfxThrowResourceException();  
  28.                 }  
  29.   
  30.                 ASSERT(pWnd->m_hWnd != NULL);  
  31.                 ASSERT(CWnd::FromHandlePermanent(pWnd->m_hWnd) == pWnd);  
  32.                 pState->m_hSocketWindow = pWnd->m_hWnd;  
  33.             }  
  34.             pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);  
  35.         }  
  36.         else  
  37.         {  
  38.             void* pvCount;  
  39.             INT_PTR nCount;  
  40.             if (pState->m_pmapDeadSockets->Lookup((void*)hSocket, pvCount))  
  41.             {  
  42.                 nCount = (INT_PTR)pvCount;  
  43.                 nCount++;  
  44.             }  
  45.             else  
  46.                 nCount = 1;  
  47.             pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);  
  48.         }  
  49.     }  
  50.     CATCH_ALL (e)   
  51.     {   
  52.         AfxEnableMemoryTracking(bEnable);   
  53.         THROW_LAST();   
  54.     }   
  55.     END_CATCH_ALL  
  56.   
  57.     AfxEnableMemoryTracking(bEnable);  
  58. }  


AttachHandle中

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define _afxSockThreadState AfxGetModuleThreadState()  
  2. #define _AFX_SOCK_THREAD_STATE AFX_MODULE_THREAD_STATE  
  3.   
  4. // AFX_MODULE_THREAD_STATE (local to thread *and* module)  
  5. class AFX_MODULE_THREAD_STATE : public CNoTrackObject  
  6. {  
  7. public:  
  8.     AFX_MODULE_THREAD_STATE();  
  9.     virtual ~AFX_MODULE_THREAD_STATE();  
  10.   
  11.     // current CWinThread pointer  
  12.     CWinThread* m_pCurrentWinThread;  
  13.   
  14.     // list of CFrameWnd objects for thread  
  15.     CTypedSimpleList<CFrameWnd*> m_frameList;  
  16.   
  17.     // temporary/permanent map state  
  18.     DWORD m_nTempMapLock;           // if not 0, temp maps locked  
  19.     CHandleMap* m_pmapHWND;  
  20.     CHandleMap* m_pmapHMENU;  
  21.     CHandleMap* m_pmapHDC;  
  22.     CHandleMap* m_pmapHGDIOBJ;  
  23.     CHandleMap* m_pmapHIMAGELIST;  
  24.   
  25.     // thread-local MFC new handler (separate from C-runtime)  
  26.     _PNH m_pfnNewHandler;  
  27.   
  28. #ifndef _AFX_NO_SOCKET_SUPPORT  
  29.     // WinSock specific thread state  
  30.     HWND m_hSocketWindow;  
  31. #ifdef _AFXDLL  
  32.     CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapSocketHandle;  
  33.     CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapDeadSockets;  
  34.     CEmbeddedButActsLikePtr<CPtrList> m_plistSocketNotifications;  
  35. #else  
  36.     CMapPtrToPtr* m_pmapSocketHandle;  
  37.     CMapPtrToPtr* m_pmapDeadSockets;  
  38.     CPtrList* m_plistSocketNotifications;  
  39. #endif  
  40. #endif  
  41.   
  42.     // common controls thread state  
  43.     CToolTipCtrl* m_pToolTip;  
  44.     CWnd* m_pLastHit;       // last window to own tooltip  
  45.     INT_PTR m_nLastHit;         // last hittest code  
  46.     TOOLINFO* m_pLastInfo;    // last TOOLINFO structure  
  47.     INT_PTR m_nLastStatus;      // last flyby status message  
  48.     CControlBar* m_pLastStatus; // last flyby status control bar  
  49. };  


在socket中使用到了的成员变量
m_pmapSocketHandle;
m_pmapDeadSockets;
m_plistSocketNotifications;
m_hSocketWindow; socket 事件与窗口消息映射 采用的窗口句柄


CAsyncSocket创建了一个窗口时CSocketWnd* pWnd = new CSocketWnd;
就为指向AFX_MODULE_THREAD_STATE的指针pState赋值
pState->m_hSocketWindow = pWnd->m_hWnd;
pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);


[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. BOOL CAsyncSocket::AsyncSelect(long lEvent)  
  2. {  
  3.     ASSERT(m_hSocket != INVALID_SOCKET);  
  4.   
  5.     _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;  
  6.     ASSERT(pState->m_hSocketWindow != NULL);  
  7.   
  8.     return WSAAsyncSelect(m_hSocket, pState->m_hSocketWindow,  
  9.         WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR;  
  10. }  

CAsyncSocket封装了socket api 并且使用WSAAsyncSelect实现了异步选择I/O模型

该窗口对象处理Socket的消息,CSocketWnd收到Socket消息之后,
通过CAsyncSocket::DoCallBack(pMsg->wParam, pMsg->lParam);
回调CAsyncSocket类的OnReceive(),OnSend(),OnOutOfBandData(),OnAccept(),OnConnect()



#define WSAGETSELECTEVENT(lParam)           LOWORD(lParam)
#define WSAGETSELECTERROR(lParam)          HIWORD(lParam)
lParam参数的高字位 包含出错码,
lParam参数的低字位 标识网络事件代码(FD_XXX)


[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     if (wParam == 0 && lParam == 0)  
  4.         return;  
  5.   
  6.     // Has the socket be closed - lookup in dead handle list  
  7.     CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);  
  8.   
  9.     // If yes ignore message  
  10.     if (pSocket != NULL)  
  11.         return;  
  12.   
  13.     pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, FALSE);  
  14.     if (pSocket == NULL)  
  15.     {  
  16.         // Must be in the middle of an Accept call  
  17.         pSocket = CAsyncSocket::LookupHandle(INVALID_SOCKET, FALSE);  
  18.         ASSERT(pSocket != NULL);  
  19.   
  20.         if(pSocket == NULL)  
  21.             return;  
  22.               
  23.         pSocket->m_hSocket = (SOCKET)wParam;  
  24.         CAsyncSocket::DetachHandle(INVALID_SOCKET, FALSE);  
  25.         CAsyncSocket::AttachHandle(pSocket->m_hSocket, pSocket, FALSE);  
  26.     }  
  27.   
  28.     int nErrorCode = WSAGETSELECTERROR(lParam);  
  29.     switch (WSAGETSELECTEVENT(lParam))  
  30.     {  
  31.     case FD_READ:  
  32.         {  
  33.             fd_set fds;  
  34.             int nReady;  
  35.             timeval timeout;  
  36.   
  37.             timeout.tv_sec = 0;  
  38.             timeout.tv_usec = 0;  
  39.   
  40.             FD_ZERO(&fds);  
  41.             FD_SET(pSocket->m_hSocket, &fds);  
  42.             nReady = select(0, &fds, NULL, NULL, &timeout);  
  43.             if (nReady == SOCKET_ERROR)  
  44.                 nErrorCode = WSAGetLastError();  
  45.             if ((nReady == 1) || (nErrorCode != 0))  
  46.                 pSocket->OnReceive(nErrorCode);  
  47.         }  
  48.         break;  
  49.     case FD_WRITE:  
  50.         pSocket->OnSend(nErrorCode);  
  51.         break;  
  52.     case FD_OOB:  
  53.         pSocket->OnOutOfBandData(nErrorCode);  
  54.         break;  
  55.     case FD_ACCEPT:  
  56.         pSocket->OnAccept(nErrorCode);  
  57.         break;  
  58.     case FD_CONNECT:  
  59.         pSocket->OnConnect(nErrorCode);  
  60.         break;  
  61.     case FD_CLOSE:  
  62.         pSocket->OnClose(nErrorCode);  
  63.         break;  
  64.     }  
  65. }  


关于CSocketWnd

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. 声明  
  2. class CSocketWnd : public CWnd  
  3. {  
  4. // Construction  
  5. public:  
  6.     CSocketWnd();  
  7.   
  8. protected:  
  9.     //{{AFX_MSG(CSocketWnd)  
  10.     LRESULT OnSocketNotify(WPARAM wParam, LPARAM lParam);  
  11.     LRESULT OnSocketDead(WPARAM wParam, LPARAM lParam);  
  12.     //}}AFX_MSG  
  13.     DECLARE_MESSAGE_MAP()  
  14. };  
  15.   
  16. 消息映射  
  17. BEGIN_MESSAGE_MAP(CSocketWnd, CWnd)  
  18.     //{{AFX_MSG_MAP(CWnd)  
  19.     ON_MESSAGE(WM_SOCKET_NOTIFY, &CSocketWnd::OnSocketNotify)  
  20.     ON_MESSAGE(WM_SOCKET_DEAD, &CSocketWnd::OnSocketDead)  
  21.     //}}AFX_MSG_MAP  
  22. END_MESSAGE_MAP()  
  23.   
  24. 实现  
  25. CSocketWnd::CSocketWnd()  
  26. {  
  27. }  
  28.   
  29. LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)  
  30. {  
  31.     CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam);  
  32.     CSocket::ProcessAuxQueue();  
  33.     return 0L;  
  34. }  
  35.   
  36. LRESULT CSocketWnd::OnSocketDead(WPARAM wParam, LPARAM lParam)  
  37. {  
  38.     CSocket::AuxQueueAdd(WM_SOCKET_DEAD, wParam, lParam);  
  39.     CSocket::ProcessAuxQueue();  
  40.     return 0L;  
  41. }  

当前线程的socket共享一个socket window


例如下面示例代码创建新的线程, 那么在新的线程环境创建了新的socket window
CAsyncSocket::Attach是进入了另一个线程环境

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // ...  
  2. class CSockThread : public CWinThread  
  3. {  
  4. // ... Other function and member declarations  
  5. protected:  
  6.   CSocket m_sConnected;  
  7. };  
  8.   
  9. SOCKET hConnected;  
  10.   
  11. BOOL CSockThread::InitInstance()  
  12. {  
  13.   // Attach the socket object to the socket handle  
  14.   // in the context of this thread.  
  15.   //   
  16.   m_sConnected.Attach(hConnected);  
  17.   
  18.   return TRUE;  
  19. }  
  20.   
  21. // This listening socket has been constructed  
  22. // in the primary thread.  
  23. //   
  24. void CListeningSocket::OnAccept(int nErrorCode)  
  25. {  
  26.   // This CSocket object is used just temporarily  
  27.   // to Accept the incoming connection.  
  28.   //   
  29.   CSocket sConnected;  
  30.   Accept(sConnected);  
  31.   
  32.   // Detach the newly accepted socket and save  
  33.   // the SOCKET handle  
  34.   hConnected = sConnected.Detach();  
  35.   
  36.   // After Detaching it, it should no longer be  
  37.   // used in the context of this thread  
  38.   
  39.   // Start the other thread  
  40.   AfxBeginThread(RUNTIME_CLASS(CSockThread));  
  41. }  




体系结构



WSAAsyncSelect  对应的 SPI是 WSPAsyncSelect

CSocket的Accept

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. BOOL CSocket::Accept(CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr, int* lpSockAddrLen)  
  2. {  
  3.     if (m_pbBlocking != NULL)  
  4.     {  
  5.         WSASetLastError(WSAEINPROGRESS);  
  6.         return FALSE;  
  7.     }  
  8.     while (!CAsyncSocket::Accept(rConnectedSocket, lpSockAddr, lpSockAddrLen))  
  9.     {  
  10.         if (GetLastError() == WSAEWOULDBLOCK)  
  11.         {  
  12.             if (!PumpMessages(FD_ACCEPT))  
  13.                 return FALSE;  
  14.         }  
  15.         else  
  16.             return FALSE;  
  17.     }  
  18.     return TRUE;  
  19. }  


AfxGetThread获取当前执行的线程的对象的指针

PumpMessages函数不断调用PeekMessage函数,直到获取到期望的消息时返回
PeekMessage和GetMessage都是从消息队列中获取消息,有消息时将消息分发出去。不同点是:当消息队列中没有消息时,GetMessage会一直等待,直到出现下一个消息时返回。而PeekMessage会在没有取得消息后,立即返回,这使得程序得以继续执行


[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. BOOL CSocket::PumpMessages(UINT uStopFlag)  
  2. {  
  3.     // The same socket better not be blocking in more than one place.  
  4.     ASSERT(m_pbBlocking == NULL);  
  5.   
  6.     _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;  
  7.   
  8.     ASSERT(pState->m_hSocketWindow != NULL);  
  9.   
  10.     BOOL bBlocking = TRUE;  
  11.     m_pbBlocking = &bBlocking;  
  12.     CWinThread* pThread = AfxGetThread();  
  13.   
  14.     // This is not a timeout in the WinSock sense, but more  
  15.     // like a WM_KICKIDLE to keep message pumping alive  
  16.     UINT_PTR nTimerID = ::SetTimer(pState->m_hSocketWindow, 1, m_nTimeOut, NULL);  
  17.   
  18.     if (nTimerID == 0)  
  19.         AfxThrowResourceException();  
  20.   
  21.     BOOL bPeek = TRUE;  
  22.   
  23.     while (bBlocking)  
  24.     {  
  25.         TRY  
  26.         {  
  27.             MSG msg;  
  28.             if (::PeekMessage(&msg, pState->m_hSocketWindow,  
  29.                 WM_SOCKET_NOTIFY, WM_SOCKET_DEAD, PM_REMOVE))  
  30.             {  
  31.                 if (msg.message == WM_SOCKET_NOTIFY && (SOCKET)msg.wParam == m_hSocket)  
  32.                 {  
  33.                     if (WSAGETSELECTEVENT(msg.lParam) == FD_CLOSE)  
  34.                     {  
  35.                         break;  
  36.                     }  
  37.                     if (WSAGETSELECTEVENT(msg.lParam) == uStopFlag)  
  38.                     {  
  39.                         if (uStopFlag == FD_CONNECT)  
  40.                             m_nConnectError = WSAGETSELECTERROR(msg.lParam);  
  41.                         break;  
  42.                     }  
  43.                 }  
  44.                 if (msg.wParam != 0 || msg.lParam != 0)  
  45.                     CSocket::AuxQueueAdd(msg.message, msg.wParam, msg.lParam);  
  46.   
  47.                 bPeek = TRUE;  
  48.             }  
  49.             else if (::PeekMessage(&msg, pState->m_hSocketWindow,  
  50.                         WM_TIMER, WM_TIMER, PM_REMOVE))  
  51.             {  
  52.             break;  
  53.             }  
  54.   
  55.             if (bPeek && ::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))  
  56.             {  
  57.                 if (OnMessagePending())  
  58.                 {  
  59.                     // allow user-interface updates  
  60.                     ASSERT(pThread);  
  61.                     pThread->OnIdle(-1);  
  62.                 }  
  63.                 else  
  64.                 {  
  65.                     bPeek = FALSE;  
  66.                 }  
  67.             }  
  68.             else  
  69.             {  
  70.                 // no work to do -- allow CPU to sleep  
  71.                 WaitMessage();  
  72.                 bPeek = TRUE;  
  73.             }  
  74.         }  
  75.         CATCH_ALL(e)  
  76.         {  
  77.             TRACE(traceSocket, 0, "Error: caught exception in PumpMessage - continuing.\n");  
  78.             DELETE_EXCEPTION(e);  
  79.             bPeek = TRUE;  
  80.         }  
  81.         END_CATCH_ALL  
  82.     }  
  83.   
  84.     ::KillTimer(pState->m_hSocketWindow, nTimerID);  
  85.   
  86.     if (!bBlocking)  
  87.     {  
  88.         WSASetLastError(WSAEINTR);  
  89.         return FALSE;  
  90.     }  
  91.     m_pbBlocking = NULL;  
  92.   
  93.     ::PostMessage(pState->m_hSocketWindow, WM_SOCKET_NOTIFY, 0, 0);  
  94.   
  95.     return TRUE;  
  96. }  

::PostMessage(pState->m_hSocketWindow, WM_SOCKET_NOTIFY, 0, 0);

向先前创建的CSocketWnd窗口发送WM_SOCKET_NOTIFY消息

PeekMessage通常不从队列里清除WM_PAINT消息。该消息将保留在队列里直到处理完毕。

但如果WM_PAINT消息有一个  NULL update region,PeekMessage将从队列里清除WM_PAINT消息

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值