1、第12章 聊天室,聊天室就是有好多人坐在一间大屋子里共同聊天,谁都可以发言,谁都可以听到别人说的话,当然也允许窃窃私语。本章就实现了这么一个C/S模式的聊天室软件,模拟了现实中的聊天室,只是声音变成了文字,大房间变成了客户端软件。这样做的好处是可以不受地点的约束,在任何地方加入到聊天室中。,12.1 聊天室功能简介,本节读者将看到本章的示例演示,各小节简要说明了要怎样使用这个聊天室,聊天室实现的效果又是怎样的。,12.1.1 开启聊天室服务器,既然示例是基于C/S模式的,那么聊天室服务器肯定是要被首先开启的,如图12.1所示。,图12.1 聊天室服务器,单击按钮“开启服务器”,然后会有信息提示
2、框弹出,成功开启服务器的话按钮会被禁用,如图12.2所示。,图12.2 服务器开启成功,接下来等待聊天室客户端的连接就可以了。,12.1.2 登录聊天室,进入聊天室前,聊友需要输入一些信息,包括:服务器的IP地址、登录的聊友名和登录后的头像。“登录”对话框如图12.3所示。,图12.3 “登录”对话框,12.1.3 聊天对话框,成功登录聊天室的话就可以开始聊天了,如图12.4所示。,图12.4 聊天对话框,对话框左面的图像列表用来显示已经登录聊天室的聊友,右上的文本框用来记录聊天的信息,右下的文本框用来编写要发送的信息,只要单击“发送”按钮就可以发送信息了。 当然也可以找人单独说话了,只要用鼠
3、标双击图像列表上的聊友就可以了。“私聊”复选框会被设置选中,那么你发的信息就只有聊友可以看到了。当不想继续“私聊”的时候,只要取消“私聊”复选框的复选就可以了,如图12.5所示。,图12.5 私聊,聊天室服务器也会记录一些信息,包括:已登录的聊友,聊友的公聊内容,聊友的登录退出信息,如图12.6所示。,图12.6 聊天室服务器,12.2 CSocket简介,CSocket继承于CAsyncSocket,后者对Win API套接字进行了很好的封装。使用这些成员函数完成网络通信,会容易许多。,12.2.1 创建socket,类成员函数Create()的原型如下: BOOL Create(UINT
4、nSocketPort = 0,int nSocketType = SOCK_STREAM,LPCTSTR lpszSocketAddress = NULL ); 参数含义如下: nSocketPort:指定用于socket的端口号。 函数返回非0表示创建成功。,12.2.2 侦听连接请求,类成员函数Listen()的原型如下: BOOL Listen(int nConnectionBacklog = 5 ); 参数nConnectionBacklog,表示等待连接的队列的最大长度。 函数返回非0表示开始侦听。,12.2.3 接受连接请求,类成员函数Accept()的原型如下: virtual
5、 BOOL Accept(CAsyncSocket 参数含义如下: rConnectedSocket:一个可用于连接的新socket的引用。,结构SOCKADDR的定义如下: struct sockaddr unsigned short sa_family;char sa_data14; ; 参数含义如下: sa_family:保存网络地址协议。,通常情况下使用结构SOCKADDR_IN代替结构SOCKADDR,前者专用于TCP/IP套接字,它们有相同的大小,使用时直接强制转换就可以了。SOCKADDR_IN的结构定义如下: struct sockaddr_inshort sin_family
6、;unsigned short sin_port;struct in_addr sin_addr;char sin_zero8; ; 参数含义如下: sin_family:地址协议,必须为AF_INET。,12.2.4 发送信息,类成员函数Send()的原型如下: virtual int Send(const void* lpBuf,int nBufLen,int nFlags = 0 ); 参数含义如下: lpBuf:包含要发送数据的缓存指针。 信息发送成功函数会返回发送的字节数,否则返回SOCKET_ERROR。,12.2.5 接收信息,类成员函数Receive()的原型如下: virtu
7、al int Receive(void* lpBuf,int nBufLen,int nFlags = 0 ); 参数含义如下: lpBuf:指向接收数据的缓存。 信息接收成功函数会返回接收的字节数,否则返回SOCKET_ERROR。,12.3 我们约定个协议,通过套接字发送和接收的只是字符串,要想赋予字符串不同的含义,就需要约定一些“标记”,加到字符串中。0x是十六进制数的标志,0x10相当于十进制的16。,1.聊友初次加入聊天室,协议信息格式:0x11聊友头像号聊友名。如client_a初次加入第一次发信息,构造信息如下: 0x110x05client_a,2.已登录聊友信息,协议信息格式
8、:0x31聊友头像号聊友名。如client_b是已登录的聊友,构造信息如下: 0x310x01client_b,3.群聊信息,协议信息格式:0x21聊友名:想说的话。如client_a想公开发言“Hello,everyone!”,构造信息如下: 0x21client_a:Hello everyone,4.私聊信息,协议信息格式:0x51对方聊友名(在第100个字节处开始)聊友名:想说的话。如client_a想和client_c私聊,说“nice to meet you”,构造信息如下: 0x51client_c (空) (第100个字节处)client_a:nice to meet you,5
9、.聊友退出聊天室,协议信息格式:0x41聊友名信息。如client_a退出聊天室,构造信息如下: 0x41client_a,12.4 灵活可靠的控件,本章示例用到了多个有趣的控件或类,本节将分别讲解这些示例程序的“部件”。,12.4.1 位图按钮,位图按钮在MFC中的类是CBitmapButton,它继承于CButton,位图按钮与父类的区别是:按钮上可以显示图片。按钮可以有4种状态,按下、弹起、被选中和不可用,每一种状态都可以用一张图像来表示,资源编辑器中没有CBitmapButton相关的控件,所以我们需要自己创建,并为按钮设置BS_OWNERDRAW样式,即自绘类型。创建位图按钮的方法会
10、因位置不同而有较大区别。,1.在窗口的客户区中,(1)在窗口的客户区创建位图按钮的方法步骤如下: 为按钮准备14个位图。(弹起状态的位图是必须的,其它的位图可以不添加) 构造CBitmapButton对象。 用构造的对象创建一个位图按钮。 当位图按钮被创建了以后,调用成员函数LoadBitmaps()载入位图资源。 (2)建立基于单文档的应用程序,命名为BMPBTNWnd,在向导的第6步,取消3个复选框的选择这三个复选框分别是:Docking toolbar、Initial status bar和Printing and print preview。如图12.7所示,取消三个复选框的选择可以使
11、向导为我们生成的程序更加简洁,当没有了工具栏和状态栏的时候,精力可以集中到要解决的问题上。,图12.7 向导对话框的设置,按步骤先往工程中导入4幅位图,如图12.8所示。,图12.8 按钮位图,修改位图的ID分别为:IDB_BIT_UP、IDB_BIT_DOWN、IDB_BIT_FOCUS和IDB_BIT_DISABLE。 (3)在类CBMPBTNWndView中添加保护的成员,如下:在视图类的构造函数中初始化成员变量m_flag,如下:给视图类添加消息WM_CREATE的响应函数,即创建视图的时候同时创建位图按钮,如下:,成员函数Create(),继承自类CButton,用来创建按钮控件,原
12、型如下: virtual BOOL Create(LPCTSTR lpszCaption,DWORD dwStyle,const RECT 参数含义如下: lpszCaption:按钮上文本字符串的指针。 nID:指定按钮控件的ID。按钮是我们动态创建的,所以ID设为NULL。,成员函数LoadBitmaps(),用于载入位图资源,原型如下: BOOL LoadBitmaps(LPCTSTR lpszBitmapResource,LPCTSTR lpszBitmapResourceSel = NULL,LPCTSTR lpszBitmapResourceFocus = NULL,LPCTSTR
13、 lpszBitmapResourceDisabled = NULL ); BOOL LoadBitmaps(UINT nIDBitmapResource,UINT nIDBitmapResourceSel = 0,UINT nIDBitmapResourceFocus = 0,UINT nIDBitmapResourceDisabled = 0 ); 从函数的原型来看,可以得到结论:位图既可以通过资源ID导入,又可以通过资源名导入。 (4)编辑菜单项,即新加入一个菜单“禁用按钮”,如图12.9所示。,图12.9 添加菜单项,在视图类中处理“禁用按钮”菜单的单击事件,如图12.10所示。,图1
14、2.10 用ClassWizard添加事件处理函数,响应函数中,添加如下代码:响应函数用来设置按钮的“禁用”与“可用”。 (5)运行程序,如图12.11所示。,图12.11 程序的运行效果,2.在对话框中,(1)在对话框中创建位图按钮的方法步骤如下: 同样需要导入14张位图。 (2)创建基于对话框的应用程序,命名为BMPBTNDlg,设计对话框如图12.12所示。,图12.12 对话框设计,为ID为IDC_BMPBTN的按钮设置自绘属性,如图12.13所示。,图12.13 设置按钮的自绘属性,导入之前用过的4张位图,修改ID如图12.14所示,其它位图修改方法一样,ID分别为:“BMPBTNU
15、“,“BMPBTND“,“BMPBTNF“和“BMPBTNX“。,图12.14 修改位图ID,(3)为对话框类添加两个成员变量,如下:在对话框的构造函数中给变量赋值,如下:在对话框的初始化函数中,设置位图按钮变量,如下:(4)为“禁用位图按钮”按钮添加单击事件的响应函数,如下:(5)运行程序,效果如图12.15所示。,图12.15 程序运行效果,12.4.2 IP编辑框,VC+6.0的工具箱中有一个控件,名叫IP Address,如图12.16所示。,图12.16 IP编辑框控件,编辑框被3个点隔开,在每个“0”的位置处可以填写0255的数字,其它的内容都不被允许输入。在MFC中它的控件类是C
16、IPAddressCtrl。,1.常用成员函数,(1)GetAddress(),用于从IP控件的4个域中获取IP地址,4个域与控件的对应关系如图12.17所示。,图12.17 IP控件的域,函数原型如下: int GetAddress(BYTE 参数含义如下: nField0、nField1、nField2、nField3:来自IP控件各个域中的数值。每个参数是8个二进制位的BYTE型。,或: int GetAddress(DWORD 参数含义如下: dwAddress:DWORD型,32个二进制位,每8个位保存域中的一个数值,具体的存放如表1所示。,表1 dwAddress各个位与域的对应关
17、系,两个函数的返回值都是IP控件中非空域的数量。 (2)IsBlank(),用来检测IP控件中是否所有域都为空,函数无参数,原型如下: BOOL IsBlank( ) const; 函数返回非零表示所有域都为空。,(3)SetAddress(),用来为IP控件的4个域赋值,有2个同名函数,原型如下: void SetAddress(BYTE nField0,BYTE nField1,BYTE nField2,BYTE nField3 ); void SetAddress(DWORD dwAddress ); 与GetAddress()的参数一样,但是功能正好相反,函数无返回值。,(4)SetF
18、ieldRange(),用来为指定的域赋值,原型如下: void SetFieldRange(int nField,BYTE nLower,BYTE nUpper ); 参数含义如下: nField:指定域号,即03中的一个。 nUpper:域中可以填写的最大值。,(5)ClearAddress(),用来清空IP控件的内容,原型如下: void ClearAddress( ); 函数不需要参数,直接调用就可以了。 (6)SetFieldFocus(),用来设置键盘焦点在IP控件的哪个域中,原型如下: void SetFieldFocus(WORD nField ); 参数含义如下: nFiel
19、d:从0开始的要被设置焦点的域,若值大于3,那么焦点会停留在第一个空白的域上,若所有的域都不为空,那么焦点被设置在0号域上。,2.使用方法举例,(1)建立基于对话框的应用程序,命名为IpCtrl,为了程序的简洁,我们在向导的第2步取消“关于”对话框的复选,如图12.18所示。,图12.18 工程向导设置,拖动控件,设计对话框如图12.19所示。,图12.19 对话框界面设计,(2)为IP控件添加成员变量,命名为m_ipAddress,如图12.20所示。,图12.20 添加成员变量,在对话框的初始化函数OnInitDialog()中,填写代码如下:函数中设置了IP控件的第3个域的取值为020,
20、并设置默认的IP为127.0.0.1。 (3)添加按钮“获取IP”被单击的响应函数OnGetip(),如下:响应函数OnGetip()首先检查IP控件是否全为空,并弹出提示信息,如图12.21所示。,图12.21 没填写IP就单击按钮,只填写部分信息的话,也会提示出错,如图12.22所示。,图12.22 信息填写不全就单击按钮,全部准确无误的话,用信息框显示输入的IP,如图12.23所示。,图12.23 准确填写IP后单击按钮,响应函数OnGetip()的编写也可以是如下的样子:功能和效果完全一样,但是用到了位操作来获取IP。 (4)添加按钮“重写IP”被单击的响应函数OnClearip(),
21、如下:函数先清空IP控件所填写的内容,然后将焦点设置在了第1个域上,如图12.24所示。,图12.24 重写IP,12.4.3 列表控件,工具箱中还有一个常用控件,名叫List Control,如图12.25所示。,图12.25 列表控件及其4中样式,在MFC中,它的控件类是CListCtrl,用来显示图标和标签项的集合。,1.常用成员函数,(1)InsertItem(),用来向列表框中插入新的一项,它有4个重载的函数,原型分别如下: int InsertItem(const LVITEM* pItem );,唯一的一个参数pItem,是结构LVITEM的指针,部分结构如下: typedef
22、struct _LVITEM UINT mask; int iItem; int iSubItem; .LPTSTR pszText; int cchTextMax; int iImage; LPARAM lParam; LVITEM, *LPLVITEM; 各成员含义如下: mask:设置标志位,用来指定结构的哪些成员需要填充数据或哪些成员会被请求。,函数通过参数结构描述的信息来插入列表项。 int InsertItem(int nItem,LPCTSTR lpszItem ); int InsertItem(int nItem,LPCTSTR lpszItem,int nImage );
23、参数含义如下: nItem:插入列表项位置的索引。,这两个函数比较常用,因为满足了我们大部分的需求。 int InsertItem(UINT nMask,int nItem,LPCTSTR lpszItem,UINT nState,UINT nStateMask,int nImage,LPARAM lParam ); 这个函数参数与讲解的第一个函数中的结构LVITEM成员类似,不再介绍。,(2)SetImageList(),为列表控件分配一个图像列表,原型如下: CImageList* SetImageList(CImageList* pImageList,int nImageListType
24、 ); 参数含义如下: pImageList:被分配的图像列表的指针。 函数返回先前分配的图像列表的指针。,(3)FindItem(),查找包含指定字符的列表项,原型如下: int FindItem(LVFINDINFO* pFindInfo,int nStart = -1 ) const; 参数含义如下: nStart:开始查找的列表项位置,可以指定-1,表示从列表项的第一项开始,查找时不包括指定的起始位置。,pFindInfo:一个包含要查找项信息的结构LVFINDINFO,结构定义如下: typedef struct tagLVFINDINFO UINT flags;LPCTSTR ps
25、z;LPARAM lParam;POINT pt;UINT vkDirection; LVFINDINFO, *LPFINDINFO; 各成员含义如下: flags:执行查找的类型。例子会用到的值LVFI_PARTIAL表示列表项标签以参数psz指定的字符串起始,LVFI_STRING表示查找的依据是列表项的标签。 函数查找匹配列表项成功会返回列表项的索引,没找到时返回-1。,(4)DeleteItem(),用来删除列表控件中指定的列表项,原型如下: BOOL DeleteItem(int nItem ); 参数nItem就是要删除的列表项的索引。 (5)GetFirstSelectedIte
26、mPosition(),用来获取被选中列表项的位置,原型如下: POSITION GetFirstSelectedItemPosition( ) const; 函数返回NULL表示没有列表项被选中。 (6)GetNextSelectedItem(),用来获取指定位置处列表项的索引,原型如下: int GetNextSelectedItem(POSITION 参数pos既作为输入,也作为输出,可能来源于GetFirstSelectedItemPosition()或本身的调用,即聊友下一个列表项选择的位置。,(7)GetNextItem(),查找具有指定属性的列表项,原型如下: int GetNe
27、xtItem(int nItem,int nFlags ) const; 参数含义如下: nItem:开始查找的列表项位置,可以指定-1,表示从列表项的第一项开始,查找时不包括指定的起始位置。,(8)InsertColumn(),用来往列表控件中插入新的一列,原型如下: int InsertColumn(int nCol,const LVCOLUMN* pColumn ); int InsertColumn(int nCol,LPCTSTR lpszColumnHeading,int nFormat = LVCFMT_LEFT,int nWidth = -1,int nSubItem = -1
28、 ); 参数含义如下: nCol:新列的索引号。,(9)SetItemText(),用来改变列表控件项或子项的标签,原型如下: BOOL SetItemText(int nItem,int nSubItem,LPCTSTR lpszText ); 参数含义如下: nItem:列表项所在的行。,(10)DeleteAllItems(),用于删除所有的列表项,原型如下: BOOL DeleteAllItems( ); 函数返回非0表示操作成功。 (11)GetItemCount(),用于获取列表控件列表项总数,原型如下: int GetItemCount( ) const; 函数返回列表项总数。,
29、2.使用方法举例,(1)建立基于对话框的应用程序,命名为ListCtrl,设计对话框界面如图12.26所示。,图12.26 对话框界面设计,其中ID为IDC_LIST的列表控件的styles为Report,ID为IDC_LIST2的列表控件的styles为Small Icon,如图12.27所示。,图12.27 列表控件属性对话框,(2)分别为两个列表控件添加成员变量,如表2所示。,表2 列表控件ID与变量名,为工程插入6个图标资源,如图12.28所示,图标的大小是32*32的。,图12.28 插入的图标资源,在工程的初始化函数OnInitDialog()中,添加代码如下:其中m_imagel
30、ist是类CListCtrlDlg的保护成员,如下:初始化函数OnInitDialog()创建了图像列表,并向其中添加了6个图标,将2个列表控件与图像列表建立了关联后,又设置了样式为Report的列表控件的标题头,效果如图12.29所示。,图12.29 工程初始化效果,(3)为类CListCtrlDlg添加2个保护的成员变量count1和count2,如下:并在类的构造函数中完成初始化,如下:给2个名称都是“添加新项”的按钮编写单击的响应函数,如下:响应函数为列表控件的“头像”列插入了循环的图像,为“名称”列插入了规范命名的字符串。这个响应函数更简单,只用了一个函数就完成了循环图像和规范字符串
31、的插入。它们的运行效果如图12.30所示。,图12.30 “添加新项”按钮的运行效果图,如果代码中缺少图像循环的机制,不断单击“添加新项”按钮的结果是耗尽所有图像资源。如图12.31所示。,图12.31 插入的新项不完整,(4)“删除指定项”按钮被设计的功能是,当选中列表控件中的某一项,再单击按钮的时候被选中的列表项会被删除,响应函数编写如下:可以看出,当没有列表项被选中的时候,单击按钮不会有任何反应。编写“删除项”按钮的消息响应函数如下:“删除项”按钮完成的操作与“删除指定项”按钮完成的操作不同,它会逆序删除列表中已有的项。两个按钮的单击效果如图12.32所示。,图12.32 删除列表项的运
32、行效果,(5)对于对话框左面的列表框,我们添加“双击”的响应函数,如下:函数会在弹出的对话框中显示选中的列表项位置。与左面的列表框不同,我们为右边的列表框添加“单击”的响应函数,原理甚至是代码都是一样的,只是响应的时机不同而已,代码如下:“单击”和“双击”两个列表框的运行效果如图12.33所示。,图12.33 列表框“单”、“双”击事件,(6)添加最大的按钮,“清空列表”按钮单击事件的响应函数,如下:函数同时清空了2个列表框中的所有列表项。,12.4.4 图像组合框控件,组合框我们很常用,但是对于图像组合框就显得陌生了许多,它们实现效果的不同在于组合框只能插入文本,而图像组合框还可以插入图像。
33、简单来说图像组合框是个功能给扩展了的组合框,它提供了对图像列表的支持。它在工具箱的右下角,名称是Extend Combo Box,在MFC中封装的类是CComboBoxEx。,1.常用成员函数,InsertItem(),用来向图像组合框中插入项,原型如下: int InsertItem(const COMBOBOXEXITEM* pCBItem );,参数pCBItem是结构COMBOBOXEXITEM的常指针变量,结构的定义如下: typedef struct UINT mask;INT_PTR iItem;LPTSTR pszText;int cchTextMax;int iImage;i
34、nt iSelectedImage;int iOverlay;int iIndent;LPARAM lParam; COMBOBOXEXITEM, *PCOMBOBOXEXITEM; 各结构成员含义如下: mask:一些位标志。用来指定结构中的哪些属性或操作是可用的,必须填充。 若函数InsertItem()插入项成功,InsertItem()会返回插入项的索引,即位置。,2.使用方法举例,(1)建立基于对话框的应用程序,命名为ComboExCtrl,设计对话框界面如图12.34所示。,图12.34 对话框界面设计,为图像组合框添加成员变量m_comboEx,类型为CComboBoxEx,同样
35、导入12.4.3小节用到的6个图标图像。 (2)在对话框的初始化函数中添加代码,如下:m_imagelist是定义在类CComboExCtrlDlg中的保护成员变量,类型为CImageList。函数OnInitDialog()首先将6个图标资源插入到图像列表中,然后将图像列表与图像组合框联系起来,最后给图像组合框插入了6个包含图像和标签的项。如图12.35所示。单击组合框的小黑三角,弹出所有的项,如图12.36所示。,图12.35 运行程序,图12.36 单击图像组合框,提醒:不仅需要为各项设置图像和标签,还要设置被选中时的图像,否则图像组合框的表现会比较奇怪,如图12.37所示。,图12.3
36、7 比较奇怪的图像组合框,(3)添加“获取当前的选择”按钮被单击的响应函数,如下:函数会获取聊友单击的项,并用信息对话框显示出来,如图12.38所示。,图12.38 单击按钮运行效果,12.5 聊天室服务器,聊天室服务器相当于“总管”,客户端发送的聊天消息都要被它提前处理,客户端接收的信息也全部来自服务器。这个“总管”维护着聊友的一切动态:进入、退出、公聊、私聊等等,12.5.1 聊天室服务器界面设计,创建基于对话框的应用程序,命名为Server,设计如图12.39所示对话框。,图12.39 对话框设计,列表框的样式为Report,文本框设置可以显示多行文本,会自动出现垂直滚动条,按钮是自绘类
37、型的,如图12.40所示。,图12.40 控件样式设置,为控件添加关联变量,变量类型和变量名如图12.41所示。,图12.41 添加关联变量,为工程导入事先做好的服务器背景和按钮位图,修改位图ID分别为:IDB_SBACK、“BTNRUNU“、“BTNRUND“和“BTNRUNX“,如图12.42和图12.43所示。,图12.42 服务器背景,图12.43 按钮位图,12.5.2 添加套接字类,(1)使用向导为工程添加新类CMySocket,继承于CSocket,如图12.44所示。,图12.44 添加新类CMySocket,为类CMySocket添加3个公有的成员变量,如下:并在类CMySo
38、cket的构造函数中完成初始化,如下:(2)重载继承于类CSocket的3个虚函数,如图12.45所示。,图12.45 重载父类虚函数,3个虚函数OnAccept()、OnClose()和OnReceive()被调用的时机分别是:有连接请求到来、连接关闭和有通信数据到达。在函数中拦截这些状态,然后发送消息到即将由我自己定义的处理函数中。3个重载函数中编写如下代码:(3)在类CMySocket的头文件中,定义消息SOCKET_EVENT和一些枚举常量,如下:为类添加公有成员函数AttachCWnd(),绑定socket和窗体,代码编写如下:,12.5.3 服务器功能实现,服务器开启时要绑定本地I
39、P和端口号,然后才能开始侦听来自客户端的连接,还要解析客户端发来的信息,了解意图后做出约定的反应。,1.设置背景和按钮位图,为类CServerDlg添加成员变量,如下:m_csList是临界区对象,用于修改列表控件项时独占列表控件,需要在类的头文件中加入包含临界区的头文件的命令。同时包含类CMySocket的头文件,如下: #include #include “MySocket.h“ 在对话框的初始化函数中,编写如下代码:函数设置了对话框背景、按钮图片和列表控件的列表头。这样还不足以让对话框的背景显示出来,为对话框添加消息WM_CTLCOLOR的响应函数,如图12.46所示。,图12.46 添
40、加消息响应函数,为响应函数OnCtlColor()添加代码,实现对话框背景图像的修改,如下:,2.开启服务器,当鼠标单击“开启服务器”按钮时启动聊天室服务器,按钮的响应函数编写如下:响应函数创建了socket,并且开始侦听来自任何IP的连接请求。,3.自定义消息的响应,(1)消息SOCKET_EVENT定义在类CMySocket的头文件中,在对话框中定义消息的处理函数,在对话框类CServerDlg头文件中声明处理函数,如下:在对话框类CServerDlg的实现文件中添加消息响应,如下:好了,现在编写处理函数OnSocket(),如下:响应消息SOCKET_EVENT的处理函数依据参数lPar
41、am判断是哪一类消息,然后交给不同的代码段或者函数来处理。,(2)函数ClosePlayer()是定义在类CServerDlg中的公有成员函数,用来关闭与退出聊天室聊友的连接,实现代码如下:函数ClosePlayer()功能的实现过程:依据传入的参数from,遍历列表控件上的列表项,按协议构造聊友退出信息,删除列表项后再给所有的剩余聊友发送聊友退出信息。 类CCriticalSection的成员函数Lock()用来获取对临界区的访问,Unlock()用来释放临界区对象。,类CListCtrl的成员函数GetItemText()用来获取列表项的文本,原型如下: int GetItemText(i
42、nt nItem,int nSubItem,LPTSTR lpszText,int nLen ) const; CString GetItemText(int nItem,int nSubItem ) const; 重载函数参数含义如下: nItem:列表控件列表项的索引号。,类CListCtrl的成员函数GetItemData(),用于获取列表项被应用程序指定的32位数值,原型如下: DWORD_PTR GetItemData(int nItem ) const; 参数nItem就是列表项的索引号。函数返回的是32位的数值。 (3)函数ParserPkt()同样定义在类CServerDlg中
43、,为公有成员,实现代码比较长,我们按功能分开来讲解,功能如图12.47所示。,图12.47 解析函数功能分解,接收到socket携带的信息后,首先判断是否为“聊友初次加入聊天室”信息,是的话就要获取聊友的姓名、IP、端口号、图像号和socket,并将这些信息插入到列表控件中。,类CAsyncSocket的成员函数GetPeerName(),用来获取建立连接的那端的IP和端口号,原型如下: BOOL GetPeerName(CString 参数含义如下: rPeerAddress:接收点分的IP地址组成的字符串。 函数返回非0,表示获取成功。,类CListCtrl的成员函数SetItemData
44、(),用于为指定列表项设置32位数值,原型如下: BOOL SetItemData(int nItem,DWORD_PTR dwData ); 参数含义如下: nItem:要设置的列表项。 函数返回非0,表示设置成功。,其次,判断如果是“聊友初次加入聊天室”信息,那么服务器还会向所有早加入聊天室的聊友原封不动的转发信息,然后给新加入的聊友发送已登录的聊友信息,代码如下:若解析信息,发现是私聊信息的话,按照信息提供的聊友名,遍历列表项找到socket连接,然后只转发消息的内容到私聊聊友就可以了,代码如下:若解析发现是公聊信息,需要向所有的聊友转发消息,在累计发送消息聊友的消息数目,代码如下:函数
45、Append()定义在CServerDlg中,用来维护服务器自己文本框消息的显示,消息包括:聊友加入、退出,聊友公聊的内容。函数的实现如下:即是将信息内容追加到文本框内容,然后显示出来。,12.6 聊天室客户端,客户端是聊友发信和收信的载体,可以通过它来了解:聊天室里来了哪些聊友,他们在讨论什么。也是通过它来实现:在聊天室里向所有人发言,或者和特定的聊友说些悄悄话。,12.6.1 聊天室客户端界面设计,(1)建立基于对话框的应用程序,命名为Client,主对话框即聊天窗体的设计如图12.48所示。,图12.48 聊天窗体界面设计,其中图像列表框样式为Small Icon,2个编辑框的样式都设置
46、为多行和自动垂直滚动条,按钮样式设置了自绘,如图12.49所示。,图12.49 控件样式设置,为控件添加关联变量,变量类型和变量名如图12.50所示。,图12.50 关联变量,(2)再添加一个对话框,修改ID为IDD_LOGIN,“登录”对话框界面设计如图12.51所示。,图12.51 “登录”对话框界面设计,为4个控件关联变量,变量类型和变量名如图12.52所示。,图12.52 关联变量,(3)为工程导入“登录”对话框和主对话框的背景,如图12.53所示,其中主对话框的背景是纯色的。,图12.53 “登录”对话框和主对话框背景图,修改背景图ID分别为:IDB_LOGBACK和IDB_CBAC
47、K。再导入“登录”和“发送”按钮的位图,如图12.54所示。,图12.54 按钮位图,12.6.2 添加套接字类,客户端同样需要添加新类CMySocket,同样继承于类CSocket,大部分和服务器端的类CMySocket相似,这小节就讲解那不同的地方。 客户端没有必要记录聊友名和消息条数,所以只保留了变量。因为客户端不会接收来自其他客户端的连接请求,所以不必重载函数OnAccept(),类CMySocket的头文件如下:其它的操作同12.5.2小节。包括:自定义消息SOCKET_EVENT、重载虚函数OnClose()和OnReceive()、发送消息SOCKET_EVENT给处理函数、使用
48、公有成员函数AttachCWnd()来实现socket和窗口的绑定。,12.6.3 客户端功能实现,客户端需要与服务器建立连接,发送不管是公聊还是私聊的信息,当然也要接收和解析服务器发来的一切信息,包括:新聊友的加入、旧聊友的退出、聊天室里大家公开说的话等等。,1.“登录”对话框,(1)为对话框关联基于CDialog的类,命名为CLogDlg,添加变量如下:(2)为对话框添加消息WM_INITDIALOG的响应函数OnInitDialog(),并且在函数中添加如下代码:初始化函数的功能实现过程:设置对话框背景、初始化图像列表并关联到图像组合框、预先为图像组合框插入了选项,最后设置图像组合框默认
49、选中第一项。,(3)为对话框添加消息WM_CTLCOLOR的响应函数OnCtlColor(),并在函数中添加如下代码:这样的话,对话框的背景就会被设置为我们事先载入的位图。 (4)添加按钮“登录”的单击响应函数OnLogin(),如下:函数将获取聊友填写的信息,然后保存在类的变量中。,2.主对话框背景和按钮位图设置,为主对话框类CClientDlg添加成员变量如下:在主对话框初始化函数OnInitDialog()中设置背景图像、按钮位图、为图像列表加载图像,如下:为主对话框添加消息WM_CTLCOLOR的响应函数OnCtlColor(),完成主对话框背景的绘制,如下:,3.校验登录信息,主对话框初始化函数OnInitDialog()会调用“登录”对话框,获取输入信息,校验输入信息,代码如下:这里用到了跳转语句goto,跳转的目标标签是tryagain。,4.连接聊天室服务器,初次发送信息,根据聊友填写的IP与服务器建立连接,构造聊友向服务器发送的第一条信息,即“初次加入聊天室”,代码如下:,