<iframe align="top" marginwidth="0" marginheight="0" src="http://www.zealware.com/csdnblog01.html" frameborder="0" width="728" scrolling="no" height="90"></iframe>
Socket API,CAsyncSocket,CSocket内幕及其用法 |
〖文章转载或出处〗≡中国电子技术信息网≡ 网址:www.CETINet.com |
SocketAPI,CAsyncSocket,CSocket内幕及其用法 jmcooler(原作)
关键字SocketCAsyncSocketCSocketCSocketFile同步(阻塞)异步(非阻塞)
Socket有同步阻塞方式和异步非阻塞方式两种使用,事实上同步和异步在我们编程的生涯中可能遇到了很多,而Socket也没什么特别。虽然同步好用,不费劲,但不能满足一些应用场合,其效率也很低。 也许初涉编程的人不能理解“同步(或阻塞)”和“异步(或非阻塞)”,其实简单两句话就能讲清楚,同步和异步往往都是针对一个函数来说的,“同步”就是函数直到其要执行的功能全部完成时才返回,而“异步”则是,函数仅仅做一些简单的工作,然后马上返回,而它所要实现的功能留给别的线程或者函数去完成。例如,SendMessage就是“同步”函数,它不但发送消息到消息队列,还需要等待消息被执行完才返回;相反PostMessage就是个异步函数,它只管发送一个消息,而不管这个消息是否被处理,就马上返回。
一、SocketAPI 首先应该知道,有Socket1.1提供的原始API函数,和Socket2.0提供的一组扩展函数,两套函数。这两套函数有重复,但是2.0提供的函数功能更强大,函数数量也更多。这两套函数可以灵活混用,分别包含在头文件Winsock.h,Winsock2.h,分别需要引入库wsock32.lib、Ws2_32.lib。
1、默认用作同步阻塞方式,那就是当你从不调用WSAIoctl()和ioctlsocket()来改变SocketIO模式,也从不调用WSAAsyncSelect()和WSAEventSelect()来选择需要处理的Socket事件。正是由于函数accept(),WSAAccept(),connect(),WSAConnect(),send(),WSASend(),recv(),WSARecv()等函数被用作阻塞方式,所以可能你需要放在专门的线程里,这样以不影响主程序的运行和主窗口的刷新。 2、如果作为异步用,那么程序主要就是要处理事件。它有两种处理事件的办法: 第一种,它常关联一个窗口,也就是异步Socket的事件将作为消息发往该窗口,这是由WinSock扩展规范里的一个函数WSAAsyncSelect()来实现和窗口关联。最终你只需要处理窗口消息,来收发数据。 第二种,用到了扩展规范里另一个关于事件的函数WSAEventSelect(),它是用事件对象的方式来处理Socket事件,也就是,你必须首先用WSACreateEvent()来创建一个事件对象,然后调用WSAEventSelect()来使得Socket的事件和这个事件对象关联。最终你将要在一个线程里用WSAWaitForMultipleEvents()来等待这个事件对象被触发。这个过程也稍显复杂。 二、CAsyncSocket 看类名就知道,它是一个异步非阻塞Socket封装类,CAsyncSocket::Create()有一个参数指明了你想要处理哪些Socket事件,你关心的事件被指定以后,这个Socket默认就被用作了异步方式。那么CAsyncSocket内部到底是如何将事件交给你的呢? CAsyncSocket的Create()函数,除了创建了一个SOCKET以外,还创建了个CSocketWnd窗口对象,并使用WSAAsyncSelect()将这个SOCKET与该窗口对象关联,以让该窗口对象处理来自Socket的事件(消息),然而CSocketWnd收到Socket事件之后,只是简单地回调CAsyncSocket::OnReceive(),CAsyncSocket::OnSend(),CAsyncSocket::OnAccept(),CAsyncSocket::OnConnect()等虚函数。所以CAsyncSocket的派生类,只需要在这些虚函数里添加发送和接收的代码。
简化后,大致的代码为: boolCAsyncSocket::Create(longlEvent)file://参/数lEvent是指定你所关心的Socket事件 { m_hSocket=socket(PF_INET,SOCK_STREAM,0);file://创/建Socket本身
CSocketWnd*pSockWnd=newCSocketWnd;file://创/建响应事件的窗口,实际的这个窗口在AfxSockInit()调用时就被创建了。 pSockWnd->Create(...);
WSAAsyncSelect(m_hSocket,pSockWnd->m_hWnd,WM_SOCKET_NOTIFY,lEvent);file://Socket/事件和窗口关联 }
staticvoidPASCALCAsyncSocket::DoCallBack(WPARAMwParam,LPARAMlParam) { CAsyncSocketSocket; Socket.Attach((SOCKET)wParam);file://wParam/就是触发这个事件的Socket的句柄 intnErrorCode=WSAGETSELECTERROR(lParam);file://lParam/是错误码与事件码的合成 switch(WSAGETSELECTEVENT(lParam)) { caseFD_READ: pSocket->OnReceive(nErrorCode); break; caseFD_WRITE: pSocket->OnSend(nErrorCode); break; caseFD_OOB: pSocket->OnOutOfBandData(nErrorCode); break; caseFD_ACCEPT: pSocket->OnAccept(nErrorCode); break; caseFD_CONNECT: pSocket->OnConnect(nErrorCode); break; caseFD_CLOSE: pSocket->OnClose(nErrorCode); break; } }
CSocketWnd类大致为:
BEGIN_MESSAGE_MAP(CSocketWnd,CWnd) ON_MESSAGE(WM_SOCKET_NOTIFY,OnSocketNotify) END_MESSAGE_MAP()
LRESULTCSocketWnd::OnSocketNotify(WPARAMwParam,LPARAMlParam) { CAsyncSocket::DoCallBack(wParam,lParam);file://收/到Socket事件消息,回调CAsyncSocket的DoCallBack()函数 return0L; }
然而,最不容易被初学Socket编程的人理解的,也是本文最要提醒的一点是,客户方在使用CAsyncSocket::Connect()时,往往返回一个WSAEWOULDBLOCK的错误(其它的某些函数调用也如此),实际上这不应该算作一个错误,它是Socket提醒我们,由于你使用了非阻塞Socket方式,所以(连接)操作需要时间,不能瞬间建立。既然如此,我们可以等待呀,等它连接成功为止,于是许多程序员就在调用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或者CAsyncSocket::GetLastError()查看Socket返回的错误,直到返回成功为止。这是一种错误的做法,断言,你不能达到预期目的。事实上,我们可以在Connect()调用之后等待CAsyncSocket::OnConnect()事件被触发,CAsyncSocket::OnConnect()是要表明Socket要么连接成功了,要么连接彻底失败了。至此,我们在CAsyncSocket::OnConnect()被调用之后就知道是否Socket连接成功了,还是失败了。 类似的,Send()如果返回WSAEWOULDBLOCK错误,我们在OnSend()处等待,Receive()如果返回WSAEWOULDBLOCK错误,我们在OnReceive()处等待,以此类推。 还有一点,也许是个难点,那就是在客户方调用Connect()连接服务方,那么服务方如何Accept(),以建立连接的问题。简单的做法就是在监听的Socket收到OnAccept()时,用一个新的CAsyncSocket对象去建立连接,例如:
voidCMySocket::OnAccept(intErrCode) { CMySocket*pSocket=newCMySocket; Accept(*pSocket); } 于是,上面的pSocket和客户方建立了连接,以后的通信就是这个pSocket对象去和客户方进行,而监听的Socket仍然继续在监听,一旦又有一个客户方要连接服务方,则上面的OnAccept()又会被调用一次。当然pSocket是和客户方通信的服务方,它不会触发OnAccept()事件,因为它不是监听Socket。
三、CSocket CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类。它是如何又把CAsyncSocket变成同步的,而且还能响应同样的Socket事件呢? 其实很简单,CSocket在Connect()返回WSAEWOULDBLOCK错误时,不是在OnConnect(),OnReceive()这些事件终端函数里去等待。你先必须明白Socket事件是如何到达这些事件函数里的。这些事件处理函数是靠CSocketWnd窗口对象回调的,而窗口对象收到来自Socket的事件,又是靠线程消息队列分发过来的。总之,Socket事件首先是作为一个消息发给CSocketWnd窗口对象,这个消息肯定需要经过线程消息队列的分发,最终CSocketWnd窗口对象收到这些消息就调用相应的回调函数(OnConnect()等)。 所以,CSocket在调用Connect()之后,如果返回一个WSAEWOULDBLOCK错误时,它马上进入一个消息循环,就是从当前线程的消息队列里取关心的消息,如果取到了WM_PAINT消息,则刷新窗口,如果取到的是Socket发来的消息,则根据Socket是否有操作错误码,调用相应的回调函数(OnConnect()等)。 大致的简化代码为:
BOOLCSocket::Connect(...) { if(!CAsyncSocket::Connect(...)) { if(WSAGetLastError()==WSAEWOULDBLOCK)file://由/于异步操作需要时间,不能立即完成,所以Socket返回这个错误 { file://进/入消息循环,以从线程消息队列里查看FD_CONNECT消息,直到收到FD_CONNECT消息,认为连接成功。 while(PumpMessages(FD_CONNECT)); } } } BOOLCSocket::PumpMessages(UINTuEvent) { CWinThread*pThread=AfxGetThread(); while(bBlocking)file://bBlocking/仅仅是一个标志,看用户是否取消对Connect()的调用 { MSGmsg; if(PeekMessage(&msg,WM_SOCKET_NOTIFY)) { if(msg.message==WM_SOCKET_NOTIFY&&WSAGETSELECTEVENT(msg.lParam)==uStopFlag) { CAsyncSocket::DoCallBack(msg.wParam,msg.lParam); returnTRUE; } } else { OnMessagePending();file://处/理消息队列里的其它消息 pThread->OnIdle(-1); } } } BOOLCSocket::OnMessagePending() { MSGmsg; if(PeekMessage(&msg,NULL,WM_PAINT,WM_PAINT,PM_REMOVE)) {file://这/里仅关心WM_PAINT消息,以处理阻塞期间的主窗口重画 ::DispatchMessage(&msg); returnFALSE; } returnFALSE; }
其它的CSocket函数,诸如Send(),Receive(),Accept()都在收到WSAEWOULDBLOCK错误时,进入PumpMessages()消息循环,这样一个原本异步的CAsyncSocket,到了派生类CSocket,就变成同步的了。 明白之后,我们可以对CSocket应用自如了。比如有些程序员将CSocket的操作放入一个线程,以实现多线程的异步Socket(通常,同步+多线程相似于异步)。
四、CSocketFile 另外,进行Socket编程,不能不提到CSocketFile类,其实它并不是用来在Socket双方发送文件的,而是将需要序列化的数据,比如一些结构体数据,传给对方,这样,程序的CDocument()的序列化函数就完全可以和CSocketFile联系起来。例如你有一个CMyDocument实现了Serialize(),你可以这样来将你的文档数据传给Socket的另一方:
CSocketFilefile(pSocket); CArchivear(&file,CArchive::store); pDocument->Serialize(ar); ar.Close();
同样,接收一方可以只改变上面的代码为CArchivear(&file,CArchive::load);即可。 注意到,CSocketFile类虽然从CFile派生,但它屏蔽掉了CFile::Open()等函数,而函数里仅扔出一个例外。那么也就是说,你不能调用CSocketFile的Open函数来打开一个实实在在的文件,否则会导致例外,如果你需要利用CSocketFile来传送文件,你必须提供CSocketFile类的这些函数的实现。 再一点,CArchive不支持在datagram的Socket连接上序列化数据。
|
|
郑重声明 近年来我站数百篇“原创文章”被一些媒体肆意转载,不但不标明出自“中国电子技术信息网”,而且把相关字眼删除!这是无视技术价值的行为,是对技术人员辛勤劳动的蔑视,我对此种行为表示强烈的谴责。 “本是同根生,相煎何太急”,考虑到我们做技术的都是同门兄弟,对以前的行为不再追究,今后再有此类事件,最低处理限度是“明示于天下”。在这里向转载过又标注了出处的兄弟媒体表示崇高的敬意! 兄弟网站如果引用本站“原创文章”,请首先经本人许可,并标注“www.cetinet.com”或“中国电子技术信息网”字样。 欢迎个人下载使用! |
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=67191
分享到:
相关推荐
SocketAPI,CAsyncSocket,CSocket内幕及其用法 MFC下CSocket编程详解 异步套接字基础 CAsyncSocket及CSocket MFC疑难注解 利用MFC的Csocket类实现网络通信 CASyncSocket类和CSocket类编程
MFC下的socket使用,及其技术内幕
Socket,CAsyncSocket,CSocket介绍对于编程人员很有用处,也是一种总结
CAsyncSocket,CSocket的使用方法
介绍MFC中CAsyncSocket与CSocket的区别,并举例说明
使用CAsyncSocket和CSocket的一种通信模型,模型通信稳定,可以挪为己用。
MFC疑难注解:CAsyncSocket及CSocket
csocket和casyncsocket的区别
基于c++的聊天室程序,支持多人聊天,给出了分别用CAsyncSocket,Csocket和Winsock实现的3个版本,是学习socket编程的绝好例子,经测试保证可以运行。
Csocket demo 跟CasyncSocket demo 。可以利用这两个demo重新改写符合 业务需求
包含四个程序代码,CAsyncSocket通信方式和 Socket程序(CSocket版)通信方式,还有一个聊天室的程序
CSocket编程介绍文档CSocket类是CAsyncSocket类的派生类,它继承了Windows Socket API封装函数。它实现了比CAsyncSocket类对Windows Sockets更高层的抽象。它与CSocketFile类和CArchive类合作完成对数据的发送、接收...
本文主要讲解以下内容 (1)CAsyncSocket类... 使用这个类进行网络编程,就要求程序员对网络通信和Socket编程模式有相当的了解,因为程序员 要自己编写程序处理阻塞、字节顺序以及Unicode与MBCS之间的转换等问题。
网络概念通讯入门及c++网络入门,MFC CAsyncSocket介绍
利用CAsyncSocket类实现Socket编程的一个带讲解的实例
非常详细,有条理,而且对socket csocket CAsyncSocket 等的概念,原理分析得非常好,看了你就知道。 1.套接字编程原理 1.1 Client/server通信模型 1.2 Windows Sockets规范 1.3 套接字 1.3.1 套接字定义 ...
其实单独使用 CSocket,与使用 CAsyncSocket 的方法是类似的,最大的不同之处在于 Connect() 函数的用法。CSocket 是同步的,Connect() 的返回值表示是否连接成功,CAsyncSocket.Connect() 通常返回 WSAEWOULDBLOCK ...
Socket,CAsyncSocket,CSocket的基本介绍,有Socket编程中select()的妙用,MFC疑难注解:CAsyncSocket及CSocket
课本程序VC++ 基于CAsyncSocket实现的Socket点对点通信小程序,完美调试,不存在一点错误。
VC++6.0中利用MFC的CSocket类实现网络通信