深入剖析mfc---CSocket类工作原理

由于 前段时间需要使用mfc开发一个网络通信程序,于是就研究了一下CSocket类的工作过程,现将我的一些学习成果分享出来,希望大家批评指正。
先来看CSocket的create函数,它调用了基类CAsyncSocket::Create函数,下面跟进去看到
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;
}

先调用Socket函数,在此函数中先调用api函数socket创建一个套接字并返回给m_hSocket,如果创建成功就进行CAsyncSocket::AttachHandle,实现套接字句柄与CSocket类对象的绑定,在绑定函数中创建了一个CSocketWnd对象,并创建一个窗口但并不显示,我称之为socket窗口,在创建之前有个判断pState->m_pmapSocketHandle->IsEmpty(),作用是检查socket的绑定映射是否为空,如果是空就不再创建socket窗口了,目的是节省资源,也就是保证只有一个socket窗口。socket窗口用于接收网络消息,并处理消息响应。接下来调用CAsyncSocket::AsyncSelect函数,其中又调用了WSAAsyncSelect函数,创建异步套接字,即当一些网络行为发生时向socket窗口发送WM_SOCKET_NOTIFY消息,然后消息处理程序在进行分类处理。这是CSocket类工作的核心。 这里注册了这些事件FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,详细含义可参考msdn。即这些事件发生时向socket窗口发送WM_SOCKET_NOTIFY消息。接下来的Bind函数无非就是做一些初始化并调用api函数bind。如果出错就关闭套接字,成功就返回。


接下来按标准过程就该监听调用CSocket::Listen函数,此函数很简单,就是调用api函数listen。
下面我来说核心的另一方面,消息处理过程。socket窗口接到WM_SOCKET_NOTIFY消息,根据消息映射,调用CSocketWnd::OnSocketNotify,其中调用静态函数CSocket::ProcessAuxQueue,经过一些判断保护,调用了CAsyncSocket::DoCallBack,同样他也是个静态函数。
这个函数最关键,我把代码放上来。详细解释我放在注释里。
void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)
{
if (wParam == 0 && lParam == 0)
return;

CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);//根据WM_SOCKET_NOTIFY消息的wParam参数寻找对应CSocket对象,这是一个保护,如果对象为空直接返回。因为在异步模式下,每发出一回动作,就会发出WM_SOCKET_NOTIFY消息,如果连续发出读操作,然后关闭套接字。那么当处理多余的消息时套接字已不存在,那么就直接返回。

if (pSocket != NULL)
return;

pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, FALSE);
if (pSocket == NULL)
{
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))//根据lParam的内容分类处理不同事件的操作,并在其中调用程序员自己编写的处理函数如OnReceive等。
{
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函数即接受连接函数中,就是调用api函数accept这么简单。


下面一点也比较关键,也就是在异步套接字中进行操作recevie等操作时如何实现同步即阻塞。
看代码:
int CSocket::Receive(void* lpBuf, int nBufLen, int nFlags)

{

//m_pbBlocking是CSocket的成员变量,用来标识当前是否正在进行

//阻塞操作。但不能同时进行两个阻塞操作。

if (m_pbBlocking != NULL)

{

WSASetLastError(WSAEINPROGRESS);

return FALSE;

}

//完成数据读取

int nResult;

while ((nResult = CAsyncSocket::Receive(lpBuf, nBufLen, nFlags)) 

== SOCKET_ERROR)

{

if (GetLastError() == WSAEWOULDBLOCK)

{

//进入消息循环,等待网络事件FD_READ

if (!PumpMessages(FD_READ))

return SOCKET_ERROR;

}

else

return SOCKET_ERROR;

}

return nResult;

}
Receive函数首先判断当前CSocket对象是否正在处理一个阻塞操作,如果是,则返回错误WSAEINPROGRESS;否则,开始数据读取的处理。读取数据时,如果基类CAsyncSocket的Receive读取到了数据,则返回;否则,如果返回一个错误,而且错误号是WSAEWOULDBLOCK,则表示操作阻塞,于是调用PumpMessage进入消息循环等待数据到达(网络事件FD_READ发生)。数据到达之后退出消息循环,再次调用CAsyncSocket的Receive读取数据,直到没有数据可读为止。PumpMessages函数中其实就是不断调用PeekMessage函数,直到取到希望的消息时返回。
当CSocket对象析构时会自动调用Close函数关闭套接字或主动关闭。其实其中调用的是CAsyncSocket::Close,又调用CAsyncSocket::KillSocket,其中又调用CAsyncSocket::DetachHandle将对象与套接字分离,并关闭套接字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值