充实每一天,终成不凡人
分类: Windows平台
2014-03-11 23:15:44
这几天都在研究MFC的套接字类CAsyncSocket用法, 将一些心得和实践中遇到的问题总结一下。 一、 一些网络的基本概念
1. 同步:指的是发送方不等接收方响应,便接着发下个数据包的通信方式; 2. 异步:指发送方发出数据后,等收到接收方发回的响应,才发下一个数据包的通信方式 3. 阻塞:指调用某函数时,直到该函数完成操作,才返回;否则一直阻塞在该调用上 4. 非阻塞:指调用某操作时,不管操作是否成功都立即返回,而不会挂在该操作上
CAsyncSocket属于异步非阻塞类; CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类
二、 CAsyncSocket的使用(伪码)
服务器端:
m_pListSocket = new CAsyncSocket();
m_pListSocket-> Create( 端口,地址); // 创建 m_pListSocket->Listen(); // 开始监听 m_pListSocket::OnAccept( ) // 有客户端请求连接时响应 { m_pSocket = new CAsyncSocket(); m_pListSocket-> Accept(m_pSocket); // 建立通信,成功后m_pSocket就用于发送和接受. // m_pSocket 就相当于连接的那个客户端了 } m_pSocket::OnRecive( int nErrorCode) { if( nErrorCode == 0) { Recevie(); // 接受客户端发送来的信息 } CAsyncSocket::OnRecive( nErrorCode) } m_pSocket::OnSend() { Send(); // 发送信息,该事件触发条件见下节 } m_pListSocket->Close(); delete m_pListSocket; delete m_pSocket;
客户端:
m_pClientSocket = new CAsyncSocket();
m_pClientSocket -> Create( 端口,地址); // 创建 m_pClientSocket->Connect(); // 连接服务器,最终将触发服务器的OnAccept(); m_ pClientSocket::OnConnect() // 当连接上服务器 { } m_ pClientSocket::OnRecive( int nErrorCode) { if( nErrorCode == 0) { Recevie(); // 接受客户端发送来的信息 } CAsyncSocket::OnRecive( nErrorCode) } m_pClientSocket::OnSend( int nErrorCode) { Send(); // 发送信息 } m_pClientSocket->Close(); |
三、 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的通信流程;
OnSend,除了在对方发送消息来的时候响应外,还会在缓冲区有空闲的时候自动触发;
如果每次发送的数据比较简单,不会造成WASEWOULDBLOCK(阻塞),不会触发OnSend;
因此小数据直接Send就行了,大数据就需要在OnSend判断数据发送是否正确;
如何手动触发OnSend()呢,采用AsyncSelect( FD_WRITE),通知CsocketWnd窗口处理写
数据操作; 同样AsyncSelect(FD_READ)将通知CsocketWnd窗口当有消息传来的时候触发OnRecevie();
BOOL AsyncSelect( long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE ); // 请求Socket响应以上事件
五、 消息为何只接收一次
编程中遇到这个问题,发现很多人都遇到过这个问题。
症状如下:Socket连接后只能发送一次消息,发送第二次消息的时候,另一方就接收不到;
原因是:没有让Socket改变响应事件的发式
解决方法:在OnReceive()中,Receive()后调用AsyncSelect(FD_READ);
或则 调用父类的OnReceive()
六、 为何服务器Socket不监听
在创建服务器Socket的时候,只有采用SOCK_STREAM(字符流),Listen才能成功;
采用SOCK_DGRAM(数据报文)创建的Socket是面向无连接发式(UDP),所以Listen不成功(有待验证)
用CAsyncSocket类实现TCP UDP的模型
网络上一般介绍的都是如何用api函数实现TCP UDP的模型。这些模型虽然效率高,但是明显破坏了oo设计的初衷,本人经过实践,总结别人的经验,用CAsyncSocket实现了TCP UDP的通讯模型,该模型结构清晰,容易扩展,使用方便,很值得借鉴。在以前的ACE库使用中,我记得也用了这样的模型,在使用中的效果非常好,有机会,我会把ACE库的这种模式总结出来,另,不知道这种模式在设计模式中叫什么,有懂行的朋友给说一下
TCP模型
实现服务器需要两个派生自CAsyncSocket类的子类,一个用于Accept,一个用于Process。具体实现如下
CListenSocket继承CAsyncSocket,重载OnAccpet,在OnAccept中Accept(*processSocket),然后在CprocessSocket中处理就可以了
CProcessSocket继承CAsyncSocket,重载OnSend OnReceive OnClose,通过AsyncSelect来切换读写的操作,在OnClose中记得delete本身就好了(这个是因为在accept中是new来的,不然回内存泄露)
实现客户端比较简单,只需要一个继承自CAsyncSocket的类,我们假设叫CConnectSocket,需要重载OnConnect OnSend OnReceive OnClose. OnConnect主要是为了受到接通时的事件,注意处理errorcode,其他跟服务器类似
使用时,服务器用Create 并listen就可以使用了,客户端Create 然后Connect就进入使用循环,很简单吧?
UDP模型
UDP模型比TCP要简单的多,不分服务器端跟客户端。也可以称为是对等的,都使用相同的派生于CAsyncSocket的类,这里假设为CupdSocket,重载OnReceive,
使用时,直接创建Create,然后用SendTo发送数据,接受数据都在OnReceive中,不需要AsyncSelect来切换,非常简单。
以上并不是新方法,只不过能更清晰地让你组织你的工程,非常方便的一个东西,希望能对你有用。
http://www.cnblogs.com/wqj1212/archive/2009/03/18/1415786.html
http://blog.csdn.net/fengjl026/article/details/1177086