MFC CSocket

2015-1-26 flyfish

继承关系



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

// 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));
}




体系结构



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)
{
	// 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消息




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西笑生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值