首页 > 代码库 > CSocket

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 (<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;
}


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消息




CSocket