2015-1-26 flyfish
继承关系
class CSocket : public CAsyncSocket
class CSocketWnd : public CWnd
调用的是父类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 // AFX_MODULE_THREAD_STATE (local to thread *and* module) class AFX_MODULE_THREAD_STATE : public CNoTrackObject { public: AFX_MODULE_THREAD_STATE(); virtual ~AFX_MODULE_THREAD_STATE(); // current CWinThread pointer CWinThread* m_pCurrentWinThread; // list of CFrameWnd objects for thread CTypedSimpleList<CFrameWnd*> m_frameList; // temporary/permanent map state DWORD m_nTempMapLock; // if not 0, temp maps locked CHandleMap* m_pmapHWND; CHandleMap* m_pmapHMENU; CHandleMap* m_pmapHDC; CHandleMap* m_pmapHGDIOBJ; CHandleMap* m_pmapHIMAGELIST; // thread-local MFC new handler (separate from C-runtime) _PNH m_pfnNewHandler; #ifndef _AFX_NO_SOCKET_SUPPORT // WinSock specific thread state 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 // common controls thread state CToolTipCtrl* m_pToolTip; CWnd* m_pLastHit; // last window to own tooltip INT_PTR m_nLastHit; // last hittest code TOOLINFO* m_pLastInfo; // last TOOLINFO structure INT_PTR m_nLastStatus; // last flyby status message CControlBar* m_pLastStatus; // last flyby status control bar };
在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; // Has the socket be closed - lookup in dead handle list CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE); // If yes ignore message if (pSocket != NULL) return; pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, FALSE); if (pSocket == NULL) { // Must be in the middle of an Accept call 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 { // Construction public: CSocketWnd(); protected: //{{AFX_MSG(CSocketWnd) LRESULT OnSocketNotify(WPARAM wParam, LPARAM lParam); LRESULT OnSocketDead(WPARAM wParam, LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; 消息映射 BEGIN_MESSAGE_MAP(CSocketWnd, CWnd) //{{AFX_MSG_MAP(CWnd) ON_MESSAGE(WM_SOCKET_NOTIFY, &CSocketWnd::OnSocketNotify) ON_MESSAGE(WM_SOCKET_DEAD, &CSocketWnd::OnSocketDead) //}}AFX_MSG_MAP 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; }
// ... class CSockThread : public CWinThread { // ... Other function and member declarations protected: CSocket m_sConnected; }; SOCKET hConnected; BOOL CSockThread::InitInstance() { // Attach the socket object to the socket handle // in the context of this thread. // m_sConnected.Attach(hConnected); return TRUE; } // This listening socket has been constructed // in the primary thread. // void CListeningSocket::OnAccept(int nErrorCode) { // This CSocket object is used just temporarily // to Accept the incoming connection. // CSocket sConnected; Accept(sConnected); // Detach the newly accepted socket and save // the SOCKET handle hConnected = sConnected.Detach(); // After Detaching it, it should no longer be // used in the context of this thread // Start the other thread AfxBeginThread(RUNTIME_CLASS(CSockThread)); }
体系结构
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; }
PumpMessages函数不断调用PeekMessage函数,直到获取到期望的消息时返回
PeekMessage和GetMessage都是从消息队列中获取消息,有消息时将消息分发出去。不同点是:当消息队列中没有消息时,GetMessage会一直等待,直到出现下一个消息时返回。而PeekMessage会在没有取得消息后,立即返回,这使得程序得以继续执行
BOOL CSocket::PumpMessages(UINT uStopFlag) { // The same socket better not be blocking in more than one place. ASSERT(m_pbBlocking == NULL); _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState; ASSERT(pState->m_hSocketWindow != NULL); BOOL bBlocking = TRUE; m_pbBlocking = &bBlocking; CWinThread* pThread = AfxGetThread(); // This is not a timeout in the WinSock sense, but more // like a WM_KICKIDLE to keep message pumping alive 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()) { // allow user-interface updates ASSERT(pThread); pThread->OnIdle(-1); } else { bPeek = FALSE; } } else { // no work to do -- allow CPU to sleep 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; }
向先前创建的CSocketWnd窗口发送WM_SOCKET_NOTIFY消息
PeekMessage通常不从队列里清除WM_PAINT消息。该消息将保留在队列里直到处理完毕。
但如果WM_PAINT消息有一个 NULL update region,PeekMessage将从队列里清除WM_PAINT消息