多人聊天室的构建——基于CAsyncSocket类的WindowsSockets编程[提要]本章介绍了Socket的工作机制和基于CAsyncSocket类的Sockets编程的基本方法。通过一个应用实例,编写服务端和客户端代码,实现多人之间信息传递。一、TCP/IP体系结构与特点1、TCP/IP体系结构TCP/IP协议实际上就是在物理网上的一组完整的网络协议。其中TCP是提供传输层服务,而IP则是提供网络层服务。TCP/IP包括以下协议:(结构如图1.1)(图1.1)IP:网间协议(InternetProtocol),负责主机间数据的路由和网络上数据的存储。同时为ICMP,TCP,UDP提供分组发送服务。用户进程通常不需要涉及这一层。ARP:地址解析协议(AddressResolutionProtocol),此协议将网络地址映射到硬件地址。RARP:反向地址解析协议(ReverseAddressResolutionProtocol),此协议将硬件地址映射到网络地址ICMP:网间报文控制协议(InternetControlMessageProtocol),此协议处理信关和主机的差错和传送控制。TCP:传送控制协议(TransmissionControlProtocol),这是一种提供给用户进程的可靠的全双工字节流面向连接的协议。它要为用户进程提供虚电路服务,并为数据可靠传输建立检查。(注:大多数网络用户程序使用TCP)UDP:用户数据报协议(UserDatagramProtocol),这是提供给用户进程的无连接协议,用于传送数据而不执行正确性检查。FTP:文件传输协议(FileTransferProtocol),允许用户以文件操作的方式(文件的增、删、改、查、传送等)与另一主机相互通信。SMTP:简单邮件传送协议(SimpleMailTransferProtocol),SMTP协议为系统之间传送电子邮件。TELNET:终端协议(TelnetTerminalProcotol),允许用户以虚终端方式访问远程主机。HTTP:超文本传输协议(HypertextTransferProcotol)TFTP:简单文件传输协议(TrivialFileTransferProtocol)2、TCP/IP特点TCP/IP协议的核心部分是传输层协议(TCP、UDP),网络层协议(IP)和物理接口层,这三层通常是在操作系统内核中实现。因此用户一般不涉及。编程时,编程界面有两种形式:一、是由内核心直接提供的系统调用;二、使用以库函数方式提供的各种函数。前者为核内实现,后者为核外实现。用户服务要通过核外的应用程序才能实现,所以要使用套接字(socket)来实现。图1.2是TCP/IP协议核心与应用程序关系图。二、套接字工作方式1、客户机/服务器模式在TCP/IP网络中两个进程间相互作用的主机模式是客户机/服务器模式(Client/Servermodel)。该模式的建立基于以下两点:1、非对等作用;2、通信完全是异步的。客户机/服务器模式在操作过程中采取的是主动请示方式:首先服务器方要先启动,并根据请示提供相应服务:(过程如下)①打开一通信通道(端口)并告知本地主机,并在某一个公认地址上接收客户请求;②等待客户请求到达该端口;③接收到重复服务请求,处理该请求并发送应答信号;④返回第二步,等待另一客户请求⑤关闭服务器。客户方:①打开一通信通道,并连接到服务器所在主机的特定端口。②向服务器发送服务请求报文,等待并接收应答;继续提出请求……③请求结束后关闭通信通道并终止。2、典型过程图⑴面向连接的套接字的系统调用时序图(图1.3)⑵无连接协议的套接字调用时序图(图1.4)三、Windows套接类——CAsyncSocket类1、CAsyncSocket类介绍CAsyncSocket类封装WindowsSocketsAPI。CAsyncSocket适合那些对网络通信细节了解,希望利用回调的便利通知网络事件的程序员使用。如果想利用WindowsSockets方便地处理MFC应用程序中的多个网络协议,而又不想放弃灵活性,可以考虑使用CasyncSocket类。其在MFC中的类层次为:Cobject└CasyncSocket2、CAsyncSocket类的主要成员函数构造函数CasyncSocket:构造CAsyncSocket对象Create:创建套接字属性操作函数:Attach:对CAsyncSocket对象附加套接字句柄Detach:从CAsyncSocket对象除去套接字句柄FromHandle:返回CAsyncSocket对象的指针,给出套接字句柄GetLastError:获得上一次运行失败的状态GetPeerName:获得与套接字连接的对等套接字的地址GetSockName:获得套接字的本地名GetSockOpt:获得套接字选项SetSockOpt:设置套接字选项成员函数:Accept:接受套接字上的连接AsyncSelect:请求对于套接字的事件通知Bind:与套接字有关的本地地址Close:关闭套接字Connect:对对等套接字建立连接IOCtl:控制套接字模式Listen建立套接字,侦听即将到来的连接请求Receive从套接字接收数据ReceiveFrom恢复数据报并且存储资源地址Send给连接套接字发送数据SendTo给特定目的地发送数据ShutDown使套接字上的Send和/或Receive调用无效虚函数:OnAccept:通知侦听套接字,它可以通过调用Accept,接受挂起连接请求OnClose:通知套接字,关闭对它的套接字连接OnConnect:通知连接套接字,连接尝试已经完成,无论成功或失败OnOutOfBandData:通知接收套接字,在套接字上有带外数据读入,通常是忙消息OnReceive:通知侦听套接字,通过调用Receive恢复数据OnSend:通知套接字,通过调用Send,它可以发送数据数据成员m_hSocket:指定附加在此CAsyncSocket对象上的SOCKET句柄3、使用CasyncSocketTCP/IP的Winsock编程有两种模式:阻塞及非阻塞。VisualC++通过MFC类CAsyncSocket提供对后者的支持。为了把问题描述清楚,先简要介绍一下流方式下用CAsyncSocket编写TCP程序的步骤:(1)客户端:①从CAsyncSocket派生自己的类并构造对象;②调用成员函数Create创建SOCKET;③调用成员函数Connect发起连接;④重载虚函数OnConnect,当连接成功时,系统会调用该函数。(2)服务器端:①从CAsyncSocket派生自己的类并构造对象;②调用成员函数Create创建SOCKET;③调用成员函数Listen进行监听;④重载虚函数OnAccept,当有客户端请求连接时,系统调用此函数,用成员函数Accept接受请求并建立连接。调用Accept时,要构造一个新的CAsyncSocket派生类对象作为函数参数,Accept用它创建连接客户端的SOCKET,原来的对象仍然保持监听状态。连接成功后,无论是客户端,还是服务器端,都需要重载虚函数OnSend及OnReceive:当可以发送数据时,系统调用OnSend,这时可以用成员函数Send发送数据;当有数据接收时,系统会调用OnReceive,可以用Receive函数接收数据。需要关闭连接时,任意一方调用成员函数Close即可。如果使用new运算符在堆上创建了套接字对象,则须使用delete运算符销毁此对象。四、多人聊天室的构建(一)类图1、服务端的类图CMySocket-m_pWnd:CDialog-peerAddress:CString-peerPort:UINTcreate-CMySocket()destroy-CMySocket()create-CMySocket(sock:CMySocket)CppOperator+=(sock:CMySocket):CMySocket+SetParent(pWnd:CDialog):void#OnClose(nErrorCode:int):void#OnReceive(nErrorCode:int):void#OnAccept(nErrorCode:int):voidCSocketServerDlg#m_hIcon:HICON+m_strMsg:CString+m_ctlRecvd:CListBox#clientAddr:CString#clientPort:UINT#socketArray:CArrayCMySocket*,CMySocket*create-CSocketServerDlg(pParent:CWnd)#OnInitDialog():BOOL+GetErrorMsg():CString+OnAccept():void+RemoveAt(pCurrentDeleteSocket:CMySocket):void+SwitchMessage(switchMes:CString,pCurrent:CMySocket):void+OnClose():void+OnBnClickedClose():void+OnBnClickedListen():void+OnBnClickedSend():void#m_sConnectSocket:CMySocket*0..*1#m_sListenSocket:CMySocket112、客户端的类图CMySocket-m_pWnd:CDialogcreate-CMySocket()destroy-CMySocket()+SetParent(pWnd:CDialog):void+OnClose(nErrorCode:int):void+OnReceive(nErrorCode:int):voidCServerAddressDlg+m_Addr:CString+m_Port:intcreate-CServerAddressDlg(pParent:CWnd)destroy-CServerAddressDlg()CSocketClientDlg#m_hIcon:HICON+m_strMsg:CString+m_ctlRecvd:CListBox-m_szPort:UINT-m_szServerAdr:CStringcreate-CSocketClientDlg(pParent:CWnd)#OnInitDialog():BOOL+OnBnClickedConnect():void+OnBnClickedCut():void+OnBnClickedSend():void+OnClose():void-GetErrorMsg(:void):CString-IsIPValid(strIP:CString):bool+OnReceive(:void):void#m_sConnectSocket:CMySocket11(二)服务端设计1、利用MFC应用程序向导生成程序框架创建一个DialogBased项目:SocketServer。根据向导创建如下:①应用程序类型选择“基于对话框”;(图1.5)②用户界面功能中添加“最小化框”;(图1.6)③高级功能中添加“Windows套接字”;(图1.7)④默认生成的类,选择完成。(图1.8)⑤打开工程属性页,在“常规”中的“字符集”项中选择“使用多字节字符集”。(图1.9)2、设计对话框界面(1)删除界面上已有的“确定”、“取消”按钮;(2)设计界面如下:(图1.10)设下属性如下:①按钮,ID:IDC_Listen,Caption:********启动服务器********;②按钮,ID:IDC_Close,Caption:********关闭服务器********;③编辑框,ID:IDC_EditSendedMessage,M