CAsyncSocket详解

一、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);


  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值