|
吾爱游客
发表于 2011-5-15 22:05
1、ID:zyq8709/zyqqyz
2、邮箱:zyq8709@163.com
3、特长:熟悉并爱好c/c++,asm,sdk,mfc,驱动,逆向,破解等底层和黑客技术,发现贵论坛资源丰富高手如云,故想申请一账号和大家交流学习,希望管理员批准,刚才发过一遍,但未看要求不合格,故重发一遍。
附上我在看雪上申请会员的原创帖(已通过申请)
链接:http://bbs.pediy.com/showthread.php?t=130688
全文如下:
注:本文适合有一定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类讲的差不多了,其中的几个关键点也讲明白了,希望大家多多关注,谢谢大家的支持,初次写文章,希望大家多多谅解。
|
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|