首页 > 代码库 > CSocket
CSocket
2015-1-26 flyfish
继承关系
class CSocket : public CAsyncSocket
class CSocketWnd : public CWnd
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 (<strong>Socket</strong>(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) { <strong>CAsyncSocket::AttachHandle</strong>(m_hSocket, this, FALSE); return <strong>AsyncSelect</strong>(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自身的运作模式
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); <strong>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;</strong> } 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); }
BOOL CAsyncSocket::AsyncSelect(long lEvent) { ASSERT(m_hSocket != INVALID_SOCKET); _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState; ASSERT(pState->m_hSocketWindow != NULL); return <strong>WSAAsyncSelect</strong>(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; } }
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 (!<strong>PumpMessages</strong>(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; }
::PostMessage(pState->m_hSocketWindow, WM_SOCKET_NOTIFY, 0, 0);
向先前创建的CSocketWnd窗口发送WM_SOCKET_NOTIFY消息
PeekMessage通常不从队列里清除WM_PAINT消息。该消息将保留在队列里直到处理完毕。
但如果WM_PAINT消息有一个 NULL update region,PeekMessage将从队列里清除WM_PAINT消息
CSocket