Windows平台下的网络异步通讯编程技术
扫描二维码
随时随地手机看文章
摘要 介绍了在TCP/IP网络中WinSock网络编程的基本流程及WinSock编程常用的两种类,集中探讨了MFC提供的异步非阻塞类CAsyncSocket的特点,包括类对象的创建、异步选择机制以及对网络事件的响应。以及采用CAsyncSocket类进行网络通信的通信流程,并结合实际开发经验,介绍了使用CAsyncSocket类进行网络编程的基本框架。通过使用可大大提高编程的效率。
关键词 TCP/IP;WinSock;异步通讯;非阻塞;CasyncSocket
随着Internet技术的应用和普及,多数应用程序都是运行在网络环境下,这就要求程序员能在应用最广泛的Windows操作系统上开发网络应用程序。文中介绍了WinSock编程的基本流程,并利用MFC提供的CAsyncSocket类,结合在VS2008环境下实际的开发经验,介绍了Windows平台下基于TCP的异步网络编程的相关知识。
1 WinSock编程的基本流程
在TCP/IP网络中,两个进程间相互作用的主要模式是客户机/服务器模式,该模式的建立基于以下两点:(1)非对等作用。(2)通信完全是异步的。客户机/服务器模式在操作过程中采取的是主动请示方式。面向连接(TCP)的典型过程如图1所示。
2 CAsyncSocket类的简单介绍
微软公司开发的Visual C++是Windows平台下强有力的开发工具。VC++对网络编程的支持有socket支持,WinInet支持,MAPI和ISAPl支持等,其中Windows Sockets API是TCP/IP网络环境下开发最为通用的API。为简化WinSock网络编程,使用户专注于应用程序的算法设计,Microsoft的基本类库(Microsoft Foundation Class,MFC)提供了两个用于Winsock编程的类,分别是CAsyncSocket类和CSocket类:这两个类在不同程度上对WinSock API函数进行了封装,具有直接调用Sockets API的灵活性。CAsyncSocket类是从CObject类派生出来的,在很低的级别上一对一封装了Windows Sockets API,因此具有直接调用Socket API的灵活性,可以使用面向对象的方式进行Socket编程,CAsync Soc ket类可以方便地调用其他MFC对象,处理多个网络协议。与CSocket类相比,CAsyncSocket类有以下特点。
2.1 CAsyncSocket类对象的创建
CAsyncSocket是一个异步非阻塞Socket封装类,CAsvncSocket的Create()函数,除创建了一个Socket以外,CAsyncSocket::Create()的参数IEvent指明了想要处理的Socket事件,关心的事件被指定以后,这个Socket默认就被用作了异步方式。CAsyncSocket还创建了个CSoc ketWnd窗口对象,并使用WSAAsyncSelect()将这个SOCKET与该窗口对象关联,以使该窗口对象处理来自Socket的事件(消息),然而CSocket Wnd收到Socket事件之后,只是简单地回调CAsyncSocket::OnReceive()等虚函数。所以CAsyncSocket的派生类,只需在这些虚函数里添加发送和接收的代码,除此外Create()函数还调用Bind()函数将Socket对象与指定的地址绑定。其函数原型为:
BOOL CAsyncSocket::Create(UINT nSocketPort=0,intnSocketType=SOCK_STREAM,long lEvent=FD_READ|FD_WRITE|FD_OOB|FD_ACC EPT|FD_CONNECT|FD_CLOSE,LPCTSTR lpszSocketAddress=NULL);
在重载函数中都有一个参数nErrorCode,为零则表示正常完成,非零则表示错误。通过int CAsyncSocket::GetLastError()可以得到错误值。参数nSocketPort为使用的端口号,为零则表示由系统自动选择,通常在客户端都使用这个选择。参数nSocketType为使用的协议族,SOCK_STREAM表明使用有连接的服务,SOCK_DGRAM表明使用无连接的数据报服务。参数lpszSocketAddress指定了IP地址,可以使用点分法表示如192.168.0.28,也可以使用默认值,此时函数将默认绑定本机IP地址。
2.2 CAsyncSocket类的异步选择机制
在网络通讯中,由于网络拥挤或数据量大的原因,数据的收发不能立刻完成,收发数据的函数因此不能返回,从而出现阻塞现象。Win Sock对有可能阻塞的函数提供了两种处理方式:阻塞和非阻塞方式。在阻塞方式下,收发数据的函数在被调用后一直要到传送完毕或者出错才能返回。对于非阻塞方式,函数被调用后立即返回,传送完成后由WinSock给程序发一个事先约定好的消息。使用Windows Sockets实现Windows网络程序设计的关键就是它提供了对网络事件基于消息的异步存取,用于注册应用程序感兴趣的网络事件。Winsock过WSAAsyncse lect()动地设置套接字处于非阻塞方式,注册一个或多个网络事件。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。WSAAsyncSelect的原型如下:
int PASCAL FAR WSAAsyncSelect(SOCTET s,HWND hWnd,unsignedint wMsg,long lEvent)它请求Windows Sockets DLL在检测到套接字上发生的网络事件时,向窗口hWnd发送一个消息。MFC在实现CAsyncSocket类时,定义了一个内部类CSocket Wnd,当使用Create函数产生Socket句柄时,就Attach这个Socket到一个窗口上,并且CAsyncSocket的DoCallBack函数为该窗口的回调函数。在此函数内根据不同的消息参数,响应各个网络事件。
2.3 CAsyncSocket对网络事件的响应
在理解以上机制后,再了解一下CAsyncSocket的通信流程。
CAsvncSocket在AsyncSelect函数中调用WSAAsyncselect函数注册感兴趣的网络事件。这样,当一个网络事件发生时,经过MFC的消息循环,就可以由CAsyncSocket的DoCAllBack函数按事件的类型:FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT和FD_CLOSE来分别调用OnReceive(),OnSend(),OnAccept(),OnConnect()和OnClose()函数。具体的对应关系如表1所示。
3 使用CAsyncSocket类的通讯流程
在理解了上述的机制后,CAsyncSocket的通信流程:客户方在使用CAsyncSocket::Connect()时,往往返回一个WSAEWOULDBLOCK的错误,实际上这不应该算作一个错误,它是Socket的提醒,由于使用了非阻塞Socket方式,所以操作需要时间,不能瞬间建立。那么可以等待,等待连接成功,于是许多程序员就在调用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或者CAsyncSocket::GetLast Error()查看Socket返回的错误,直到返回成功为止。这是一种错误的做法,断言不能达到预期目的。事实上,可以在Connect()调用之后等待CAsyncSocket::OnConnect()事件被触发。类似地,Send()如果返回WSAEWOULDBLOCK错误,在OnSend()处等待,Receive()如果返回WSAE WOULDBLOCK错误,则在OnReceive()处等待,具体的内部通信流程如图2所示。
4 使用CAsyncSocket编程的程序框架
在进行C/S编程之前,需在定义应用程序行为的文件030 303.cpp中的Initlnstance()函数里调用AfxSocketInit()函数来初始化Wind ows Sockets。
(1)服务器端
以public的方式从CAsyncSocket类派生新类CServerSock,并重载OnAccept、OnReceive、OnSend函数。
函数重载完成后,在主窗口构造新的CServeSock对象,用来监听来自客户机的连接,添加代码如下:
CServeSock m_ListenSock;//m_ListenSock为监听套接字
m_ListenSock.Create(m_Port,SOCK_STREAM,FD ACCEPT|FD_READ|FD_WRITE|FD_CLOSE)
m_ListenSock→Listen(int nConnectionBacklog=5);
函数Send()的参数说明:
nconnectionBacklog:等待连接的最大队列长度。
此时服务器开始监听来自客户机的连接请求。
(2)客户机端
以public的方式从CAsyncSocket类派生新类CClientSock,与服务器端类似,重载OnReceive()、OnSend()函数。
已经搭建好使用CAsyncSocket类实现基于TCP协议的异步网络通讯的框架,具体的应用程序可以在此基础上进行丰富与修改。
5 结束语
CAsyncSocket类为使用Socket提供了方便。建立Socket的WSAStartup过程和bind过程被简化成为Create过程,IP地址类型转换、主机名和IP地址转换的过程中许多复杂的变量类型都被简化成字符串和整数操作,特别是CAsyncSocket类的异步特点,完全可以替代繁琐的线程操作。MFC提供了大量的类库,若能灵活地使用,可大大提高编程效率。