您的位置:首页 > 其它

CAsyncSocket详解

2015-10-27 14:47 141 查看
一、CAsyncSocket异步机制

当你获得了一个异步连接后,实际上你扫除了发送动作与接收动作之间的依赖性。所以你随时可以发包,也随时可能收到包。发送、接收函数都是异步非阻塞的,顷刻就能完成,所以收发交错进行着,你可以一直工作,保持很高的效率。但是,正因为发送、接收函数都是异步非阻塞的,所以仅调用它们并不能保障发送或接收的完成。例如发送函数Send,调用它可能有3种结果:错误、部分完成、全部完成。其中错误又分两种情况:一种是由各种网络问题导致的失败,你需要马上决定是放弃本次操作,还是启用某种对策;另一种是“忙”,你实际上不用马上理睬。你需要调用GetLastError来判断是哪种情况,GetLastError返回WSAEWOULDBLOCK,代表“忙”,为什么当你Send得到WSAEWOULDBLOCK却不用理睬呢?因为CAsyncSocket会记得你的SendWSAEWOULDBLOCK了,待发送的数据会写入CAsyncSocket内部的发送缓冲区,并会在不忙的时候自动调用OnSend,发送内部缓冲区里的数据。同样,如果Send只完成了一部分,你也不需要理睬,尚未发送的数据同样会写入CAsyncSocket内部的发送缓冲区,并在适当的时候自动调用OnSend完成发送。与OnSend协助Send完成工作一样,OnRecieve、OnConnect、OnAccept也会分别协助Recieve、Connect、Accept完成工作。这一切都通过消息机制完成:在你使用CAsyncSocket之前,必须调用AfxSocketInit初始化WinSock环境,而AfxSocketInit会创建一个隐藏的CSocketWnd对象,由于这个对象由Cwnd派生,因此它能够接收Windows消息。一方面它会接受各个CAsyncSocket的状态报告,另一方面它能捕捉系统发出的各种SOCKET事件。所以它能够成为高层CAsyncSocket对象与WinSock底层之间的桥梁:例如某CAsyncSocket在Send时WSAEWOULDBLOCK了,它就会发送一条消息给CSocketWnd作为报告,CSocketWnd会维护一个报告登记表,当它收到底层WinSock发出的空闲消息时,就会检索报告登记表,然后直接调用报告者的OnSend函数。所以前文所说的CAsyncSocket会自动调用OnXxx,实际上是不对的,真正的调用者是CSocketWnd——它是一个CWnd对象,运行在独立的线程中。

二、CAsyncSocket的用法

你会发现直接使用CAsyncSocket类来创建对象很难满足业务需要。因为CAsyncSocket隐藏了很多细节并不告诉你,你对业务的控制能力得不到保障。例如你需要知道每笔订单发给对端业务系统的确切时间,如果你直接使用CAsyncSocket的话,你无从得知,因为每次只要Send函数不是马上全部完成,而是进入了缓冲区等待CSocketWnd调用OnSend,这个订单到底是啥时候发出去的就很难说了。OnSend内部毕竟还是调用Send来发送的,所以可能OnSend一次也并不能完全发送,还要等待下一次甚至多次OnSend。而且这笔糊涂账全闷在CSocketWnd肚子里,它并不提供一个让你了解的机会。接收就更成问题了,通常业务系统会定义多种类型的数据包格式,你既不知道下一个将收到的包是哪种包,也不知道它将何时抵达。所以你在业务逻辑上永远不应该主动调用Recieve,而应该被动等待数据抵达时OnRecieve被自动调用,但OnRecieve也不懂你的业务包的格式,甚至也不会向你报告请示。CAsyncSocket只能处理字节流,但你需要处理的通常是业务流。所以,使用CAsyncSocket,通常是需要派生的,一般你至少需要重载OnSend和OnRecieve,你需要参与其中以进行业务控制。当然,既然你重载了OnSend和OnRecieve,你得自己编码实现协助Send、Recieve保证完成收发,你可能发现你自己并不能访问CAsyncSocket的内部缓冲区,所以你还要自己定义收发缓冲区并记录收发进度。其实你不大可能在OnSend或OnRecieve里面做好一切,你往往不得不

与你的主线程交互,你可以参考以下两种模式:第一种,在你的CXxxSocket类中申明一个你的主线程类指针,作为成员变量,典型的情况下,主线程是一个CXxxDlg,所以:

//CXxxSocket类头文件

Class CXxxDlg;

Class CXxxSocket : public CAsyncSocket

{

CXxxDlg* m_pDlg;

...

virtual void OnReceive(int nErrorCode);

}

//CXxxSocket类实现文件

...

void CXxxSocket::OnReceive(int nErrorCode)

{

...

m_pDlg->SomeFunc();

...

}

...

//CXxxDlg类实现文件

...

CXxxSocket sock;

sock.m_pDlg=this;

sock.Create(...);

...

这种方法很方便,但并不灵活,代码可重用性低,只有CXxxDlg类能够使用CXxxSocket类。

第二种,在你的CXxxSocket类中申明一个窗口句柄作为成员变量,当你需要主线程参与工作时,向主线程发送自定义消息:

//CXxxSocket类头文件

static UINT WM_SOCKET_MSG=::RegisterWindowMessage("SocketMsg");

Class CXxxSocket : public CAsyncSocket

{

HWND m_hWnd;

...

virtual void OnReceive(int nErrorCode);

}

//CXxxSocket类实现文件

...

void CXxxSocket::OnReceive(int nErrorCode)

{

...

SendMessage(m_hWnd,WM_SOCKET_MSG,...);

...

}

...

//CXxxDlg类实现文件

...

CXxxSocket sock;

sock.m_hWnd=GetSafeHwnd();

sock.Create(...);

...

三、CAsyncSocket的运作流程

使用CAsyncSocket时,Send流程和Recieve流程是不同的,不理解这一点就不可能顺利使用CAsyncSocket。MSDN对CAsyncSocket的解释很容易让你理解为:只有OnSend被触发时你Send才有意义,你才应该Send,同样只有OnRecieve被触发时你才应该Recieve。很不幸,你错了:你会发现,连接建立的同时,OnSend就第一次被触发了,嗯,这很好,但你现在还不想Send,你让OnSend返回,干点其他的事情,等待下一次OnSend试试看?实际上,你再也等不到OnSend被触发了。因为,除了第一次以外,OnSend的任何一次触发,都源于你调用了Send,但碰到了WSAEWOULDBLOCK或只完成了部分发送!所以,使用CAsyncSocket时,针对发送的流程逻辑应该是:你需两个

成员变量,一个发送任务表,一个记录发送进度。你可以,也应该,在任何你需要的时候,主动调用Send来发送数据,同时更新任务表和发送进度。而OnSend,则是你的负责擦屁股工作的助手,它被触发时要干的事情就是根据任务表和发送进度调用Send继续发,若此次发送没能将任务表全部发送完成,根据发送结果更新发送进度;若任务表

已全部发送完毕,则清空任务表及发送进度。使用CAsyncSocket的接收流程逻辑是不同的:你永远不需要主动调用Recieve,你只应该在OnRecieve中等待。由于你不可能知道将要抵达的数据类型及次序,所以你需要一个成员变量来存储已收到但尚未处理的数据。每次OnRecieve被触发,你只需要被动调用一次Recieve来接受固定长度的数据,并添加到你的已收数据表后。然后你需要扫描已收数据表,若其中已包含一条或数条完整的可解析的业务数据包,截取出来,调用主线程的处理函数来处理或作为消息参数发送给主线程。而已收数据表中剩下的数据,将等待下次OnRecieve中被再次组合、扫描并处理。在长连接应用中,连接可能因为各种原因中断,所以你需要自动重连。你需要根据CAsyncSocket的成员变量m_hSocket来判断当前连接状态:

if(m_hSocket==INVALID_SOCKET)

当然,很奇怪的是,即使连接已经中断,OnClose也已经被触发,你还是需要在OnClose中调用Close,否则m_hSocket并不会被自动赋值为INVALID_SOCKET。

在很多长连接应用中,除建立连接以外,还需要先Login,然后才能进行业务处理,连接并Login是一个步骤依赖性过程,用异步方式处

理反而会很麻烦,而CAsyncSocket是支持切换为同步模式的,你应该掌握在适当的时候切换同异步模式的方法:

DWORD dw;

//切换为同步模式

dw=0;

IOCtl(FIONBIO,&dw);

...

//切换回异步模式

dw=1;

IOCtl(FIONBIO,&dw);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: