2.1MFC及其Socket类2.2C/S模式下网络程序的Socket通信实例2.3与第三方程序的互通及Socket编程的本质2.1.1MFC简介MFC,微软基础类(MicrosoftFoundationClasses),同VCL类似,是一种应用框架(ApplicationFramework),随微软VisualC++开发工具发布。MFC是很庞大的,如最新版本中包含了大约两百多个不同的类(如图2.1所示)。1.CAsyncSocket类CAsyncSocket类是从MFC的根类CObject派生出来的,它在较低的级别上封装WindowsSocketAPI。CAsyncSocket类在MFC套接字类中的继承位置如图2.2所示。CAsyncSocket类的成员函数如表2.1所示。2.CSocket类及其相关类(1)CSocket类。CSocket类是从CAsyncSocket派生而来的,它继承了CAsyncSocket对WindowsSocketAPI的封装。(2)CSocket与CArchive、CSocketFile类的配合使用。用CSocket类编写网络程序,既可以使用如CAsyncSocket类网络程序一样的Send和Receive函数来收发信息,也可以与CSocketFile类和CArchive类一起来管理对数据的发送和接收。2.2.1客户—服务器方式(C/S模式)1.网络软件的通用体系结构客户(Client)和服务器(Server)是指通信中所涉及的两个应用进程。客户—服务器方式所描述的是进程之间服务和被服务的关系。在图2.3中,主机A运行客户程序而主机B运行服务器程序。2.最简单的Socket通信流程一个只有客户方向服务方发信息的单向通信,并且也只有客户方会主动提出断开连接的最简单的情形(相反过程的原理是一样的),其双方Socket之间的关系如图2.4所示。由上面这个十分简单的过程很容易得出最简单的Socket通信流程,如图2.5所示。1.对象分析由2.2.1节分析的最简单的Socket通信流程可见,要实现这样一个完整流程需要三个套接字对象:客户端一个(我们称为“客户Socket”),服务器端两个(一个用于监听,称为“监听Socket”;另一个用于接收客户发来的信息,称为“服务Socket”),这三个套接字对象对应三个Socket类,都继承自CAsyncSocket,分别给它们取名如表2.2所示。Socket对象类名客户SocketCClientSocket监听SocketCListenSocket服务SocketCServerSocket2.创建工程和套接字对象首先创建客户端工程。打开VisualStudio2008环境,建立一个新的基于对话框的MFC项目,项目名称为ChatClient,接着一直单击“下一步”按钮,直到设置程序“高级功能”对话框,如图2.6所示。创建类名为CClientSocket的客户Socket对象,下面给工程添加类,选择菜单命令“项目”→“添加类”,如图2.7所示。在弹出的“添加类”对话框中选择“MFC类”项,单击“添加”按钮(如图2.8所示)。在“MFC类向导”对话框中输入类名CClientSocket,向导将自动为这个添加的类生成名为“ClientSocket.h”和“ClientSocket.cpp”的头文件和源文件,如图2.9所示。单击“完成”按钮,可以在类视图中看到刚刚添加的类CClientSocket(如图2.10所示)。3.理清程序文件的组织结构(1)客户端程序文件。打开已经创建好的客户端工程,在解决方案资源管理器中可以看到工程的所有程序文件(如图2.11所示)。全部程序文件可以分成三类:第一类是.h后缀的头文件,用来集中声明定义程序中用到的类、变量、函数、宏等;第二类是.cpp后缀的源文件,这是程序代码的主体,集中存放程序的源代码;第三类是资源文件,存放程序中用到的资源,如图标、图像、音频、视频等,(2)服务器端的源文件。同理,服务器端也对应这几种程序文件,打开服务器工程,可以看到它们(如图2.12所示)。4.用头文件和类对象将程序源文件联成有机整体例如,已经创建了各个Socket对象并且也有了它们各自对应的程序文件,但这些文件仍然是孤立的,相互之间的代码无法访问,要使它们能联系在一起,使控制程序的主文件能够自如地操纵Socket,就必须通过头文件声明和创建类对象成员变量将各个类联成有机的整体。要使客户端程序能够创建和控制本地的Socket,在客户端工程界面控制模块的头文件ChatClientDlg.h中添加如下两行代码:#includeClientSocket.h//使主界面程序能够访问Socket类的代码文件CClientSocketm_ClientSocket;//为了后面与服务器通信而定义的Socket成员变量以上两行代码的添加位置如图2.13所示中“//ADD”记号之间标出的部分。反过来要使Socket能够访问到主界面上的控件,以便能够将自己的状况随时反映给主程序并在主界面上显示出来,也需要在Socket源文件ClientSocket.cpp中添加头文件声明:#includeChatClientDlg.h同理,要使服务端程序能够创建和控制本地的Socket,也要在服务端工程界面控制模块的头文件ChatServerDlg.h中添加如下代码:#includeListenSocket.h//使主界面程序能够访问监听Socket类的代码文件#includeServerSocket.h//使主界面程序能够访问服务Socket类的代码文件CServerSocketm_ServerSocket;//为了后面与客户通信而定义的Socket成员变量CListenSocketm_ListenSocket;//为了监听客户端的连接请求而定义的Socket成员变量以上四行代码的添加位置如图2.14所示中“//ADD”记号之间标出的部分。5.布局简洁的界面在客户端“资源视图”展开的目录树下双击Dialog文件夹下第二个项目,转到用户界面设计工作区(如图2.15所示)。完成的客户端简化界面(如图2.16所示)上,包括IP地址控件、列表框各1个,文本框两个,四个按钮。用相同的方法设置服务器端的用户界面如图2.17所示,也一样为IP地址控件关联变量ServerIP,为文本框控件关联int型变量sPort,为列表框关联变量m_ListWords。6.雏形程序试运行到此为止,这个程序的雏形已经形成,分别运行客户和服务器端的工程如图2.18所示。7.程序代码的组织接下来就要为这个程序雏形注入灵性,添加核心源代码了。在此之前,先来回顾一下前面设计的那个最简单的Socket通信流程,如图2.19所示。从上面已经标注了所用函数的通信流程图,可以进一步得出下面的源程序代码组织框图(如图2.20所示)。8.源代码完全剖析(1)客户端源码。客户端用户首先主动发起连接,以下是“连接”按钮的事件过程,位于ChatClientDlg.cpp文件中://连接服务器BYTEnFild[4];CStringsIP;UpdateData();ServerIP.GetAddress(nFild[0],nFild[1],nFild[2],nFild[3]);sIP.Format(%d.%d.%d.%d,nFild[0],nFild[1],nFild[2],nFild[3]);m_ClientSocket.Create();//创建客户端Socketm_ClientSocket.Connect(sIP,sPort);//发起连接请求Create()函数用来创建和初始化套接字,具体过程为:构造套接字对象m_ClientSocket后,调用Create()成员函数创建Socket句柄,并调用Bind()成员函数将其与指定的地址绑定,Create()函数原型为:BOOLCreate(UINTnSocketport=0,intnSocketType=SOCK_STREAM,longlEvent=FD_READ︱FD_WRITE︱FD_OOB︱FD_ACCEPT︱FD_CONNECT︱FD_CLOSE,LPCTSTRlpszSocketaddress=NULL);Connect()函数用于未连接的数据流或者数据报套接字建立连接。其函数原型为:BOOLConnect(LPCTSTRlpszHostAddress,UINTnHostPort);BOOLConnect(constSOCKADDR*lpSockAddr,intnSockAddrLen);客户端也可以随时主动断开通信连接,下面是“断开”按钮的事件过程://断开与服务器的连接m_ClientSocket.Close();//关闭客户端Socketm_ListWords.AddString(从服务器断开);Close()函数用来关闭套接字并释放Socket描述符,其函数原型为:virtualvoidClose();客户端可以向服务器发送信息,“发送”按钮的事件过程为://向服务器发信息UpdateData();m_ClientSocket.Send(m_sWords,m_sWords.GetLength());//发信息m_ListWords.AddString(发送:+m_sWords);m_ListWords.SetTopIndex(m_ListWords.GetCount()-1);Send()函数通过数据报或者数据流向对方套接字发送数据,其函数原型为:virtualintSend(constvoid*lpBuf,intnBufLen,intnFlags=0);在类视图中选中CClientSocket,在界面右下角的属性窗口中单击“重写”按钮,就可以为该Socket类编写被动响应网络事件的函数(如图2.21所示)。选择OnConnect()函数,为其添加代码,系统将该函数的代码自动置于ClientSocket.cpp文件中。OnConnect()函数代码如下://确认客户端是否成功连接到服务器if(nErrorCode){AfxMessageBox(连接失败,请您重试!);return;}((CChatClientDlg*)(AfxGetApp()-m_pMainWnd))-m_ListWords.AddString(连接服务器成功!);((CChatClientDlg*)(AfxGetApp()-m_pMainWnd))-m_ListWords.SetTopIndex(((CChatClientDlg*)(AfxGetApp()-m_pMainWnd))-m_ListWords.GetCount()-1);(2)服务器端源码(ChatServerDlg.cpp文件中)。“开始监听”按钮的事件过程如下://监听开始,服务器等待连接请求的到来BYTEnFild[4];CStringsIP,sP;UpdateData();ServerIP.GetAddress(nFild[0],nFild[1],nFild[2],nFild[3]);sIP.Format(%d.%d.%d.%d,nFild[0],nFild[1],nFild[2],nFild[3]);sP.Format(%d,sPort);m_ListenSocket.Create(sPort,1,FD_ACCEPT,sIP);//创建服务端监听Socketm_ListenSocket.Listen(1);//开始监听m_ListWords.AddString(监听开始:);m_ListWords.AddString(地址+sIP+端口+sP);m_ListWords.AddString(等待客户端连接……);Listen()函数用于侦听连接请求,原型为:BOOLListen(intnConnectionBackloh=5);Accept()函数接受一个套接字的连接请求,从连接请求队列中取出第一个连接,