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

- CAsyncSocket::Create(nSocketPort, nSocketType, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT |
-
- FD_CLOSE, lpszSocketAddress);
-
- BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType,
- long lEvent, LPCTSTR lpszSocketAddress)
- {
- if (Socket(nSocketType, lEvent))
- {
- if (Bind(nSocketPort,lpszSocketAddress))
- return TRUE;
- int nResult = GetLastError();
- Close();
- WSASetLastError(nResult);
- }
- return FALSE;
- }
- BOOL CAsyncSocket::Socket(int nSocketType, long lEvent,
- int nProtocolType, int nAddressFormat)
- {
- ASSERT(m_hSocket == INVALID_SOCKET);
-
- m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType);
- if (m_hSocket != INVALID_SOCKET)
- {
- CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);
- return AsyncSelect(lEvent);
- }
- return FALSE;
- }
创建一个不可见的窗口CSocketWnd
第一步new 一个C++对象
第二步调用CWnd的成员函数Create创建真正的Windows对象
管理windows窗口对象是通过句柄完成的
Attach是将C++对象与WINDOWS对象关联
detach是分离关联
所以多线程使用CAsyncSocket 要么attach和detach操作,要么利用窗口句柄向窗口发送消息
windows程序的运行的本质就是 以消息为基础(Message Based),事件驱动(Event Driven)。
把socket的消息映射到windows窗口的消息循环,很符合windows自身的运作模式
它与windows消息集成在一起,实现异步套接字 不必采用多线程或者管理同步对象,可以通过windows消息或者执行回调函数来接收一个操作完成的通知。
- void PASCAL CAsyncSocket::AttachHandle(
- SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)
- {
- _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
-
- BOOL bEnable = AfxEnableMemoryTracking(FALSE);
-
- TRY
- {
- if (!bDead)
- {
- ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);
- if (pState->m_pmapSocketHandle->IsEmpty())
- {
- ASSERT(pState->m_pmapDeadSockets->IsEmpty());
- ASSERT(pState->m_hSocketWindow == NULL);
-
- CSocketWnd* pWnd = new CSocketWnd;
- pWnd->m_hWnd = NULL;
-
- if (!pWnd->CreateEx(0, AfxRegisterWndClass(0),
- _T("Socket Notification Sink"),
- WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))
- {
- TRACE(traceSocket, 0, "Warning: unable to create socket notify window!\n");
- delete pWnd;
- AfxThrowResourceException();
- }
-
- ASSERT(pWnd->m_hWnd != NULL);
- ASSERT(CWnd::FromHandlePermanent(pWnd->m_hWnd) == pWnd);
- pState->m_hSocketWindow = pWnd->m_hWnd;
- }
- pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
- }
- else
- {
- void* pvCount;
- INT_PTR nCount;
- if (pState->m_pmapDeadSockets->Lookup((void*)hSocket, pvCount))
- {
- nCount = (INT_PTR)pvCount;
- nCount++;
- }
- else
- nCount = 1;
- pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
- }
- }
- CATCH_ALL (e)
- {
- AfxEnableMemoryTracking(bEnable);
- THROW_LAST();
- }
- END_CATCH_ALL
-
- AfxEnableMemoryTracking(bEnable);
- }
AttachHandle中
- #define _afxSockThreadState AfxGetModuleThreadState()
- #define _AFX_SOCK_THREAD_STATE AFX_MODULE_THREAD_STATE
-
-
- class AFX_MODULE_THREAD_STATE : public CNoTrackObject
- {
- public:
- AFX_MODULE_THREAD_STATE();
- virtual ~AFX_MODULE_THREAD_STATE();
-
-
- CWinThread* m_pCurrentWinThread;
-
-
- CTypedSimpleList<CFrameWnd*> m_frameList;
-
-
- DWORD m_nTempMapLock;
- CHandleMap* m_pmapHWND;
- CHandleMap* m_pmapHMENU;
- CHandleMap* m_pmapHDC;
- CHandleMap* m_pmapHGDIOBJ;
- CHandleMap* m_pmapHIMAGELIST;
-
-
- _PNH m_pfnNewHandler;
-
- #ifndef _AFX_NO_SOCKET_SUPPORT
-
- HWND m_hSocketWindow;
- #ifdef _AFXDLL
- CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapSocketHandle;
- CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapDeadSockets;
- CEmbeddedButActsLikePtr<CPtrList> m_plistSocketNotifications;
- #else
- CMapPtrToPtr* m_pmapSocketHandle;
- CMapPtrToPtr* m_pmapDeadSockets;
- CPtrList* m_plistSocketNotifications;
- #endif
- #endif
-
-
- CToolTipCtrl* m_pToolTip;
- CWnd* m_pLastHit;
- INT_PTR m_nLastHit;
- TOOLINFO* m_pLastInfo;
- INT_PTR m_nLastStatus;
- CControlBar* m_pLastStatus;
- };
在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);
- BOOL CAsyncSocket::AsyncSelect(long lEvent)
- {
- ASSERT(m_hSocket != INVALID_SOCKET);
-
- _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
- ASSERT(pState->m_hSocketWindow != NULL);
-
- return WSAAsyncSelect(m_hSocket, pState->m_hSocketWindow,
- WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR;
- }
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)
- void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)
- {
- if (wParam == 0 && lParam == 0)
- return;
-
-
- CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);
-
-
- if (pSocket != NULL)
- return;
-
- pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, FALSE);
- if (pSocket == NULL)
- {
-
- pSocket = CAsyncSocket::LookupHandle(INVALID_SOCKET, FALSE);
- ASSERT(pSocket != NULL);
-
- if(pSocket == NULL)
- return;
-
- pSocket->m_hSocket = (SOCKET)wParam;
- CAsyncSocket::DetachHandle(INVALID_SOCKET, FALSE);
- CAsyncSocket::AttachHandle(pSocket->m_hSocket, pSocket, FALSE);
- }
-
- int nErrorCode = WSAGETSELECTERROR(lParam);
- switch (WSAGETSELECTEVENT(lParam))
- {
- case FD_READ:
- {
- fd_set fds;
- int nReady;
- timeval timeout;
-
- timeout.tv_sec = 0;
- timeout.tv_usec = 0;
-
- FD_ZERO(&fds);
- FD_SET(pSocket->m_hSocket, &fds);
- nReady = select(0, &fds, NULL, NULL, &timeout);
- if (nReady == SOCKET_ERROR)
- nErrorCode = WSAGetLastError();
- if ((nReady == 1) || (nErrorCode != 0))
- pSocket->OnReceive(nErrorCode);
- }
- break;
- case FD_WRITE:
- pSocket->OnSend(nErrorCode);
- break;
- case FD_OOB:
- pSocket->OnOutOfBandData(nErrorCode);
- break;
- case FD_ACCEPT:
- pSocket->OnAccept(nErrorCode);
- break;
- case FD_CONNECT:
- pSocket->OnConnect(nErrorCode);
- break;
- case FD_CLOSE:
- pSocket->OnClose(nErrorCode);
- break;
- }
- }
关于CSocketWnd
- 声明
- class CSocketWnd : public CWnd
- {
-
- public:
- CSocketWnd();
-
- protected:
-
- LRESULT OnSocketNotify(WPARAM wParam, LPARAM lParam);
- LRESULT OnSocketDead(WPARAM wParam, LPARAM lParam);
-
- DECLARE_MESSAGE_MAP()
- };
-
- 消息映射
- BEGIN_MESSAGE_MAP(CSocketWnd, CWnd)
-
- ON_MESSAGE(WM_SOCKET_NOTIFY, &CSocketWnd::OnSocketNotify)
- ON_MESSAGE(WM_SOCKET_DEAD, &CSocketWnd::OnSocketDead)
-
- END_MESSAGE_MAP()
-
- 实现
- CSocketWnd::CSocketWnd()
- {
- }
-
- LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)
- {
- CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam);
- CSocket::ProcessAuxQueue();
- return 0L;
- }
-
- LRESULT CSocketWnd::OnSocketDead(WPARAM wParam, LPARAM lParam)
- {
- CSocket::AuxQueueAdd(WM_SOCKET_DEAD, wParam, lParam);
- CSocket::ProcessAuxQueue();
- return 0L;
- }
当前线程的socket共享一个socket window
例如下面示例代码创建新的线程, 那么在新的线程环境创建了新的socket window
CAsyncSocket::Attach是进入了另一个线程环境
-
- class CSockThread : public CWinThread
- {
-
- protected:
- CSocket m_sConnected;
- };
-
- SOCKET hConnected;
-
- BOOL CSockThread::InitInstance()
- {
-
-
-
- m_sConnected.Attach(hConnected);
-
- return TRUE;
- }
-
-
-
-
- void CListeningSocket::OnAccept(int nErrorCode)
- {
-
-
-
- CSocket sConnected;
- Accept(sConnected);
-
-
-
- hConnected = sConnected.Detach();
-
-
-
-
-
- AfxBeginThread(RUNTIME_CLASS(CSockThread));
- }
体系结构

WSAAsyncSelect 对应的 SPI是 WSPAsyncSelect
CSocket的Accept
- BOOL CSocket::Accept(CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr, int* lpSockAddrLen)
- {
- if (m_pbBlocking != NULL)
- {
- WSASetLastError(WSAEINPROGRESS);
- return FALSE;
- }
- while (!CAsyncSocket::Accept(rConnectedSocket, lpSockAddr, lpSockAddrLen))
- {
- if (GetLastError() == WSAEWOULDBLOCK)
- {
- if (!PumpMessages(FD_ACCEPT))
- return FALSE;
- }
- else
- return FALSE;
- }
- return TRUE;
- }
AfxGetThread获取当前执行的线程的对象的指针
PumpMessages函数不断调用PeekMessage函数,直到获取到期望的消息时返回
PeekMessage和GetMessage都是从消息队列中获取消息,有消息时将消息分发出去。不同点是:当消息队列中没有消息时,GetMessage会一直等待,直到出现下一个消息时返回。而PeekMessage会在没有取得消息后,立即返回,这使得程序得以继续执行
- BOOL CSocket::PumpMessages(UINT uStopFlag)
- {
-
- ASSERT(m_pbBlocking == NULL);
-
- _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
-
- ASSERT(pState->m_hSocketWindow != NULL);
-
- BOOL bBlocking = TRUE;
- m_pbBlocking = &bBlocking;
- CWinThread* pThread = AfxGetThread();
-
-
-
- UINT_PTR nTimerID = ::SetTimer(pState->m_hSocketWindow, 1, m_nTimeOut, NULL);
-
- if (nTimerID == 0)
- AfxThrowResourceException();
-
- BOOL bPeek = TRUE;
-
- while (bBlocking)
- {
- TRY
- {
- MSG msg;
- if (::PeekMessage(&msg, pState->m_hSocketWindow,
- WM_SOCKET_NOTIFY, WM_SOCKET_DEAD, PM_REMOVE))
- {
- if (msg.message == WM_SOCKET_NOTIFY && (SOCKET)msg.wParam == m_hSocket)
- {
- if (WSAGETSELECTEVENT(msg.lParam) == FD_CLOSE)
- {
- break;
- }
- if (WSAGETSELECTEVENT(msg.lParam) == uStopFlag)
- {
- if (uStopFlag == FD_CONNECT)
- m_nConnectError = WSAGETSELECTERROR(msg.lParam);
- break;
- }
- }
- if (msg.wParam != 0 || msg.lParam != 0)
- CSocket::AuxQueueAdd(msg.message, msg.wParam, msg.lParam);
-
- bPeek = TRUE;
- }
- else if (::PeekMessage(&msg, pState->m_hSocketWindow,
- WM_TIMER, WM_TIMER, PM_REMOVE))
- {
- break;
- }
-
- if (bPeek && ::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
- {
- if (OnMessagePending())
- {
-
- ASSERT(pThread);
- pThread->OnIdle(-1);
- }
- else
- {
- bPeek = FALSE;
- }
- }
- else
- {
-
- WaitMessage();
- bPeek = TRUE;
- }
- }
- CATCH_ALL(e)
- {
- TRACE(traceSocket, 0, "Error: caught exception in PumpMessage - continuing.\n");
- DELETE_EXCEPTION(e);
- bPeek = TRUE;
- }
- END_CATCH_ALL
- }
-
- ::KillTimer(pState->m_hSocketWindow, nTimerID);
-
- if (!bBlocking)
- {
- WSASetLastError(WSAEINTR);
- return FALSE;
- }
- m_pbBlocking = NULL;
-
- ::PostMessage(pState->m_hSocketWindow, WM_SOCKET_NOTIFY, 0, 0);
-
- return TRUE;
- }
::PostMessage(pState->m_hSocketWindow, WM_SOCKET_NOTIFY, 0, 0);
向先前创建的CSocketWnd窗口发送WM_SOCKET_NOTIFY消息
PeekMessage通常不从队列里清除WM_PAINT消息。该消息将保留在队列里直到处理完毕。
但如果WM_PAINT消息有一个 NULL update region,PeekMessage将从队列里清除WM_PAINT消息