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 (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;
}
当前线程的socket共享一个socket window
例如下面示例代码创建新的线程, 那么在新的线程环境创建了新的socket window
CAsyncSocket::Attach是进入了另一个线程环境
// ...
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;
}
::PostMessage(pState->m_hSocketWindow, WM_SOCKET_NOTIFY, 0, 0);
向先前创建的CSocketWnd窗口发送WM_SOCKET_NOTIFY消息
PeekMessage通常不从队列里清除WM_PAINT消息。该消息将保留在队列里直到处理完毕。
但如果WM_PAINT消息有一个 NULL update region,PeekMessage将从队列里清除WM_PAINT消息