第4章 聊天系统——服务器端
在这一章,要实现一个聊天系统的服务器端。它与下一章将要介绍的聊天系统客户
端一起,够成了一个完整的聊天系统。该服务器是基于对话框的应用程序。它能够提供
强大的网络服务功能,比如建立侦字套接字,接受客户端连接,处理各种客户端请求,
向客户端发送消息和系统命令等功能。除提供网络服务外,它还提供用户信息的保存与
加载功能,系统消息的发送界面,用户状态的查看界面等一些必需的本地功能。
实际效果如图 4-1和 4-2所示。
图 4-1 聊天服务器实际效果图-1 图 4-2聊天服务器实际效果图-2
● 建立侦听套接字
● 建立连接套接字
● 接收与处理客户端请求
● 发送消息与系统命令
● 管理用户信息
● 使用静态文本框显示提示信息
● 动态隐藏与显示控件
● 使用列
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
框控件显示用户状态信息
4.1 窗体
设计
领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计
该聊天服务器是基于对话框的应用程序,它使用了较多的对话框控件资源。首先利
用 MFC AppWizard生成聊天服器的应用程序框架,其次利用 Visual C++提供的资源编
辑器为对话框添加控件资源。
4.1.1 建立应用程序框架
利用 MFC AppWizard生成应用计算器应用程序框架,具体步骤如下:
(1)执行 VC程序,选择 FILE|New命令,弹出 New对话框,单击 Projects选项卡,
Visual C++ 简明教程
选择 MFC AppWizard(exe)选项,然后在 Project Name文本框中输入 MyQQServer。
Location文本框是指项目的本地路径,这里读者可以自行设定。保持 Platform里的Win32
复选框不变。如图 4-2所示。
(2)单击 OK按钮,弹出 MFC AppWizard-Step1对话框。选择 Dialog Based单选
按钮。如图 4-3所示。
图4-2 New对话框 图 4-3 MFC AppWizard-Step1对话框
(3)单击Next按钮,弹出MFC AppWizard-Step2 of 4对话框。选中Windows Socket
复选框,这样 MFC AppWizard 会自动为应用程序框架添加网络功能所需的头文件。如
图 4-4所示。
(4)单击 Next按钮,弹出 MFC AppWizard-Step3 of 4对话框,如图 4-5所示。
图 4-4 MFC AppWizard-Step2对话框 图 4-5 MFC AppWizard-Step3对话框
(5)单击 Next按钮,弹出 MFC AppWizard-Step4 of 4对话框。如图 4-6所示。
点击 Finish按钮,创建框架。
第 4章 聊天系统——服务器端 • 3 •
图 4-6 MFC AppWizard-Step4对话框
4.1.2 添加对话框控件
利用 MFC AppWizard创建的对话框资源如图 4-7所示。
图 4-7 利用 MFC AppWizard创建的对话框资源
删除对话框资源上 ID 为 IDC_STATIC 的静态文本框,删除 ID 为 IDOK 的按钮和
ID为 IDCANCEL的按钮。
添加一个 ID 为 IDC_CONTROL_BAR 的 Picture 按钮。打开其属性对话框,在其
General选项卡上,将 Type下拉列表选择为 Frame项,将 Color下拉列表选择为White。
在其 Extended Styles选项卡上,选中 Client edge复选框。其效果如图 4-8所示。
图 4-8 添加 IDC_CONTROL_BAR资源
Visual C++ 简明教程
在图 4-8的基础上,继续为对话框添加四个按钮控件,ID分别为 IDC_SETSERVER,
IDC_SYSTEMMSG,IDC_STATUS,IDC_STOPSERVER,其标
题
快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题
与功能描述如表 4-1
所示。
表 4-1 添加按钮控件清单
按钮 ID 按钮标题 按钮功能描述
IDC_SETSERVER 启动服务 启动服务器
IDC_SYSTEMMSG 系统消息 向客户端发送系统消息
IDC_STATUS 连接状态 查看所有用户连接状态
IDC_STOPSERVER 停止服务 停止服务器
再为对话框添加四个静态文本控件,其 ID 分别设为 IDC_MSG1,IDC_MSG2,
IDC_MSG3,IDC_MSG4,这四个静态文本控件用来向用户显示必要的提示信息,比如
服务器运行状态等。
添加完按钮控件与静态文本控件的对话框资源如图 4-9所示。
图 4-9 添加按钮控件与静态文本控件
最后,再为对话框添加一个编辑控件 IDC_MSG_BOX 和一个列表控件 IDC_
STATUSLIST。
打开编辑控件 IDC_MSG_BOX的属性对话框,选择 Styles选项卡,选中 MultiLines
复选框,Want Return复选框,ReadOnly复选框。选对 Extends Styles选项卡,选中 Client
edge复选框。其 Styles选项卡设置如图 4-10所示。
图 4-10 编辑控件属性对话框 Styles选项卡
第 4章 聊天系统——服务器端 • 5 •
打开列表控件 IDC_ STATUSLIST 的属性对话框,选择 Styles选项卡,在 View下
拉列表中选择 Report 项,在 Align 下拉列表中选择 Top 项,在 Sort 下拉列表中选择
Ascending项,选中 Single Selection复选框。并在 Extends styles中选中 Client edge复选
框。其 Styles选项卡设置如图 4-11所示。
图 4-11 列表控件属性对话框 Styles选项卡
所有控件资源添加完毕后的对话框如图 4-12所示。
图 4-12 所有控件资源添加完毕后的对话框
因为编辑控件 IDC_MSG_BOX和列表控件 IDC_STATUSLIST位置完全重合,所以
在图 4-12 中看不到编辑控件 IDC_MSG_BOX。之所以让两个控件的位置完全重合,是
为了满足程序的需求。读者可以在后面的章节中看到这一简单界面技巧的应用。
4.2 建立侦听套接字类
利用 Class Wizard添加一个新类 CListenSocket。如图 4-13所示。
Visual C++ 简明教程
图 4-13 添加 CListenSocket类
CListenSocket类是的父类是 CAsyncSocket类。CListenSocket类将用于建立侦听套
接字。
4.2.1 添加成员变量
为 CListenSocket类添加一个的私有成员变量 m_wndParent,用于保存 CListenSocket
类的主窗口指针。其声明如下:
class CMyQQServerDlg; //声明 CMyQQServerDlg类
class CListenSocket : public CAsyncSocket
{
… …
// 成员变量
private:
CMyQQServerDlg * m_wndParent;
… …
}
4.2.2 添加成员函数
利用 ClassWizard为 CListenSocket类添加一个成员函数 OnAccept。该成员函数将覆
盖其父类 CAsyncSocket的 OnAccept函数。如图 4-14所示。
图 4-14 添加成员函数 OnAccpet
OnAccept 函数是 CAsyncSocket 类的虚函数。当侦听套接字接收到连接请求的时,
OnAccept函数会被自动调用。
第 4章 聊天系统——服务器端 • 7 •
在新添加的 OnAccept函数中,调用主窗口类的同名函数 OnAccept。即当侦听套接
字接收到连接请求时,其主窗口类中的 OnAccept就会被调用。其实现代码如下:
void CListenSocket::OnAccept(int nErrorCode)
{
CAsyncSocket::OnAccept(nErrorCode);
if(m_wndParent) { //判断指针是否为空
m_wndParent->OnAccept(); //调用主窗口的同名函数 OnAccept
}
}
主窗口类的指针被保存在 m_wndParent中,而为了增强程序的封装性,m_wndParent
被设置为私有成员变量。因此,需要再为 CListenSocket类添加一个成员函数 SetParent,
用来设置 m_wndParent的值。其函数声明如下:
class CListenSocket : public CAsyncSocket
{
… …
// 成员函数
public:
void SetParent(CMyQQServerDlg * pParent);
… …
}
在函数 SetParent中,设定指针 m_wndParent的值。其实现代码如下:
void CListenSocket::SetParent(CMyQQServerDlg *pParent)
{
m_wndParent = pParent;
}
4.3 建立连接套接字类
利用 Class Wizard添加一个新类 CClientSocket。如图 4-15所示。
图 4-15 添加 CClientSocket类
CClientSocket 类是的父类是 CAsyncSocket 类。CClientSocket 类将用于建立与客户
Visual C++ 简明教程
端套接字的连接。
4.3.1 添加成员变量
为 CListenSocket 类添加两个的私有成员变量,分别为 UINT 型变量 m_UserID 和
CMyQQServerDlg 型指针变量 m_wndParent。m_UserID 用于保存与此套接字相连接的
用户的 ID。wndParent 变量用来保存使用 CListenSocket 对象的主窗口指针。其声明如
下:
class CMyQQServerDlg; //声明 CMyQQServerDlg类
class CClientSocket : public CAsyncSocket
{
… …
//变量声明
private:
CMyQQServerDlg * m_wndParent; //保存主窗口指针
UINT m_UserID; //保存用户 ID
… …
}
4.3.2 添加成员函数
利用 ClassWizard 为 CClientSocket 类添加一个成员函数 OnReceive。该成员函数将
覆盖其父类 CAsyncSocket的 OnReceive函数。如图 4-16所示。
图 4-16 添加 OnReceive函数
OnReceive函数是 CAsyncSocket类的虚函数,当连接套接字接收到与之相对应的连
接套接字发送的讯息时,OnReceive函数就会被自动调用。
在新添加的 OnReceive 函数中,调用主窗口类的同名函数 OnReceive。即当连接套
接字接收到连接请求时,其主窗口类中的同名函数 OnReceive就会被调用。其实现代码
如下:
void CClientSocket::OnReceive(int nErrorCode)
{
if(m_wndParent) { //判断主窗口指针是否为空
m_wndParent->OnReceive(this); //调用主窗口的同名函数 OnReceive
}
第 4章 聊天系统——服务器端 • 9 •
CAsyncSocket::OnReceive(nErrorCode);
}
主窗口类的指针被保存在 m_wndParent中,而为了增强程序的封装性,m_wndParent
被设置为私有成员变量。因此,需要再为 CClientSocket类添加一个成员函数 SetParent,
用来设置 m_wndParent的值。其函数声明如下:
class CClientSocket: public CAsyncSocket
{
… …
// 成员函数
public:
void SetParent(CMyQQServerDlg * pParent);
… …
}
在函数 SetParent中,设定指针 m_wndParent的值。其实现代码如下:
void CListenSocket::SetParent(CMyQQServerDlg *pParent)
{
m_wndParent = pParent;
}
CClientSocket类的另一个成员变量 m_UserID也被声明为私有变量。因此,需要再
为 CClientSocket 添加一个成员函数 SetUser,用来设置 m_UserID 的值。其函数声明如
下:
class CClientSocket: public CAsyncSocket
{
… …
// 成员函数
public:
void SetUser(UINT pUserID);
… …
}
在 SetUser中,设定私有成员变量 m_UserID的值。其实现代码如下:
//设定与此套接字相连接的用户的 ID
void CClientSocket::SetUser(UINT piUser)
{
m_UserID = piUser;
}
4.4 实现网络服务功能
该聊天服务器最核心的功能在于其能够提供的强大的网络服务。它能够建立侦听套
接字,能够接受客户端的请求,能够处理各种客户端请求以及向客户端发送相应的消息
与命令。下面就利用已经建立的侦听套接字类 CListenSocket 和连接套接字类
CClientSocket来实现上述的功能。
Visual C++ 简明教程
4.4.1 公共数据结构与通信协议
服务器端与客户端的连接是利用Windows套接字及网络连接来实现的,但是服务器
端与客户端能够进行良好的通信,能够发送各种命令与请求则是依赖于服务器和客户端
之间的公共数据结构与协议。
1. 公共数据结构
服务器与客户端采用的公共数据结构有两个。一个是消息包的描述结构
SServerMsg,另一个是用户信息的描述结构 SUserInfo。
SServerMsg在 CMyQQServerDlg.h文件中声明如下:
//消息包描述结构
struct SServerMsg
{
UINT m_msgType; //消息类型
char m_msgBuff[2048]; //消息内容
};
SServerMsg结构体包含两部分:m_msgType表示消息类型,m_msgBuff为一个 2048
个字节的缓冲区,用来存放消息内容。
SUserInfo在 CMyQQServerDlg.h文件中声明如下:
//用户信息描述结构
struct SUserInfo
{
UINT m_infoID; //用户帐号
char m_infoNick[64]; //用户昵称
char m_infoName[64]; //用户姓名
UINT m_infoPSW; //用户密码
UINT m_infoAge; //用户年龄
UINT m_infoSex; //用户性别
UINT m_infoPicture; //用户头像
UINT m_infoIsOnLine; //用户在线状态
UINT m_infoFriendList[64]; //好友名单,最多可设定 64位好友
UINT m_infoBlackList [16]; //黑名单,最多可设定 16位黑名单
//网络相关信息
char m_infoAddr[20]; //用户 IP地址
UINT m_infoPort; //用户端口
void * m_infoSocket; //用户服务器端的 SOCKET指针
UINT m_infoTime; //记录用户上次保持连接的时间
};
SUserInfo 结构体中包含了用户的全部信息,比如帐号、昵称、密码等,而且包含
了服务器与客户端,客户端与客户端通信所需的其本网络信息。
第 4章 聊天系统——服务器端 • 11 •
2. 通信协议
服务器端与客户端要能够进行良好的通信,要能够互相响应对方的请求,那么服务
器与客户端必定要有一套完整的协议。这样,服务器端与客户端才能
分析
定性数据统计分析pdf销售业绩分析模板建筑结构震害分析销售进度分析表京东商城竞争战略分析
对方的请求,
并作出相应的动作。
服务器和客户端的协议体现在 SServerMsg结构体的 m_msgType变量上,该变量是
一个 UINT 型变量。变量 m_msgType 在服务器和客户端都用来识别消息类型,以作出
不同的响应。本例程涉及服务器向客户端发送命令和响应客户端请求两个方面,因此必
须对这两个方面都加以定义。
服务器可识别的客户端命令有五种,分别为用户注册请求,用户登陆请求,用户下
线请求,下载用户信息请求,连接保持请求。消息类型的值与其含义对应况如表 4-2所
示:
表 4-2 服务器可识别的客户端命令
消息类型标识 含义
000 用户注册请求
001 用户登陆请求
002 用户下线请求
003 下载用户信息请求
004 连接保持请求
005 修改用户信息
服务器可发送给客户端的消息有 11 种,分别为服务器关闭消息,连线成功消息,
用户下线成功消息,保持连接消息,系统消息,注册成功消息,未注册帐户消息,密码
错误消息,登陆成功消息,用户信息消息,用户资料修改成功消息。
消息类型的值与其含义对应情况如表 4-3所示:
表 4-3 服务器可发送给客户端的消息
消息类型标识 含义
000 服务器关闭消息
001 连线成功消息
002 用户下线成功消息
003 保持连接消息
004 系统消息
005 注册成功消息
006 未注册帐户消息
007 密码错误消息
008 登陆成功消息
009 用户信息消息
010 用户资料修改成功
Visual C++ 简明教程
3. 私有成员变量
服务器的主窗口为 CMyQQServerDlg,所有的操作都是基于此类完成的。为了实现
网络服务的功能,必须为 CMyQQServerDlg类添加若干私有成员变量。
添加的私有成员变量的类型、ID及用途如表 4-4所示:
表 4-4 CMyQQServerDlg类主要私有成员变量清单
变量类型 变量标识 用途
CListenSocket m_listenSocket 唯一的侦听套接字
CList m_lstSocke 保存所有用户的连接套接字
CList m_lstAllUser 保存所有用户的用户信息
UINT m_MAXID 当前已注册的最大用户 ID
UINT m_iActiveUser 当前在线用户总数
UINT m_iTotalUser 当前所有注册用户总数
CString m_strServerIP 服务器 IP地址
UINT m_iServerPort 服务器端口
BOOL m_IsServerOn 服务器开启标识
4.4.2 初始化主程序对话框
该聊天服务器的主窗口为 CMyQQServerDlg 对话框。CMyQQServerDlg 的父类为
CDialog类。利用 ClassWizard为 CMyQQServerDlg类添加消息响应函数 OnInitDialog。
如图 4-17所示。
图 4-17 添加消息响应函数 OnInitDialog
函数 OnInitDialog 是消息 WM_INITDIALOG 的响应函数。当对话框初始化时,该
消息就会被触发,框架就会自动调用其响应函数 OnInitDialog。因此,可以在此函数中
进与对话框初始相关的操作。
在 OnInitDialog 函中,为列表控件 IDC_STATUSLIST 添加表头,并且初始化所有
控件。根据服务器开启状况显示和隐藏或允许和禁止相应控件。并设置相关提示信息。
其实现代码如下:
//初始化主对话框 CMyQQServerDlg
BOOL CMyQQServerDlg::OnInitDialog()
第 4章 聊天系统——服务器端 • 13 •
{
CDialog::OnInitDialog();
… …
//列表框控件的表头信息描述
LV_COLUMN lvColumn;
lvColumn.mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
lvColumn.fmt=LVCFMT_LEFT;
lvColumn.cx=100;
//加入第一列,帐号
lvColumn.iSubItem=0;
lvColumn.pszText="帐号";
m_listStatus.InsertColumn(0,&lvColumn);
//加入第二列,昵称
lvColumn.iSubItem=1;
lvColumn.pszText="昵称";
m_listStatus.InsertColumn(1,&lvColumn);
//加入第二列,姓名
lvColumn.iSubItem=2;
lvColumn.pszText="姓名";
m_listStatus.InsertColumn(2,&lvColumn);
//加入第二列,状态
lvColumn.iSubItem=3;
lvColumn.pszText="状态";
m_listStatus.InsertColumn(3,&lvColumn);
// 判断服务器是否开启
if(!m_IsServerOn) {
m_listenSocket.SetParent(this); //整个系统唯一的侦听套接字
//
m_MsgBox.SetWindowText("\n聊天服务器已关闭!\n");
m_StopServer.ShowWindow(SW_HIDE);
m_btnSendSysMsg.ShowWindow(SW_HIDE);
m_MsgBox.ShowWindow(SW_SHOW);
m_listStatus.ShowWindow(SW_HIDE);
m_editSystemMsg.ShowWindow(SW_HIDE);
//将提示信息置空
m_wndMsg1.SetWindowText("");
m_wndMsg2.SetWindowText("");
m_wndMsg3.SetWindowText("");
Visual C++ 简明教程
m_wndMsg4.SetWindowText("");
m_btnSystemMsg.EnableWindow(FALSE); //禁止按钮 IDC_SYSTEMMSG
m_btnStatus.EnableWindow(FALSE); //禁止按钮 IDC_STATUS
}
else {
//更新用户界面控件
m_StopServer.ShowWindow(SW_SHOW); //显示按钮 IDC_STOPSERVER
m_StopServer.SetWindowText(“关闭服务器”); //按钮设置 IDC_STOPSERVER标题
m_btnSystemMsg.EnableWindow(TRUE); // 允许 IDC_SYSTEMMSG
m_MsgBox.ShowWindow(SW_SHOW); //显示 IDC_MSG_BOX
m_btnStatus.EnableWindow(TRUE); //允许按钮 IDC_STATUS
m_listStatus.ShowWindow(SW_HIDE);//隐藏列表 IDC_STATUSLIST
CString strMsg;
//更新提示信息 1
strMsg = "服务器地址:";
m_wndMsg1.SetWindowText(strMsg + m_strServerIP);
//更新提示信息 2
strMsg.Format("%s%d","服务器端口:",m_iServerPort);
m_wndMsg2.SetWindowText(strMsg);
//更新提示信息 3
strMsg = "服务器开启时间:";
m_wndMsg3.SetWindowText(strMsg);
//更新提示信息 4
strMsg.Format("用户总数:%d\t\t在线人数:%d",m_iTotalUser,m_iActiveUser);
m_wndMsg4.SetWindowText(strMsg);
//更新控件 IDC_MSGBOX
m_MsgBox.SetWindowText("\n聊天服务器已启动!\n");
}
return TRUE; // return TRUE unless you set the focus to a control
}
4.4.3 初始化服务器
利用 ClassWizard 为按钮 IDC_SETSERVER 添加响应函数 OnSetserver。如图 4-17
第 4章 聊天系统——服务器端 • 15 •
所示。
图 4-17 添加按钮响应函数 OnSetserver
当用户单击按钮 IDC_SETSERVER时,函数 OnSetserver会被框架自动调用。在函
数 OnSetserver中进行启动服务器的操作。
在 OnSetserver函数中,判断服务器是否已经开启。
若服务器未开启,则首先创建侦听套接字,建立侦听。其次,加载用户信息。再次,
调整用户界面,显示与隐藏相应控件。最后,更新所有提示信息,并将服务器开启标识
m_IsServerOn设置为 TRUE。
若服务器已开启,则调整用户界面,显示与隐藏相应控件,并更新所有提示信息。
其实现代码如下:
//启动服务器
void CMyQQServerDlg::OnSetserver()
{
// 判断服务器是否已经打开
if(!m_IsServerOn) {
if(!m_listenSocket.Create(m_iServerPort)) { //创建侦听套接字
m_MsgBox.SetWindowText("\n聊天服务器启动失败——套接字创建错误!\n");
return ;
}
if(!m_listenSocket.Listen(5)) { //建立侦听
m_MsgBox.SetWindowText("\n聊天服务器启动失败——侦听错误!\n");
return ;
}
if(!LoadFromFile()) { //加载用户信息
m_MsgBox.SetWindowText("\n加载用户信息失败!\n");
}
//更新用户界面控件
m_StopServer.ShowWindow(SW_SHOW); //显示按钮 IDC_STOPSERVER
m_StopServer.SetWindowText(“关闭服务器”); //按钮设置 IDC_STOPSERVER标题
m_btnSystemMsg.EnableWindow(TRUE); // 允许 IDC_SYSTEMMSG
Visual C++ 简明教程
m_MsgBox.ShowWindow(SW_SHOW); //显示 IDC_MSG_BOX
m_btnStatus.EnableWindow(TRUE); //允许按钮 IDC_STATUS
m_listStatus.ShowWindow(SW_HIDE);//隐藏列表 IDC_STATUSLIST
m_editSystemMsg.ShowWindow(SW_HIDE);
m_btnSendSysMsg.ShowWindow(SW_HIDE);
CString strMsg;
//更新提示信息 1
strMsg = "服务器地址:";
m_wndMsg1.SetWindowText(strMsg + m_strServerIP);
//更新提示信息 2
strMsg.Format("%s%d","服务器端口:",m_iServerPort);
m_wndMsg2.SetWindowText(strMsg);
//更新提示信息 3
strMsg = "服务器开启时间:";
m_wndMsg3.SetWindowText(strMsg);
//更新提示信息 4
strMsg.Format("用户总数:%d\t\t在线人数:%d",m_iTotalUser,m_iActiveUser);
m_wndMsg4.SetWindowText(strMsg);
//更新控件 IDC_MSGBOX
m_MsgBox.SetWindowText("\n聊天服务器已启动!\n");
m_IsServerOn = TRUE; //修改服务器开启标识
}
else { //如果服务器已经开启
//更新用户界面控件
m_StopServer.ShowWindow(SW_SHOW); //显示按钮 IDC_STOPSERVER
m_StopServer.SetWindowText(“关闭服务器”); //按钮设置 IDC_STOPSERVER标题
m_btnSystemMsg.EnableWindow(TRUE); //允许 IDC_SYSTEMMSG
m_MsgBox.ShowWindow(SW_SHOW); //显示 IDC_MSG_BOX
m_btnStatus.EnableWindow(TRUE); //允许按钮 IDC_STATUS
m_listStatus.ShowWindow(SW_HIDE); //隐藏列表 IDC_STATUSLIST
CString strMsg;
//更新提示信息 1
strMsg = "服务器地址:";
m_wndMsg1.SetWindowText(strMsg + m_strServerIP);
第 4章 聊天系统——服务器端 • 17 •
//更新提示信息 2
strMsg.Format("%s%d","服务器端口:",m_iServerPort);
m_wndMsg2.SetWindowText(strMsg);
//更新提示信息 3
strMsg = "服务器开启时间:";
m_wndMsg3.SetWindowText(strMsg);
//更新提示信息 4
strMsg.Format("用户总数:%d\t\t在线人数:%d",m_iTotalUser,m_iActiveUser);
m_wndMsg4.SetWindowText(strMsg);
//更新控件 IDC_MSGBOX
m_MsgBox.SetWindowText("\n聊天服务器已启动!\n");
}
}
4.4.4 建立侦听套接字
在 CMyQQServerDlg类中,添加 CListenSocket类型的私有成员变量 m_listenSocket。
变量 m_listenSocket 是整个聊天服务器系统唯一的侦听套接字。侦听套接字一经创建,
可以指定端口侦听客户端的连接。
在 CMyQQServerDlg类中,添加 SetListenSocket成员函数。在函数 SetListenSocket
中,建立侦听套接字。
其实现代码如下:
//建立侦听套接字
BOOL CMyQQServerDlg::SetListenSocket()
{
if(!m_listenSocket.Create(m_iServerPort)) { //创建侦听套接字
m_MsgBox.SetWindowText("\n聊天服务器启动失败——套接字创建错误!\n");
return FALSE;
}
if(!m_listenSocket.Listen(5)) { //建立侦听
m_MsgBox.SetWindowText("\n聊天服务器启动失败——侦听错误!\n");
return FALSE;
}
return TRUE; //
}
4.1.5 接受客户端连接
在 CMyQQServerDlg 类中,添加成员函数 OnAccept。当侦听套接字接收到客户连
Visual C++ 简明教程
接请求时,此函数将会被调用。
在 OnAccept 函数中,首先设置连接套接字的父窗口指针,接着调用连接套接字中
的 Accept 函数接受客户端连接请求,并将用来接受客户端求的边接套接字保存在
m_listenSocket链表中。
其实现代码如下:
//接受客户端连接请求
void CMyQQServerDlg::OnAccept()
{
CClientSocket * m_clientSocket = new CClientSocket;
m_clientSocket->SetParent(this); //设置连接套接字的父窗口指针
m_listenSocket.Accept(*m_clientSocket); //接受客户端请求
m_lstSocket.AddTail(m_clientSocket); //
SServerMsg msg;
msg.m_msgType = 001; //001,连接成功
sprintf(msg.m_msgBuff,"%s","服务器运行正常!");
//发送服务器消息,通知客户端连接成功
m_clientSocket->Send(&msg,sizeof(SServerMsg));
}
4.1.6 处理各种客户端请求
聊天服务器最核心的服务功能就体现在它能够处理各种客户端请求,比如响应用户
的注册、上线、离线等等。在表 4-2中列出了聊天服务器能处理的各种客户端请求。在
表 4-3中列出了聊天服务器能向客户端发送的各种消息。下面就来实现聊天服务器对各
种客户端请求的处理功能。
当服务器端的连接套接字接收到客户端发来信息时,其成员函数 OnReceive会被自
动调用。连接套接字的 OnReceive函数会调用主窗口 CMyQQServerDlg类中的同名函数
OnReceive。因此,所有由客户端发送到服务器端的消息实际上都是在 CMyQQServerDlg
类中的 OnReceive函数中被处理的。
在 OnRecive函数中,调用连接套接写的 Receive函数,接收用客户端信息到缓冲区
中。当接收到客户端发送的信息后,则对 SServerSmg结构体中的 m_msgType域的值进
行判断,根据不同的 m_msgType的值,选择进行不同的操作以响应用户的请求。
其实现代码如下:
void CMyQQServerDlg::OnReceive(CClientSocket *pClientSocket)
{
SServerMsg msg; //用于接收客户端信息的缓冲区
SUserInfo p; //用于保存用户信息的缓冲区
SUserInfo * info; //用户信息指针
CString strMsg; //临时字符串类对象
int count = 0; //计数器
int i = 0; //临时变量
第 4章 聊天系统——服务器端 • 19 •
//接受客户端发送的信息
pClientSocket->Receive(&msg,sizeof(SServerMsg));
//对信息类型进行判断
switch (msg.m_msgType)
{
case 0: //000 用户注册
… … //执行处理用户注册的操作
break;
case 1: //001 用户登陆
… … //执行处理用户登陆的操作
break;
case 2: //002 用户下线
… … //执行处理用户下线的操作
break;
case 3: //003 下载用户信息
… … //执行处理下载用户信息的请求的操作
break;
case 4: //004 保持连接
… … //执行处理客户端保持连接的请求的操作
break;
case 5: //005 修改资料请求
… … //执行处理修改资料请求的操作
break;
}
}
}
下面就在 switch语句的每一条 case分支语句中,分别处理相应的客户请求。
1. 处理用户注册请求
当信息类型标识 msg.m_msgType 值为 0 时,说明此条信息为用户注册请求,服务
器需要执行处理用户注册请求的操作。
在处理用户注册请求时,首先将数据缓冲区 msg.m_msgBuff 中的信息强型转化成
SUserInfo类型,将信息类型标识 msg.m_msgType修改为 5,表示注册已经成功。其次,
调用函数 GenerateID为该用户生成一个唯一的用户 ID,将其赋值给 info->m_infoID,并
且修改用户在线标识 info->m_infoIsOnLine 为 1,表示用户已上线,将新注册的用户追
加到用户链表 m_lstAllUser尾部。
其实现代码如下:
//处理用户注册请求
void CMyQQServerDlg::OnReceive(CClientSocket *pClientSocket)
{
… …
//接受客户端发送的信息
pClientSocket->Receive(&msg,sizeof(SServerMsg));
Visual C++ 简明教程
//对信息类型进行判断
switch (msg.m_msgType)
{
case 0: //000 用户注册
msg.m_msgType = 5;
info = (SUserInfo*)(msg.m_msgBuff);
info->m_infoID = GenerateID(); //产生 ID
info->m_infoIsOnLine = 1;
m_lstAllUser.AddTail(*info);
m_iTotalUser ++;
m_iActiveUser ++;
strMsg.Format("用户总数:%d\t\t在线人数:%d",m_iTotalUser,m_iActiveUser);
m_wndMsg4.SetWindowText(strMsg);
pClientSocket->SetUser(info->m_infoID);
pClientSocket->Send(&msg,sizeof(SServerMsg));
break;
case 1: //001 用户登陆
… … //执行处理用户登陆的操作
break;
case 2: //002 用户下线
… … //执行处理用户下线的操作
break;
case 3: //003 下载用户信息
… … //执行处理下载用户信息的请求的操作
break;
case 4: //004 保持连接
… … //执行处理客户端保持连接的请求的操作
break;
case 5: //005 修改资料请求
… … //执行处理修改资料请求的操作
break;
}
}
2. 处理用户登陆请求
当信息类型标识 msg.m_msgType 值为 1 时,说明此条信息为用户登陆请求,服务
器需要执行处理用户登陆请求的操作。
在处理用户登陆请求时,首先将数据缓冲区 msg.m_msgBuff 中的信息强型转化成
SUserInfo类型。遍历用户列表,查找与此用户 ID相同的元素。
第 4章 聊天系统——服务器端 • 21 •
若在用户列表中找到与此用户 ID 相同的元素,则检验其密码是否正确。如果密码
正确,则向客户端发送登陆成功消息。如果密码错误,则向用户发送密码错误消息。
若在用户列表中没有找到与此用户 ID 相同的元素,则向客户端发送未注册帐户消
息。
其实现代码如下:
//处理用户登陆请求
void CMyQQServerDlg::OnReceive(CClientSocket *pClientSocket)
{
… …
//接受客户端发送的信息
pClientSocket->Receive(&msg,sizeof(SServerMsg));
//对信息类型进行判断
switch (msg.m_msgType)
{
case 0: //000 用户注册
… … //执行处理用户注册的操作
break;
case 1: //001 用户登陆
info = (SUserInfo*)(msg.m_msgBuff); //将缓冲区内容强行转化为 SUserInfo类型
count = m_lstAllUser.GetCount(); //得到用户链表中的很户数量
i = 0;
//遍历用户链表,查找指定 ID
while( i < count ) { //查找用户
p = (SUserInfo)(m_lstAllUser.GetAt(m_lstAllUser.FindIndex(i)));
if(p.m_infoID == info->m_infoID) { //如果找到,则跳出循环
break;
}
i++ ; //计数器加 1
}
if( i < count ) { //判断查找指定 ID是否成功
if(p.m_infoPSW == info->m_infoPSW) { //密码正确
//为当前连接套接字设置用户 ID
pClientSocket->SetUser(p.m_infoID); //
p.m_infoIsOnLine = 1; //修改在线标识
//插入修改后的新元素
m_lstAllUser.InsertAfter(m_lstAllUser.FindIndex(i),p);
//删除原来的旧元素
m_lstAllUser.RemoveAt(m_lstAllUser.FindIndex(i));
msg.m_msgType = 8; //登陆成功
memcpy(info,&p,sizeof(SUserInfo)); //准备消息内容
Visual C++ 简明教程
pClientSocket->Send(&msg,sizeof(SServerMsg)); //发送消息
m_iActiveUser ++; //当前在线用户为数加 1
strMsg.Format("用户总数:%d\t\t在线人数:%d",
m_iTotalUser,m_iActiveUser);
//重新设置提示信息
m_wndMsg4.SetWindowText(strMsg);
}
else //密码错误
{
msg.m_msgType = 7; //密码错误
pClientSocket->Send(&msg,sizeof(SServerMsg)); //发送信息
}
}
else {//查无此 ID
msg.m_msgType = 6; //未注册用户
pClientSocket->Send(&msg,sizeof(SServerMsg)); //发送信息
}
break;
case 2: //002 用户下线
… … //执行处理用户下线的操作
break;
case 3: //003 下载用户信息
… … //执行处理下载用户信息的请求的操作
break;
case 4: //004 保持连接
… … //执行处理客户端保持连接的请求的操作
break;
case 5: //005 修改资料请求
… … //执行处理修改资料请求的操作
break;
}
}
3. 处理用户下线请求
当信息类型标识 msg.m_msgType 值为 2 时,说明此条信息为用户下线请求,服务
器需要执行处理用户下线请求的操作。
在处理用户登陆请求时,首先将数据缓冲区 msg.m_msgBuff 中的信息强型转化成
SUserInfo类型。遍历用户列表,查找与此用户 ID相同的元素。
若在用户列表中找到与此用户 ID 相同的元素,将用户的在线标识 m_infoIsOnLine
设为 0,表示此用户已离线。将 msg.m_msgType修改为 2,表示用户离线成功。向用户
发送离线成功消息。关闭套接字,更新界面提示信息。
若在用户列表中没有找到与此用户 ID 相同的元素,则向客户端发送未注册帐户消
息。
第 4章 聊天系统——服务器端 • 23 •
其实现代码如下:
//处理用户下线请求
void CMyQQServerDlg::OnReceive(CClientSocket *pClientSocket)
{
… …
/