CSocket

生活百科 2023-01-17 20:14生活百科www.aizhengw.cn

CSocket

CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类,它的定义包含在<afxsock.h>中。它是如何又把CAsyncSocket变成同步的,而且还能回响同样的Socket事件呢?

其实很简单,CSocket在Connect()返回WSAEWOULDBLOCK错误时,不是在OnConnect(),OnReceive()这些事件终端函数里去等待。你先必须明白Socket事件是如何到达这些事件函数里的。这些事件处理函式是CSocketWnd视窗对象回调的,而视窗对象收到来自Socket的事件,又是靠执行绪讯息伫列分发过来的。,Socket事件是作为一个讯息发给CSocketWnd视窗对象,这个讯息肯定需要经过执行绪讯息伫列的分发,最终CSocketWnd视窗对象收到这些讯息就调用相应的回调函式(OnConnect()等)。

所以,CSocket在调用Connect()之后,如果返回一个WSAEWOULDBLOCK错误时,它马上调用一个用于提取讯息的函式PumpMessage(...),就是从当前执行绪的讯息伫列里取关心的讯息.

情况

概况

CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类,它的定义包含在<afxsock.h>中。它是如何又把CAsyncSocket变成同步的,而且还能回响同样的Socket事件呢?
参考图片
其实很简单,CSocket在Connect()返回WSAEWOULDBLOCK错误时,不是在OnConnect(),OnReceive()这些事件终端函数里去等待。你先必须明白Socket事件是如何到达这些事件函数里的。这些事件处理函式是CSocketWnd视窗对象回调的,而视窗对象收到来自Socket的事件,又是靠执行绪讯息伫列分发过来的。,Socket事件是作为一个讯息发给CSocketWnd视窗对象,这个讯息肯定需要经过执行绪讯息伫列的分发,最终CSocketWnd视窗对象收到这些讯息就调用相应的回调函式(OnConnect()等)。
所以,CSocket在调用Connect()之后,如果返回一个WSAEWOULDBLOCK错误时,它马上调用一个用于提取讯息的函式PumpMessage(...),就是从当前执行绪的讯息伫列里取关心的讯息.

情况

PumpMessage会遇到下面几种情况
1 提取出了(从讯息伫列中移出来Remove),用户正在使用的一个Socket传送的WM_SOCKET_NOTIFY讯息和对应的 FD_XXX事件,返回True.
2 提取出了(从讯息伫列中移出来Remove),用户正在使用的一个Socket传送的WM_SOCKET_NOTIFY讯息和对应的 FD_Close事件,返回True.
3 提取出了(从讯息伫列中移出来Remove),PumpMessage(..)设定的定时器的WM_TIMER讯息,TimeOut事件为 CSocket的一个成员变数,m_nTimeOut=2000ms,返回True
4 用户调用了CancelBlockingCall() 设定错误代码为WSAEINTR(被中断了),返回False
5 用户一直没有取到用户正在使用的一个Socket传送的WM_SOCKET_NOTIFY讯息和对应的FD_XXX事件,取到了同一个执行绪中的其他Socket的WM_SOCKET_NOTIFY讯息及其对应的讯息,则将这些讯息,加入到一个辅助性的伫列中去,以后处理.
6 没有取到任何WM_SOCKET_NOTIFY讯息,则开始查看(不是取出来,而是查看)本执行绪的讯息伫列中是否有其它讯息,如果有的话,调用虚函式OnMessagePending(),来处理这些讯息(OnMessagePending()用户可以自定义。在阻塞时,用户想要处理的讯息),如果没有,则调用WaitMessage()开始等待讯息的到来.

说明

代码说明如下
A 先看Connect,因为Connect的阻塞的实现和Accept,Receive,ReceiveFrom,Send,SendTo都有点不同.
也许你们会奇怪为何是ConnectHelper(...),而不是Connect(...).其实ConnectHelper(...)才是Connect(..)

调用

真正调用的东西,如下
BOOL CAsyncSocket::Connect(const SOCKADDR lpSockAddr,int nSockAddrLen)
{
return ConnectHelper(lpSockAddr,nSockAddrLen);
}
//ConnectHelper(...)为一虚函式
//继承自CAsyncSocket,Csocket有重新定义过.
//这也是为什幺CSocket是Public继承的原因
BOOL CSocket::ConnectHelper(...)
{
//一旦调用 就先检查当前是否有一个阻塞操作正在进行
//如果是,立马返回,并设定错误代码.
......
......
m_nConnectError = -1;
//注意它只调用了一次CAsyncSocket::ConnectHelper(...)
if(!CAsyncSocket::ConnectHelper(...))
{
//由于Connect(...)要求自己和Server进行三步握手
//即需要传送一个Packet,而且等待回复(这个也就是
//涉及连线和传送数据操作的Socket API会阻塞的原因)
//所以CAsyncSocket::ConnectHelper(...)会立即返回,
//并且设定错误为WSAEWOULDBLOCK(调用该函式会导致阻塞)
if(WSAGetLastError() == WSAEWOULDBLOCK)
{
//进入讯息循环,以从执行绪讯息伫列里查看FD_CONNECT讯息,
//收到FD_CONNECT讯息(在PumpMessage中会修改m_nConnectError),返回
//或者WM_TIMER/FD_CLOSE(return true,但不会修改m_nConnectError),
//继续调用PumpMessage来取讯息
//或者错误,那幺就返回socket_error
while(PumpMessages(FD_CONNECT))
{
if (m_nConnectError != -1)
{
WSASetLastError(m_nConnectError);
return (m_nConnectError == 0);
}
} //end while
}
return false;
}
return true;
}
//在PumpMessages中会设定一个定时器,时间为m_nTimeOut=2000ms
//当在这个时间之内,依然没有得到讯息的话,就返回
BOOL CSocket::PumpMessages(UINT uStopFlag)
{
//一旦进入这个函式,就设定Socket当前状态为阻塞
BOOL bBlocking = TRUE;
m_pbBlocking = &bBlocking;
....................
.....................
....................
CWinThread pThread = AfxGetThread();
//bBlocking是一个标誌,
// 用来判断用户是否取消对Connect()的调用
//即是否调用CancelBlockingCall()
while(bBlocking)
{
//#define WM_SOCKET_NOTIFY 0x0373
//#define WM_SOCKET_DEAD 0x0374
MSG msg;
//在此处只是取WM_SOCKET_NOTIFY 和WM_SOCKET_DEAD讯息
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)
{
//这个是PumpMessage的第2种情况
if (WSAGETSELECTEVENT(msg.lParam) == FD_CLOSE)
{ break;}
//这个是PumpMessage的第1种情况
if(WSAGETSELECTEVENT(msg.lParam) == uStopFlag)
{ ......; break;}
}
//这个是PumpMessage的第5种情况
if (msg.wParam != 0 || msg.lParam != 0)
CSocket::AuxQueueAdd(msg.message,msg.wParam,msg.lParam);
}
//这个是PumpMessage的第3种情况
else if (::PeekMessage(&msg,pState->m_hSocketWindow,WM_TIMER,WM_TIMER,PM_REMOVE))
{ break;}
//这个是PumpMessage的第6种情况
if (bPeek && ::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
{
if (OnMessagePending())
{
}
else
{
// 等待讯息的到来
WaitMessage();
.....
}
}
}//end while
////这个是PumpMessage的第4种情况
if (!bBlocking)
{
WSASetLastError(WSAEINTR);
return FALSE;
}
m_pbBlocking = NULL;
//将WM_SOCKET_NOTIFY讯息传送到Creat CSocketWnd执行绪的讯息伫列中
//以便处理其他的Socket讯息
::PostMessage(pState->m_hSocketWindow,WM_SOCKET_NOTIFY,0,0);
return TRUE;
}
B 再看Receive(..)
//其实CSocket的这种实现方式决定了,应用程式不能够在一个执行绪中Create一个socket,
//然后创建一个新的执行绪来专门Receive,因为这个新的执行绪将永远不能取到FD_Read的事件,
//因为并不是在这个执行绪中创建的CSocketWnd对象,它无法接受到传送到CSocketWnd的讯息
//(在Windows中接受讯息的主体是视窗)
//Receive和Connect的实现方式的最大区别为
//Connect 是不断的调用PumpMessage(..)
//而Receive则不断的调用自身
int CSocket::Receive(void lpBuf,int nBufLen,int nFlags)
{
if (m_pbBlocking != NULL)
{
WSASetLastError(WSAEINPROGRESS);
returnFALSE;
}
int nResult;
while ((nResult = CAsyncSocket::Receive(lpBuf,nBufLen,nFlags)) == SOCKET_ERROR)
{
if (GetLastError() == WSAEWOULDBLOCK)
{
//一旦提取到FD_READ///FD_CLOSE///WM_TIMER时
// 就调用CAsyncSocket::Receive(...)
if (!PumpMessages(FD_READ))
return SOCKET_ERROR;
}
else
return SOCKET_ERROR;
}
return nResult;
}

CSocket模式与socket API模式的最大区别在于它实现了
1、将socket事件讯息化
2、用讯息阻塞方式实现同步
3、用序列化方式读写数据以防止死锁,简化读取数据模型(流数据)。
多执行绪编程时,由于CSocket不能跨执行绪使用,所以,新建工作执行绪中会有一个CSocket对象,而该对象会重新与已知SOCKET句柄重新Attach,实现在新的执行绪中以CSocket对象的方式编程。
然后使用讯息通信的方式,实现在新建执行绪中读写数据。
通过讯息、执行绪事件等同步机制 实现UI执行绪与CSocket对象所在工作执行绪的通信,并不会影响UI的回响。
在BOOL CSocket::PumpMessages(UINT uStopFlag)中
//...if(bPeek&&::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){if(OnMessagePending()){////allowuser-interfaceupdatesASSERT(pThread);pThread->OnIdle(-1);}else{bPeek=FALSE;}}//...
看OnMessagePending
BOOLCSocket::OnMessagePending(){MSGmsg;if(::PeekMessage(&msg,NULL,WM_PAINT,WM_PAINT,PM_REMOVE)){//重新处理当前执行绪所有视窗的WM_PAINT讯息,::DispatchMessage(&msg);//重新派送returnFALSE;//usuallyreturnTRUE,butOnIdleusuallycausesWM_PAINTs//(通常返回TRUE,就会导致OnIdle被执行,OnIdle通常导致多次WM_PAINT讯息)}returnFALSE;}
但上面针对的都是当前执行绪的WM_PAIT讯息,而不是跨执行绪的,而多执行绪模式下的SOCKET都是放在工作执行绪中执行的!

Copyright@2015-2025 www.aizhengw.cn 癌症网版板所有