首页
社区
课程
招聘
[原创]深入剖析mfc---CSocket类工作细节原理
发表于: 2011-3-11 20:18 6674

[原创]深入剖析mfc---CSocket类工作细节原理

2011-3-11 20:18
6674

注:本文适合有一定Winsock编程和mfc编程基础的朋友阅读。
我是一个对一切事情都爱探究到底的人,前一段由于需要使用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将对象与套接字分离,并关闭套接字。
好吧,这些已经将CSocket类讲的差不多了,其中的几个关键点也讲明白了,希望大家多多关注,谢谢大家的支持,初次写文章,希望大家多多谅解。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (2)
雪    币: 693
活跃值: (108)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
自己顶一下!!!
2011-3-13 18:04
0
雪    币: 2105
活跃值: (424)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
嗯 写的不错
2011-3-14 09:42
0
游客
登录 | 注册 方可回帖
返回
//