第5章 MFC WinSock类的编程 5.1 CAsyncSocket类 5.2 CSocket类 5.3 CSocket类的编程模型
第5章 MFC Winsock类的编程 为简化套接字网络编程,更方便地利用Windows的消息驱动机制,微软的基础类库(Microsoft Foundation Class Library,简称MFC),提供了两个套接字类,在不同的层次上对Windows Socket API函数进行了封装,为编写Windows Socket网络通信程序,提供了两种编程模式。
第5章 MFC Winsock类的编程 CAsyncSocket类,在很低的层次上对Windows Sockets API进行了封装。 它的成员函数和Windows Sockets API的函数调用直接对应。一个CAsyncSocket对象代表了一个Windows套接字。它是网络通信的端点。除了把套接字封装成C++的面向对象的形式供程序员使用以外,这个类唯一所增加的抽象就是将那些与套接字相关的Windows消息变为CAsyncSocket类的回调函数。 如果你对网络通信的细节很熟悉,可以利用Windows Sockets API编程的灵活性。但是你要自己解决阻塞问题、字节顺序问题、字符串转换问题。
第5章 MFC Winsock类的编程 CSocket类,从CAsyncSocket类派生,是对Windows Sockets API的高级封装。CSocket类继承了CAsyncSocket类的许多成员函数,用法一致。CSocket类的高级表现在三个方面: (1)CSocket结合Archive类来使用套接字。 (2)CSocket管理了通信的许多方面,如字节顺序问题和字符串转换问题。 (3)CSocket类为Windows消息的后台处理提供了阻塞的工作模式(Blocking)。
第5章 MFC Winsock类的编程 这两个类提供了事件处理函数,编程者通过对事件处理函数进行重载,可方便地对套接字发送数据、接收数据等事件进行处理。同时,可以结合MFC的其它类来使用这两个套接字类,并利用MFC的各种可视化向导,从而大大简化了编程。 在MFC中,有一个名为afxSock.h的包含文件,C:\Program Files\Microsoft Visual Studio 11.0\VC \atlmfc \include \afxsock.h 。在这个文件中定义了CAsyncSocket,CSocket,和CSocketFile这三个套接字类。 class CAsyncSocket : public Cobject class CSocket : public CAsyncSocket class CSocketFile : public CFile
5.1 CasyncSocket类 P110 CAsyncSocket类从Cobject类派生而来,如下图所示: 使用CAsyncSocket类进行网络编程,可以充分利用Windows的消息驱动机制,通过应用程序框架来传递消息,方便地处理各种网络事件。 作为MFC类库中的一员, CAsyncSocket类可以与其它类融为一体,方便了编程。利用VC++的可视化控件,方便构造用户界面,使程序员能够把精力用在网络编程的算法上。
5.1.1 使用CAsyncSocket类的一般步骤 P110 序 号 服务器(Server) 客户机(Client) 1 //构造一个套接字 CAsyncSocket sockSrvr; CAsyncSocket sockClient; 2 //创建SOCKET句柄,绑定到指定的端口 sockSrvr.Create(nPort); //创建SOCKET句柄,使用默认参数 sockClient.Create( ); 3 //启动监听,时刻准备接受连接请求 sockSrvr.Listen( ); 4 //请求连接到服务器sockClient.Connect(strAddr, nport); 5 //构造一个新的空的套接字CAsyncSocket sockRecv; //接收连接请求sockSrvr.Accept(sockRecv); 6 //接收数据 sockRecv.Receive(pBuf, nLen); //发送数据 sockClient.Send(pBuf, nLen); 7 sockRecv. Send(pBuf, nLen); sockClient.Receive(pBuf, nLen); 8 //关闭套接字对象 sockRecv.Close( ); sockClient.Close( );
5.1.1 使用CAsyncSocket类的一般步骤 P110 (1)客户机与服务器端都要首先构建一个CAsyncSocket对象,然后使用该对象的Create成员函数来创建底层的SOCKET句柄。服务器端一定要绑定到特定的端口。 (2)服务器端的套接字对象应使用CAsyncSocket::Listen成员函数启动监听,当收到来自客户端的连接请求,就调用CAsyncSocket::Accept成员函数来接收,此时建立一个新的空的CAsyncSocket 对象用于数据交换;客户端的套接字对象应使用CAsyncSocket::Connect成员函数连接到服务器的套接字对象。连接建立以后,双方可以进行数据交换。 (3) 调用CAsyncSocket的Send和Receive对象与通信对方交换数据。这些成员函数与Windows Socket API用法基本相同。 (4)关闭并销毁CAsyncSocket对象。
5.1.1 使用CAsyncSocket类的一般步骤 P110 (2)字节顺序的转换。用户程序需要自己对不同的字节进行转换。 (3)字符串转换 。用户程序需要自己解决如Unicode和multibyte set字符串之间的转换。
5.1.2 创建CAsyncSocket类对象 P111 这里将CAsyncSocket类对象称为异步套接字对象。创建异步套接字对象一般分为两个步骤,首先构造一个CAsyncSocket对象,再创建该对象的底层的SOCKET句柄。 1.创建空的异步套接字对象 通过调用CAsyncSocket类的构造函数,创建一个新的空CAsyncSocket类套接字对象,构造函数不带参数。然后必须调用它的Create成员函数,来创建底层的套接字数据结构,并绑定它的地址。
5.1.2 创建CasyncSocket类对象 P112 有以下两种使用方法调用Create成员函数: (1)如: CAsyncSocket aa; aa.Create(……); 这种方式直接定义了CAsyncSocket 的对象,会隐式的调用类的构造函数,创建这个类的对象实例。使用对象调用该类的成员变量时,要用点号( . )操作符。
5.1.2 创建CasyncSocket类对象 P112 (2)如: CAsyncSocket* Pa; Pa = new CAsyncSocket; Pa->Create(……); 这种方式先定义套接字类的指针变量,再显式地调用该类的构造函数,生成类的对象,并将指向该类的指针返回给套接字指针变量。使用这样的对象指针变量调用该类的成员时,要用箭头( -> ) 操作符。
5.1.2 创建CasyncSocket类对象 2.创建异步套接字对象的底层套接字句柄 通过调用CAsyncSocket类的Create( )成员函数,创建该对象的底层套接字句柄,决定套接字对象的具体特性。调用格式为: BOOL Create( UINT nSocketPort=0, Int nSocketType = SOCK_STREAM, Long Ievent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT |FD_CONNECT | FD_CLOSE, LPCTSTR IpszSocketAddress = NULL );
5.1.2 创建CasyncSocket类对象 参数1 nSocketPort,指定一个分配给套接字的端口号,默认值0,表示让系统为其分配一个自由端口号。 参数2 nSocketType ,指定套接字的类型,是用流式套接字SOCK_STREAM,还是用数据报套接字SOCK_DGRAM。 参数3 Ievent,指定为此CAsyncSocket对象生成通知消息的套接字事件。默认对所有套接字事件都生成通知消息。 参数4 IpszSocketAddress,指定套接字的主机域名或IP地址,如果用NULL则使用本机默认地址。
5.1.2 创建CasyncSocket类对象 举例:创建一个使用27端口的流式异步套接字对象。 CAsyncSocket* pSocket = new CAsyncSocket; int nPort = 27; pSocket->Create( nPort, SOCK_STREAM );
5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 1.6种与套接字相关的事件与通知消息 P113 参数Ievent可以选用的六个符号常量是在winsock.h文件中定义的。 #define FD_READ 0x01 #define FD_WRITE 0x02 #define FD_OOB 0x04 #define FD_ACCEPT 0x08 #define FD_CONNECT 0x10 #define FD_CLOSE 0x20
5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 他们代表MFC套接字对象可以接受并处理的六种网络事件,当事件发生时,套接字对象会收到相应的通知消息,并自动执行套接字对象响应的事件处理函数。 (1)FD_READ事件通知:通知有数据可读。 当套接字的输入缓冲区收到了对方发来的数据,发生此事件,并通知套接字对象,可以调用Receive成员函数来接收数据(读),把套接字输入缓冲区的数据送进应用进程缓冲区 。 (2)FD_WRITE事件通知:通知可以写数据。 当套接字的输出缓冲区中的数据发送完毕,缓冲区已经腾空,发生此事件,并通知套接字对象,可以调用Send成员函数发送数据(写),把应用进程缓冲区的数据送进套接字输出缓冲区。
5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 (3)FD_ACCEPT事件通知:通知监听套接字有连接请求可以接受。当客户端的连接请求进入服务器监听套接字的接收缓冲区队列时,发生此事件,并通知套接字对象,可以调用Accept成员函数,来接受待决的连接请求。 (4)FD_CONNECT事件通知:通知请求连接的套接字,连接的要求已被处理。当客户端的连接请求已被处理,发生此事件。存在两种情况,一种是服务器接受了客户端的连接请求,双方的连接已建立,通知客户端套接字,可以传输数据了;另一种是服务器拒绝,通知客户端套接字,连接失败。 (5)FD_CLOSE事件通知:通知套接字已关闭。所连接的套接字关闭时发生此事件。 (6)FD_OOB事件通知:通知将有带外数据到达。当对方的流式套接字发送带外数据时,发送此事件。
5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 2.MFC框架对于六个网络事件的处理 当上述的网络事件发生时,MFC框架作何处理呢?按照Windows的消息驱动机制,MFC框架应当把消息发送给相应的套接字对象,并调用作为该对象成员函数的事件处理函数。事件与处理函数是一一映射的。
5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 在afxSock.h文件中的CAsyncSocket类的声明中,定义了与这六个网络事件对应的事件处理函数。 virtual void OnReceive(int nErrorCode);//对应 FD_READ事件 virtual void OnSend(int nErrorCode);//对应 FD_WRITE事件 virtual void OnAccept(int nErrorCode);//对应 FD_ACCEPT事件 virtual void OnConnect(int nErrorCode); //对应 FD_CONNECT事件
5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 virtual void OnClose(int nErrorCode); //对应 FD_CLOSE virtual void OnOutOfBandData(int nErrorCode); //对应 FD_OOB事件 当某个网络事件发生时,MFC框架会自动调用套接字对象的对应的事件处理函数。这就相当于给了套接字对象一个通知,告诉它某个重要的事件已经发生。所以也称之为套接字类的通知函数(notification functions)或回调函数(callback functions)。
5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 3.重载套接字对象的回调函数 P114 回调函数定义的前面都有virtual关键字,表明是可重载的。一般不直接使用CAsyncSocket或Csocket,而是派生出你自己的套接字类,在派生类中对这些虚拟函数重载,并加入你对于网络事件处理的特定代码. 如果你从CAsyncSocket类派生了自己的套接字类,你必须重载你的应用程序所感兴趣的那些网络事件所对应的通知函数。如果从Csocket派生了一个类,是否重载所感兴趣的函数则由你自己决定。 MFC框架自动调用通知函数,使得你可以在套接字被通知的时候来优化套接字的行为。
5.1.4 客户端套接字对象请求连接到服务器端套接字对象 在服务器端套接字对象已经进入监听状态之后,客户端应用程序可以调用CAsyncSocket类的Connect()成员函数,向服务器发出一个连接请求, 如果服务器接受了这个连接请求,两端的连接就建立起来了。 CAsyncSocket::Connect()成员函数有两种重载的调用形式: 格式一:BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort ); 格式二:BOOL Connect( const SOCKADDR* lpSockAddr, int nSockAddrLen );
5.1.4 客户端套接字对象请求连接到服务器端套接字对象 如果调用成功或者发生了WSAEWOULDBLOCK错误,当调用结束返回时,都会发生FD_CONNECT事件,MFC框架会自动调用客户端套接字的OnConnect()事件处理函数,并将错误代码作为参数传送给它。它的原型调用格式如下, virtual void OnConnect( int nErrorCode ); 对于流式套接字,初始化了与服务器的连接,套接字已准备好发送/接收数据;对于数据报套接字,仅仅设置了默认目标,将用于随后的sendto( )或recvfrom( )函数。
5.1.5 服务器接收客户机的连接请求 P115 在服务器端,使用CAsyncSocket流式套接字对象,一般按照以下步骤来接收客户端套接字对象的连接请求。 (1)服务器应用程序必须首先创建一个CAsyncSocket流式套接字对象,并调用它的Create成员函数创建底层套接字句柄。这个套接字对象专门用来监听来自客户机的连接请求,所以称它为监听套接字对象。 (2)调用监听套接字对象的Listen成员函数,使监听套接字对象开始监听来自客户端的连接请求。此函数的调用格式是: BOOL Listen( int nConnectionBacklog = 5); 参数nConnectionBacklog是等待队列最大处理连接请求数。
5.1.5 服务器接收客户机的连接请求 Listen( )函数启动后,监听套接字处于被动状态,如果有客户端连接请求到来,就被确认(acknowledged),并被放进监听套接字的等待队列中,排队待处理。 当Listen函数确认并接纳了一个来自客户端的连接请求后,会触发FD_ACCEPT事件,监听套接字会收到通知,表示监听套接字已经接纳了一个客户的连接请求,MFC框架会自动调用监听套接字的OnAccept事件处理函数。它的原型调用格式如下: virtual void OnAccept( int nErrorCode ); 编程者一般应重载此函数,在其中调用监听套接字对象的Accept函数,来接收客户端的连接请求。
5.1.5 服务器接收客户机的连接请求 (3)创建一个新的空的套接字对象,不需要使用它的Create函数来创建底层套接字句柄。这个套接字专门用来与客户端连接,并进行数据的传输。一般称它为连接套接字,并作为参数传递给下一步的Accept成员函数。 (4)调用监听套接字对象的Accept成员函数,调用格式为: virtual BOOL Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* IpSockAddr = NULL, int* IpSockAddrLen = NULL ); 参数1 : rConnectedSocket,服务器端新的空的套接字对象。 参数2: IpSockAddr,指向SOCKADDR结构的指针,用来返回所连接的客户机套接字的网络地址。 参数3: IpSockAddrLen,用来返回套接字网络地址长度。
5.1.5 服务器接收客户机的连接请求 Accept函数的执行过程是: 首先从监听套接字的待决队列中取出第1个连接请求,使用与监听套接字相同的属性创建1个新的套接字,用来与客户端建立连接。 如果调用Accept函数时,等待队列中没有待决的连接请求, Accept函数立即返回。 rConnectedSocket套接字对象仅用来与连接的客户机套接字对象交换数据,而原来的监听套接字仍然保持打开和监听状态。 Accept函数仅用于面向连接的流式套接字。
5.1.6 发送与接收流式数据 P116 当服务器和客户机建立了连接以后,就可以在服务器端的连接套接字对象和客户端的套接字对象之间传输数据了。对于流式套接字对象,使用CAsyncSocket类的Send成员函数向流式套接字发送数据,使用Receive成员函数从流式套接字接收数据。
5.1.6 发送与接收流式数据 1.用Send成员函数发送数据 P116 格式:virtual int Send( const void* IpBuf, int nBufLen, int nFlags = 0); 参数1: IpBuf, 指向发送缓冲区的指针。 参数2: nBufLen,发送缓冲区数据的长度。 参数3: nFlags,指定发送方式。 用这个成员函数向一个已建立连接的套接字发送数据。 对于一个CAsyncSocket套接字对象,当它的发送缓冲区腾空时,会激发FD_WRITE事件,套接字会得到通知,MFC框架会自动调用这个套接字对象的OnSend事件处理函数。一般编程者会重载这个函数,在其中调用Send成员函数来发送数据。
5.1.6 发送与接收流式数据 2.用Receive成员函数接收数据 P117 格式: Virtual int Receive( Void* lpBuf, Int nBufLen, Int nFlags = 0); 这个函数用来从1个已建立连接的套接字接收数据,将已经到达套接字输入队列中的数据读到指定的输入缓冲区中。 对于一个CAsyncSocket套接字对象,当有数据到达它的接收队列时,会激发FD_READ事件,套接字会得到已经有数据到达的通知,MFC框架会自动调用这个套接字对象的OnReceive事件处理函数。一般编程者会重载这个函数,在其中调用Receive成员函数来接收数据。在应用程序将数据取走之前,套接字接收的数据将一直保留在套接字的缓冲区中。
5.1.7 关闭套接字 P118 1.使用CAsyncSocket类的Close成员函数 格式:virtual void Close( ); 当数据交换结束后,Close成员函数用来释放套接字占用的系统资源,也可以在CAsyncSocket对象被删除时,由析构函数自动调用Close函数。 Close函数的运行行为取决于套接字选项的设置。如果设置了SO_LENGER,如果缓冲区尚有未发送完的数据,要等到数据发送完毕之后才关闭套接字;如果设置了SO_DONTLENGER,则不等待而立即关闭。
5.1.7 关闭套接字 P118 2.使用CAsyncSocket类的ShutDown()成员函数 格式:BOOL ShutDown( int nHow = sends ); nHow=0 禁止套接字接收数据 nHow=1 禁止套接字发送数据 nHow=2 禁止套接字发送和接收数据 如果函数执行成功,返回非0值;否则返回0,并可得到一个错误代码。
5.1.8 错误处理 P118 一般说来,调用CAsyncSocket对象的成员函数后,返回一个逻辑型的值。如果成员函数执行成功,返回TRUE;如果失败,返回FALSE。究竟是什么原因造成错误呢?这时,可以进一步调用CAsyncSocket对象的GetLastError()成员函数,来获取更详细的错误代码,并进行相应的处理。 格式: static int GetLastError( ); 返回值是一个错误码,针对刚刚执行的CAsyncSocket成员函数。 用所获得的这个刚刚失败的操作错误码,可以从出错表中查到其含义。
5.1.9 其它的成员函数 P119 1.关于套接字属性的函数 要设置底层套接字对象的属性,可以调用SetSocketOpt()成员函数; 要获取套接字的设置信息,可调用GetSocketOpt()成员函数; 要控制套接字的工作模式,可调用IOCtl()成员函数,选择合适的参数,可以将套接字设置在阻塞模式(Blocking mode)下工作。
5.1.9 其它的成员函数 2.发送和接收数据 如果创建的是数据报类型的套接字,用SendTo()成员函数来向指定的网络地址发送数据,事先不需要建立发送端和接收端之间的连接,用ReceiveFrom()成员函数可以从某个指定的网络地址接收数据。
5.1.9 其它的成员函数 P119 发送数据SendTo的调用格式,有两种重载的形式,区别在于参数不同。 int SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 ); nHostPort----发送目的方的端口号 lpszHostAddress----发送目的方的主机地址 int SendTo( const void* lpBuf, int nBufLen, const SOCKADDR* lpSockAddr, int nSockAddrLen, int nFlags = 0 ); lpSockAddr----SockAddr结构指针,包含发送目的方的网络地址
5.1.9 其它的成员函数 接收数据ReceiveFrom的调用格式,也有两种重载的形式,区别在于参数不同。 int ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort, int nFlags = 0 ); rSocketAddress ----接收数据的来源对方的点分十进制IP地址 rSocketPort ----接收数据的来源对方的端口号 int ReceiveFrom( void* lpBuf, int nBufLen, SOCKADDR* lpSockAddr, int* lpSockAddrLen, int nFlags = 0 ); lpSockAddr----SockAddr结构指针,用来返回数据来源的网络地址。
5.2 CSocket类 P120 CSocket类是从CAsyncSocket类派生而来的,它们的派生关系如下图所示: CSocket类是CAsyncSocket的高级封装,可以和CSocketFile类和CArchive类一起工作,用户不必处理字节顺序和字节转换等,并提供了阻塞调用功能。
5.2.1 创建Csocket对象 创建CSocket对象分为两个步骤: (2)调用此CSocket对象的Create()成员函数,创建对象的底层套接字。调用格式是: BOOL Create( UINT nSocketPort = 端口号, int nSocketType = SOCK_STREAM | SOCK_DGRAM, LPCTSTR lpszSocketAddress = 套接字所用的IP地址 ); 如果打算使用CArchive对象和套接字一起进行数据传输工作,必须使用流式套接字。
5.2.2 建立连接 CSocket类使用基类CAsyncSocket的同名成员函数Connect()、Listen()、Accept()来建立服务器和客户机套接字之间的连接,使用方法相同。不同的是:CSocket类的Connect()和Accept()支持阻塞调用。比如:在调用Connect()函数时会发生阻塞,直到成功地建立了连接或有错误发生才返回,在多线程的应用程序中,一个线程发生阻塞,其他的线程仍能处理Windows事件。 CSocket对象从不调用OnConnect()事件处理函数。
5.2.3 发送和接收数据 在创建CSocket类对象后,对于数据报套接字,直接使用CSocket类的SendTo()、ReceiveFrom()成员函数来发送和接收数据。对于流式套接字,首先在服务器和客户机之间建立连接,然后使用CSocket类的Send()、Receive()成员函数来发送和接收数据,它们的调用方式与CAsyncSocket类相同。 不同的是:CSocket类的这些函数工作在阻塞的模式。比如,一旦调用了Send()函数,在所有的数据发送之前,程序或线程将处于阻塞的状态。一般将CSocket类与CArchive类和CSocketFile类结合,来发送和接收数据,这将使编程更为简单。 CSocket对象从不调用OnSend()事件处理函数。
5.2.4 CSocket类、CArchive类和CSocketFile类 P121 (2)创建一个基于CSocketFile类的文件对象,并把它的指针传给上面的已创建的CSocket对象。 (3)分别创建用于输入和输出的CArchive对象,并将它们与这个CSocketFile文件对象连接。 (4)利用CArchive对象来发送和接收数据。
5.2.4 CSocket类、CArchive类和CSocketFile类 下面是一段示例代码: CSocket exSocket; // 创建一个空的CSocket对象 CSocketFile* pExFile; // 定义一个CSocketFile对象指针 // 定义一个用于输入的CArchive对象指针 CArchive* pCArchiveIn; // 定义一个用于输出的CArchive对象指针 CArchive* pCArchiveOut; exSocket.Create(); // 创建Csocket对象的底层套接字
5.2.4 CSocket类、CArchive类和CSocketFile类 // 创建CSocketFile对象,并将CSocket对象的指针传递给它。 pExFile = new CSocketFile( & exSocket,TRUE); //创建用于输入的CArchive对象 pCArchiveIn = new CArchive(pExFile, CArchive::load); //创建用于输出的CArchive对象。 pCArchiveOut = new CArchive(pExFile, CArchive::store);
5.2.4 CSocket类、CArchive类和CSocketFile类
5.2.4 CSocket类、CArchive类和CSocketFile类 在发送端,把需要传输的数据插入到用于发送的CArchive对象,CArchive对象会将数据传输到CSocketFile对象中,再交给Csocket对象,由套接字来发送数据。 在接收端,按照相反的顺序传递数据,最终,应用程序从用于接收数据的CArchive对象中获取传输过来的数据。 注意,一个 特定的CArchive对象只能进行单方向的数据传递,就是说要么只能输入要么只能输出。因此,必须在每一端创建两个独立的CArchive对象,一个用CArchive::load创建,用来接收数据;另一个用CArchive::store创建,用来发送数据。 这两个Carchive对象可以共享同一个CSocketFile对象和Csocket套接字。
5.2.5 关闭套接字和清除相关的对象 P122 在使用完CSocket对象以后,应用程序应调用它的Close()成员函数来释放套接字占用的系统资源,也可以调用它的ShutDown()成员函数来禁止套接字读写。而对于相应的CArchive对象、CSocketFile对象和CSocket对象,可以将它们销毁;也可以不作处理,因为当应用程序终止时,会自动调用这些对象的析构函数,从而释放这些对象占用的资源。
5.3 CSocket类的编程模型 P122 CSocket类从CAsyncSocket类派生而来,结合CArchive类使用套接字,使得CSocket类比CAsyncSocket类更容易使用。 CSocket类从CAsyncSocket类继承了许多成员函数,这些函数封装了Windows套接字应用程序编程接口。 CSocket类管理了通信的许多方面,而这些在使用原始API和CAsyncSocket时都必须由编程者自己来做。 使用CSocket类编写网络应用程序的思路与使用WinSock API基本一致,但可以充分利用MFC框架的优势,利用可视化编程语言的控件,方便的构造用户界面,同时利用框架的消息处理机制,方便的对各种网络事件做出响应,从而简化了程序的编写。
5.3 CSocket类的编程模型 下面给出针对流式套接字的CSocket类的编程模型。分为服务器端和客户端。 1.服务器端 (1)CSocket sockServ; // 创建空的服务器端监听套接字对象 // 用众所周知的端口,创建监听套接字对象的底层套接字句柄 (2)sockServ.Create( nPort ); (3)sockServ.Listen(); // 启动对于客户端连接请求的监听 (4)CSocket sockRecv; // 创建空的服务器端连接套接字对象
5.3 CSocket类的编程模型 // 接收客户端的连接请求,并将其他的任务 //转交给连接套接字对象 sockServ.Accept( sockRecv); (5)CSockFile* file ; //创建文件对象并关联到连接套接字对象 file = new CSockFile( &sockRecv); (6)CArchive* arIn, arOut; // 创建用于输入的归档对象 arIn = new CArchive(&file, CArchive::load); // 创建用于输出的归档对象 arOut = new CArchive( &file, CArchive::store); // 归档对象必须关联到文件对象
5.3 CSocket类的编程模型 (7)arIn >> dwValue; // 进行数据输入 // 进行数据输出,输入或输出可以反复进行 arOut << dwValue; (8)sockRecv.Close(); sockServ.Close(); // 传输完毕,关闭套接字对象
5.3 CSocket类的编程模型 2.客户端 P123 (1)CSocket sockClient; // 创建空的客户机端套接字对象 (2)sockClient.Create( ); // 创建套接字对象的底层套接字 // 请求连接到服务器 (3)sockClient.Connect( strAddr, nPort ); (4)CSockFile* file ; //创建文件对象,并关联到套接字对象 file = new CSockFile( &sockClent);
5.3 CSocket类的编程模型 (5)CArchive* arIn, arOut; // 创建用于输入的归档对象 arIn = new CArchive(&file, CArchive::load); // 创建用于输出的归档对象 arOut = new CArchive( &file, CArchive::store); // 归档对象必须关联到文件对象。 (6)arIn >> dwValue; // 进行数据输入 // 进行数据输出。输入或输出可以反复进行 arOut << dwValue; (7)sockClient.Close(); // 传输完毕,关闭套接字对象
5.3 CSocket类的编程模型 强调几点: 服务器端在创建专用监听套接字时,要指定为这种服务分配的众所周知的保留端口号。 服务器接收到客户端的连接请求后,必须创建一个专门用于连接的套接字对象,并将后面的数据传输工作移交给它。 通常,一个服务器应用程序应能处理多个客户机的连接请求,对于每一个客户机的套接字对象,服务器端都应创建一个连接套接字与其进行数据交换。
5.4 用CAsyncSocket类实现聊天室程序 P123 5.4.1 实现目标 应用实例是一个简单的聊天室程序,采用客户/服务器模式,分为客户端程序和服务器端程序。由于服务器只能支持一个客户,实际是一个点对点通信的程序。客户端程序和服务器程序通过网络交换聊天的字符串内容,并在窗口的列表框中显示。
5.4.1 实现目标 实例程序的技术要点是: (1)如何从CAsyncSocket类派生出自己的WinSock类。 (3)重点学习流式套接字对象的使用。 (4)处理网络事件的方法。
5.4.2 创建客户端应用程序 P124 1.使用MFC AppWizard创建客户端应用程序框架 (1)打开Visual Studio 2015,点击File-New-Project, 出现图5.4所示的“New Project”对话框,在“New Project”(新建工程)对话框中,从左边的列表框中选择Visual C++下的MFC条目,然后在中间区域选中MFC Aoolication(MFC应用程序),在下方的“Name”(工程名)文本框中填入工程名talkc,在“Location”(位置)文本框中选定存放此工程的目录,并在右下角的复选框勾选Create directory for solution(为解决方案创建目录),然后单击“OK”按钮。
5.4.2 创建客户端应用程序 图5.4 利用AppWizard创建应用程序
5.4.2 创建客户端应用程序 (2)出现MFC AppWizard设置对话框(MFC AppWizard – talkc),如图5.5所示。因为咱们这儿有些设置需要自定义,直接单击“Next”按钮。 图5.5 MFC AppWizard 设置
5.4.2 创建客户端应用程序 (3)出现MFC AppWizard设置的Application Type(应用程序类型)对话框,如图5.6所示,选择“Dialog Based”复选框,并将下面的Resource Language改为(中文,简体中国)。其他的设置默认就行。直接单击“Next”按钮,出现User interface Features设置,还是按照默认,单击“Next”按钮。
5.4.2 创建客户端应用程序 图5.6 设置应用程序类型以及语言
5.4.2 创建客户端应用程序 (4)出现“Advanced Features”(高级功能)对话框,如图5.7,勾选复选框“Windows Sockets”,表示支持Windows Sockets编程,其他设置默认,然后直接单击“Finish”完成项目的创建。
5.4.2 创建客户端应用程序 2.为对话框界面添加控件对象 P126 在创建了应用程序骨架之后,可以布置程序的主对话框。在Visual Studio的“Solution Explorer(解决方案资源管理器)”(可通过View-Solution Explorer调出)中选择“Resource Files”并通过单击展开,双击其子项“talkc.rc”,在出现的界面中展开Dialog,双击其中的IDD_TALKC_DIALOG,便会出现图形界面的可视化设计窗口以及图形界面控件面板,利用控件面板可以方便地在程序的主对话框界面中添加相应的可视控件对象,如图5.8所示。
5.4.2 创建客户端应用程序 图5.8 在主对话框中添加控件
5.4.2 创建客户端应用程序 完成的对话框如图5.9所示,然后按照表5.2修改控件的属性。 图5.9 Talkc程序的主对话框
5.4.2 创建客户端应用程序 表5.2 Talkc程序主对话框中的控件属性 P127 控件类型 控件ID Caption 静态文本 static text IDC_STATIC_SERVNAME 服务器名称 IDC_STATIC_SERVPORT 服务器端口 IDC_STATIC_MSG 消 息 IDC_STATIC_SENT 发送的消息 IDC_STATIC_RECEIVED 接收的消息 编辑框 edit box IDC_EDIT_SERVNAME IDC_EDIT_SERVPORT IDC_EDIT_MSG 命令按钮 button IDC_BUTTON_CONNECT 连接 IDC_BUTTON_CLOSE 断开 IDOK 发送 列表框 listbox IDC_LIST_SENT IDC_LIST_RECEIVED
5.4.2 创建客户端应用程序 3.为对话框中的控件对象定义相应的成员变量 P127 在窗口菜单中选择“Project(项目)”/“Class Wizard(类向导)”命令,进入“MFC ClassWizard”(类向导)对话框,如图5.10所示。
图5.10 “MFC ClassWizard”(类向导)对话框 5.4.2 创建客户端应用程序 图5.10 “MFC ClassWizard”(类向导)对话框
5.4.2 创建客户端应用程序 将Class name通过下拉列表勾选为CTalkcDlg,然后选择“Member Variables”(成员变量)选项卡,用类向导为对话框中的控件对象定义相应的成员变量。在左边的列表框中选择一个控件,然后单击“Add Variable”(添加变量)按钮,会弹出“Add Member Variable”(添加成员变量)对话框,如图5.11所示,然后按照表5.3输入即可。
5.4.2 创建客户端应用程序 图5.11 增加控件成员变量的对话框
5.4.2 创建客户端应用程序 表5.3 客户端程序对话框中的控件对象对应的成员变量 P129 控件ID Control IDs 变量名称Member Variable Name 变量类别 Category 变量类型 Variable Type IDC_BUTTON_CONNECT m_btnConnect Control CButton IDC_EDIT_SERVNAME m_strServName Value CString IDC_EDIT_SERVPORT m_strServPort int IDC_EDIT_MSG m_strMsg IDC_LIST_SENT m_listSent CListBox IDC_LIST_RECEIVED m_listRecetved
5.4.2 创建客户端应用程序 4.创建从CAsyncSocket类继承的派生类 P129 (1)为了能够捕获并响应socket事件,应创建用户自己的套接字类,它应当从CAsyncSocket类派生,还能将套接字事件传递给对话框,以便执行用户自己的事件处理函数。在Solution Explorer(解决方案资源管理器)中选中“talkc”右键,选择Add—Class,在弹出的对话框里选择MFC下的MFC Class,然后点击“Add”按钮,进入“New Class”(新建类)对话框,如图5.12所示。
5.4.2 创建客户端应用程序 图5.12 添自己的套接字类
5.4.2 创建客户端应用程序 选择或输入以下信息: Class Name: 输入CMySocket Base class:选择CAsyncSocket 点击”Finish”按钮,系统会自动生成CMySocket类对应的包含文件MySocket.h和MySocket.cpp文件,在VC界面的Class View中就可以看到这个类。
5.4.2 创建客户端应用程序 (2)利用类向导ClassWizard为这个套接字类添加响应消息的事件处理成员函数。选择“Project(项目)”/“Class Wizard(类向导)”命令,进入类向导对话框,通过下拉列表选择Class name为CMySocket,选择“Virtual Functions”(虚函数)选项卡,从“Virtual Functions”(虚函数)列表框中选择事件消息对应的虚函数,然后单击“Add Function”(添加函数)按钮或直接双击虚函数,就会看到在“Overriden virtual functions”(重写的虚函数)列表框中添加了相应的事件处理函数。如图5.13所示,此程序中需要添加OnConnect、OnClose和OnReceive 3个函数。这一步会在CMySocket类的MySocket.h中自动生成这些函数的声明,在MySocket.cpp中生成这些函数的框架,以及消息映射的相关代码(可参看后面的程序清单)。
图5.13 为套接字类添加响应消息的事件处理成员函数 5.4.2 创建客户端应用程序 图5.13 为套接字类添加响应消息的事件处理成员函数
5.4.2 创建客户端应用程序 (3)为套接字类添加一般的成员函数和成员变量。选择“Project(项目)”/“Class Wizard(类向导)”命令,进入类向导对话框。通过下拉列表选择Class name为CMySocket。选择“Methods”选项卡,点击Add Method,可以为该类添加成员函数。选择“Member Variables”选项卡,点击“Add Custom”可以为该类添加成员变量,如图5.14所示。 图5.15和图5.16所示是添加操作的对话框。
5.4.2 创建客户端应用程序 图5.14 通过类向导添加成员变量或员函数 图5.15 为套接字类添加一般的成员变量
5.4.2 创建客户端应用程序 图5.16 为套接字类添加一般的员函数
5.4.2 创建客户端应用程序 对这个套接字类,添加一个私有的成员变量,是一个对话框类的指针。 private: CTalkcDlg * m_pDlg; 再添加一个成员函数:void SetParent(CTalkcDlg * pDlg); 这一步同样会在MySocket.h中生成变量或函数的声明,在MySocket.cpp中生成函数的框架代码。如果熟悉的话,这一步的代码也可以直接手工添加。
5.4.2 创建客户端应用程序 (4)手工添加其他代码 P132 手工添加其他代码。在VC++的界面中,在工作区窗口选择“FileView”卡,双击要编辑的文件,在右面的窗口中就会展示该文件的代码,可以编辑添加。
5.4.2 创建客户端应用程序 对于MySocket.cpp,有四处添加: ① 应在文件开头,添加包含文件说明。这是因为此套接字类用到了对话框类的变量。 #include “TalkcDlg.h” ② 在构造函数中,添加对于对话框指针成员变量的初始化代码: CMySocket::CMySocket() { m_pDlg = NULL; } ③ 在析构函数中,添加对于对话框指针成员变量的初始化代码: CMySocket::~CMySocket() { m_pDlg = NULL; } ④ 为成员函数setParent和事件处理函数OnConnect,OnClose和OnReceive添加代码。详见后面的程序清单。
5.4.2 创建客户端应用程序 5.为对话框类添加控件对象事件的响应函数 P132 用类向导(Class Wizard)为对话框中的控件对象添加事件响应函数。打开类向导的方式就不再赘述。打开类向导后选择Class Name为CtalkcDlg,然后选择“Commands”选项卡,按照表5.4依次为3个按钮的单击添加事件的处理函数,如图5.17所示。其他函数是原有的。
5.4.2 创建客户端应用程序 表5.4 为对话框中的控件对象添加事件响应函数 控件类型 对象标识 Object IDs 消息 Messages 函数Member functions 命令按钮 IDC_BUTTON_CLOSE BN_CLICKED OnButtonClose IDC_BUTTON_CONNECT OnButtonConnect IDOK OnSendMsg
5.4.2 创建客户端应用程序 6.为CTalkcDlg对话框类添加其它的成员函数和成员变量 成员变量: CMySocket m_sConnectSocket; //用来与服务器端连接的套接字。 成员函数: void OnClose(); void OnConnect(); // 用来处理与服务器端的通信 void OnReceive();
5.4.2 创建客户端应用程序 7.手工添加的代码 P134 在CTalkcDlg对话框类的talkcDlg.h中添加对于MySocket.h的包含命令,来获得对于套接字的支持: #include “MySocket.h” 在CTalkcDlg对话框类的talkcDlg.cpp中添加对于控件变量的初始化代码: // TODO: Add extra initialization here //用户添加的控件变量的初始化代码 BOOL CTalkcDlg::OnInitDialog() { m_strServName="localhost"; // 服务器名 = localhost m_nServPort=1000; // 服务端口 = 1000 UpdateData(FALSE); // 更新用户界面 //设置套接字类的对话框指针成员变量 m_sConnectSocket.SetParent(this);}
5.4.2 创建客户端应用程序 8.添加事件函数和成员函数的代码 主要在CTalkcDlg对话框类的talkcDlg.cpp中和CMySocket类的Mysocket.cpp中,添加用户自己的事件函数和成员函数的代码,要注意,这些函数的框架已经在前面的步骤中,由VC++的向导生成,只要将用户自己的代码填入其中即可。 9.进行测试。 测试应分步进行,在上面的步骤中,每作一步,都可以试着编译执行。
5.4.3 客户端程序的类与消息驱动 下图说明了点对点交谈的客户端程序的类与消息驱动关系。
5.4.4 客户端程序主要功能的代码和分析 P135 1.应用程序类CTalkcApp对应的文件 应用程序类CTalkcApp,对应的文件是talkc.h和talkc.cpp; talkc.h定义了CTalkcApp类, talkc.cpp是该类的实现代码,完全由VC++自动创建,用户不必作任何改动。 2.派生的套接字类CMySocket对应的文件 CMySocket类对应MySocket.h头文件和MySocket.CPP文件。
5.4.4 客户端程序主要功能的代码和分析 3.对话框类CTalkcDlg对应的文件 对话框类CTalkcDlg,对应的文件是talkcDlg.h和talkcDlg.cpp。 4.其他文件 对于VC++为talkc工程创建的其他文件,如stdafx.h和stdafx.cpp,以及Resource.h和talkc.rc都不需要作任何处理。
5.4.5 创建服务器端程序 同样利用可视化语言的集成开发环境(IDE)来创建服务器端应用程序框架。步骤是: 1.使用MFC AppWizard创建服务器端应用程序框架。 2.为对话框界面添加控件对象 3.为对话框中的控件对象定义相应的成员变量 4.创建从CAsyncSocket类继承的派生类 5.为对话框类添加控件对象事件的响应函数 6.为CTalksDlg对话框类添加其它的成员函数和成员变量 7.手工添加的代码 8.添加事件函数和成员函数的代码 9. 进行测试。
5.4.6 服务器端程序的流程和消息驱动 Talks服务器端程序的类与消息驱动的关系
talks.h和talks.cpp不需要做任何改动 2.CMySocket类对应的文件 (1)MYSOCKET.H文件 1.CTalksApp类对应的文件 talks.h和talks.cpp不需要做任何改动 2.CMySocket类对应的文件 (1)MYSOCKET.H文件 (2)MySocket.cpp文件 3.CTalksDlg类对应的文件 (1)talksDlg.h (2)talksDlg.cpp文件 4.其它的文件不必改动
5.5 用Csocket类实现聊天室程序 P151 5.5.1 聊天室程序的功能 聊天室程序采用C/S模式。 服务器可以同时与多个客户机建立连接,为多个客户机服务。服务器接收客户机发来的信息,然后将它转发给聊天室的其他客户机,从而实现多个客户机之间的信息交换。 服务器动态统计进入聊天室的客户机数目,并显示出来。服务器及时显示新的客户机进入聊天室和客户机退出聊天室的信息,也转发给其他的客户机。 进入服务器程序后,用户应首先输入监听端口号,单击“监听”按钮启动监听。服务器等待客户端的连接请求,当客户端的连接请求到来时,服务器接收它,然后进入与客户机的会话期。服务器程序动态地为新的客户机创建相应的套接字对象,并采用链表来管理客户机的套接字对象,从而实现了一个服务器为多个客户机服务的目标。
5.5.1 聊天室程序的功能 可以同时启动多个客户端程序。进入客户端程序后,用户应首先输入要连接的服务器名,服务器的监听端口和客户机名,然后单击“连接”按钮,就能与服务器建立连接。然后即可输入信息,单击“发送”按钮向服务器发送聊天信息。在客户机程序的列表框中,能实时显示聊天室的所有客户机发送的信息,以及客户机进出聊天室的信息。
5.5.1 聊天室程序的功能 这个实例程序的技术要点如下。 (1)如何从CSocket类派生出自己所需的Win Sock类。 (2)如何利用CSocketFile类、CArchive类和CSocket类的合作来实现网络进程之间的数据传输。 (3)如何用链表管理多个动态客户机的套接字,实现服务器和所有的聊天客户机所显示信息的同步更新。
5.5.2 创建聊天室的服务器端程序 利用可视化语言的集成开发环境(IDE)来创建服务器端应用程序框架,步骤如下。 1.利用MFC AppWizard创建服务器端应用程序框架 工程名为ts,选择Dialog based的应用程序类型,选择中文(中国),选择Windows Sockets支持,其他接受系统的默认值。所创建的程序将自动创建两个类,应用程序类CTsApp,对应的文件是ts.h和ts.cpp;对话框类CTsDlg,对应的文件是tsDlg.h和tsDlg.cpp。
5.5.2 创建聊天室的服务器端程序 2.为对话框界面添加控件对象 完成的对话框如图5.21所示,然后按照表5.8修改控件的属性。 表5.8 ts程序主对话框中的控件属性 控 件 类 型 控件ID Caption 静态文本 static text IDC_STATIC_PORT 监听端口号 IDC_STATIC_NUM 聊天室在线人数:0 编辑框 edit box IDC_EDIT_PORT 命令按钮 button IDC_BUTTON_LISTEN 监听 IDOK 停止服务 列表框 listbox IDC_LIST_MSG 注:不选Sort
变量名称 (Member Variable Name) 5.5.2 创建聊天室的服务器端程序 3.为对话框中的控件对象定义相应的成员变量 用类向导为对话框中的控件对象定义相应的成员变量。按照表5.9输入即可。 表5.9 ts程序服务器端对话框中控件对象对应的成员变量 控件ID (Control IDs) 变量名称 (Member Variable Name) 变量类别 (Category) 变量类型 (Variable Type) IDC_STATIC_NUM m_staNum Control CStatic IDC_EDIT_PORT m_nPort Value UINT IDC_BUTTON_LISTEN m_btnListen CButton IDOK m_btnClose IDC_LIST_MSG m_listMsg CListBox
5.5.2 创建聊天室的服务器端程序 4.创建从CSocket类继承的派生类 从CSocket类派生两个套接字类,一个类名为CLSocket,专用于监听客户端的连接请求,为它添加OnAccept事件处理函数;另一个类名为CCSocket,专用于与客户端建立连接并交换数据,为它添加OnReceive事件处理函数。这两个类都要添加一个指向对话框类的指针变量:CTsDlg * m_pDlg;
5.5.2 创建聊天室的服务器端程序 为CCSocket添加以下成员变量和成员函数。 成员变量: CSocketFile* m_pFile; //CSocketFile对象的指针变量 //用于输入的CArchive对象的指针变量 CArchive* m_pArchiveIn; //用于输出的CArchive对象的指针变量 CArchive* m_pArchiveOut; 成员函数: void Initialize(); //初始化 void SendMessage(CMsg* pMsg); //发送消息 void ReceiveMessage(CMsg* pMsg); //接收消息
5.5.2 创建聊天室的服务器端程序 5.为对话框类添加控件对象事件的响应函数 按照表5.10,用类向导为服务器端的对话框中的控件对象添加事件响应函数,主要是针对“监听”按钮和“停止服务”按钮的单击事件的处理函数。 表5.10 服务器端的控件对象对应的事件响应函数 控 件 类 型 对象标识 (Object IDs) 消息 (Messages) 成员函数 (Member functions) 命令按钮 IDOK BN_CLICKED OnClose IDC_BUTTON_LISTEN OnButtonListen
5.5.2 创建聊天室的服务器端程序 6.为CTsDlg对话框类添加其他的成员函数和成员变量 成员变量: CLSocket* m_pLSocket; //侦听套接字指针变量 CPtrList m_connList; //连接列表 成员函数: void OnAccept(); //接收连接请求 //获取客户机的发送消息 void OnReceive(CCSocket* pSocket); //向聊天室的所有的客户机转发消息 void backClients(CMsg* pMsg);
5.5.2 创建聊天室的服务器端程序 7.创建专用于数据传输序列化处理的类CMsg 为了利用CSocket类及其派生类可以和CSocketFile对象、CArchive对象合作来进行数据发送和接收的特性,构造一个专用于消息传输的类。该类必须从CObject类派生,如图5.23所示。 选择“插入”/“新建类”命令,弹出“New Class”(新建类)对话框,在“Class type”(类类型)处选择Generic Class,在“Name”(名称)处输入类名CMsg,在基类的“Derived From”处输入CObject,单击“OK”按钮即可。
5.5.2 创建聊天室的服务器端程序 为CMsg类添加成员变量和成员函数(可参考后面的文件清单): CString m_strText; //字符串成员 BOOL m_bClose; //是否关闭状态 virtual void Serialize(CArchive& ar); //序列化函数
5.5.2 创建聊天室的服务器端程序 8.添加事件函数和成员函数的代码 主要在CTsDlg对话框类的tsDlg.cpp中和两个套接字类的实现文件中,添加用户自己的事件函数和成员函数的代码。
5.5.3 聊天室服务器端程序的主要实现代码和分析 1. CLSocket类对应的文件 (1)LSocket.h文件清单。 (2)LSocket.cpp文件清单。 2. CCSocket类对应的文件 (1)CSocket.h文件清单。 (2)CSocket.cpp文件清单。 3. CMsg类对应的文件 (1)Msg.h文件清单。 (2)Msg.cpp文件清单。 4. CTsDlg类对应的文件 (1)tsDlg.h文件清单。 (2) tsDlg.cpp文件清单。
5.5.4 创建聊天室的客户端程序 利用可视化语言的集成开发环境(IDE)来创建服务器端应用程序框架,步骤如下。 1.利用MFC AppWizard创建客户端应用程序框架 工程名为tc,选择Dialog based的应用程序类型,选择中文(中国),选择Windows Sockets支持,其他接受系统的默认值。所创建的程序将自动创建两个类,应用程序类CTcApp,对应的文件是tc.h和tc.cpp;对话框类CTcDlg,对应的文件是tcDlg.h和tcDlg.cpp。
5.5.4 创建聊天室的客户端程序 2.为对话框界面添加控件对象 完成的对话框如图5.22所示,然后按照表5.11修改控件的属性。
5.5.4 创建聊天室的客户端程序 表5.11 聊天客户端tc程序主对话框中的控件属性 控件类型 控件ID Caption 静态文本 static text IDC_STATIC_CNAME 客户名 IDC_STATIC_SNAME 服务器名 IDC_STATIC_PORT 端口号 IDC_STATIC_MSG 消息 编辑框 edit box IDC_EDIT_CNAME 注:输入客户名的文本框 IDC_EDIT_SNAME 注:输入服务器名的文本框 编辑框 edit box IDC_EDIT_PORT 注:输入端口号的文本框 IDC_EDIT_MSG 注:输入消息的文本框 命令按钮 button IDC_BUTTON_CONN 连接 IDOK 发送 IDC_BUTTON_CLOSE 退出 列表框 listbox IDC_LIST_MSG 注:不选Sort
5.5.4 创建聊天室的客户端程序 3. 为对话框中的控件对象定义相应的成员变量 用类向导为对话框中的控件对象定义相应的成员变量。按照表5.12输入即可。
变量名称 (Member Variable Name) 5.5.4 创建聊天室的客户端程序 表5.12 聊天客户端tc程序主对话框中控件对象对应的成员变量 控件ID (Control IDs) 变量名称 (Member Variable Name) 变量类别 (Category) IDC_EDIT_CNAME m_strCName Value IDC_EDIT_SNAME m_strSName IDC_EDIT_PORT m_nPort IDC_EDIT_MSG m_strMsg IDC_BUTTON_CONN m_btnConn Control IDOK m_Send IDC_BUTTON_CLOSE m_btnClose IDC_LIST_MSG m_listMsg
5.5.4 创建聊天室的客户端程序 4. 创建从CSocket类继承的派生类 从CSocket类派生一个套接字类,类名为CCSocket,用于与服务器端建立连接并交换数据。改造它的构造函数,为它添加OnReceive事件处理函数和以下的成员变量: CTcDlg* m_pDlg; //成员变量
5.5.4 创建聊天室的客户端程序 5.为CTcDlg对话框类添加控件对象事件的响应函数 按照表5.13,用类向导为客户端的对话框中的控件对象添加事件响应函数,主要是针对按钮的单击事件的处理函数。 控 件 类 型 对象标识 (Object IDs) 消息(Messages) 命令按钮 IDOK BN_CLICKED IDC_BUTTON_CONN IDC_BUTTON_CLOSE 对话框 CTcDlg WM_DESTROY
5.5.4 创建聊天室的客户端程序 6.为CTcDlg对话框类添加其他的成员函数和成员变量 成员变量: CCSocket* m_pSocket; //套接字对象指针 CSocketFile* m_pFile; //CSocketFile对象指针 CArchive* m_pArchiveIn;//用于输入的CArchive对象指针 CArchive* m_pArchiveOut;//用于输出的CArchive对象指针 成员函数: void OnReceive(); //接收信息 void ReceiveMsg(); //接收服务器发来的信息 void SendMsg(CString& strText,bool st);//向服务器发送信息
5.5.4 创建聊天室的客户端程序 7.创建专用于数据传输序列化处理的类CMsg 与服务器端一样,客户端也要构造一个专用于消息传输的类。该类必须从CObject类派生,类名CMsg。 为CMsg类添加成员变量和成员函数(可参考后面的文件清单): CString m_strBuf; //字符串成员 BOOL m_bClose; //是否关闭状态 virtual void Serialize(CArchive& ar); //序列化函数
5.5.4 创建聊天室的客户端程序 8.添加事件函数和成员函数的代码 主要在CTcDlg对话框类的tcDlg.cpp中和套接字类的实现文件中,添加用户自己的事件函数和成员函数的代码。
1.CCSocket类对应的文件 (1)Csocket.h文件清单 (2)Csocket.cpp文件清单 2.CMsg类对应的文件 5.5.5 聊天室客户端程序的主要实现代码和分析 1.CCSocket类对应的文件 (1)Csocket.h文件清单 (2)Csocket.cpp文件清单 2.CMsg类对应的文件 (1)Msg.h文件清单 (2)Msg.cpp文件清单 3.CTcDlg类对应的文件 (1)tcDlg.h文件清单 (2)tcDlg.cpp文件清单