文章目录
一、实验目的
- 掌握MFC套接字编程方法
- 借助VS2016的MFC应用程序向导创建程序框架;
- 从CSocket类派生用户自定义的套接字类;
- 通过CArchive类、CSocketFile类、CSocket类实现网络数据交换。
二、实验环境
- 操作系统:WINDOWS 7及以上
- 开发工具:Microsoft VisualBasic6.0
- 实验设备:PC
三、实验内容
MFC框架下设计一个聊天室服务器和多个聊天室客户机,实现多客户机并发的群聊功能,在服务器端需要用链表动态管理与客户机连接的套接字,实时更新服务器和客户机群的界面显示,其基本功能如下:
1.要求服务器能与多个客户机建立连接,同时为多个客户机服务。
2.服务器相当于聊天室大厅,它发布所有客户机的发言,并将客户机发言转发给其他客户机,从而间接实现客户机之间的通信。
3.服务器动态统计进入聊天室的客户机数目,当有新客户机加入或退出时,试试更新在线客户数量。
客户机
Client.h:
// Client.h : PROJECT_NAME 应用程序的主头文件
#pragma once
#ifndef __AFXWIN_H__
#error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件"
#endif
#include "resource.h" // 主符号
class CClientApp : public CWinApp
{
public:
CClientApp();
public:
virtual BOOL InitInstance();
DECLARE_MESSAGE_MAP()
};
extern CClientApp theApp;
Client.cpp:
// Client.cpp : 定义应用程序的类行为。
//
#include "stdafx.h"
#include "Client.h"
#include "ClientDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CClientApp
BEGIN_MESSAGE_MAP(CClientApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
// CClientApp 构造
CClientApp::CClientApp()
{
// 支持重新启动管理器
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
// TODO: 在此处添加构造代码,
// 将所有重要的初始化放置在 InitInstance 中
}
// 唯一的一个 CClientApp 对象
CClientApp theApp;
// CClientApp 初始化
BOOL CClientApp::InitInstance()
{
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// 创建 shell 管理器,以防对话框包含
// 任何 shell 树视图控件或 shell 列表视图控件。
CShellManager *pShellManager = new CShellManager;
// 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
CClientDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用
// “确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
// TODO: 在此放置处理何时用
// “取消”来关闭对话框的代码
}
else if (nResponse == -1)
{
TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\n");
TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
}
// 删除上面创建的 shell 管理器。
if (pShellManager != NULL)
{
delete pShellManager;
}
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
// 而不是启动应用程序的消息泵。
return FALSE;
}
ClientDlg.h:
// ClientDlg.h : 头文件
//
#pragma once
#include "ClientSocket.h" //手动添加包含语句
// CClientDlg 对话框
class CClientDlg : public CDialogEx
{
public:
CClientDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_CLIENT_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
//以下代码通过类向导添加
CString m_strServerName;
int m_nServerPort;
CString m_strSpeaking;
CString m_strUserName;
CListBox m_listCRoom;
afx_msg void OnClickedButtonLogin();
afx_msg void OnClickedButtonLogout();
afx_msg void OnClickedButtonSpeak();
afx_msg void OnDestroy();
CClientSocket* m_pSocket;
CSocketFile* m_pFile;
CArchive* m_pArchiveIn;
CArchive* m_pArchiveOut;
void onReceive(void);
void ReceiveMessage(void);
void SendMyMessage(CString& strMessage,BOOL bClosed);
};
ClientDlg.cpp:
// ClientDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "Client.h"
#include "ClientDlg.h"
#include "afxdialogex.h"
#include "ClientSocket.h"//手动添加包含语句
#include "Message.h"
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// 对话框数据
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CClientDlg 对话框
CClientDlg::CClientDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CClientDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
//类向导自动添加的初始化代码
m_strServerName = _T("");
m_nServerPort = 0;
m_strSpeaking = _T("");
m_strUserName = _T("");
//手动添加的初始化代码
m_pSocket=NULL;
m_pFile=NULL;
m_pArchiveIn=NULL;
m_pArchiveOut=NULL;
}
void CClientDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_SERVERNAME, m_strServerName);
DDX_Text(pDX, IDC_EDIT_SERVERPORT, m_nServerPort);
DDV_MinMaxInt(pDX, m_nServerPort, 1024, 49151);
DDX_Text(pDX, IDC_EDIT_SPEAKING, m_strSpeaking);
DDX_Text(pDX, IDC_EDIT_USERNAME, m_strUserName);
DDX_Control(pDX, IDC_LIST_CROOM, m_listCRoom);
}
BEGIN_MESSAGE_MAP(CClientDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_LOGIN, &CClientDlg::OnClickedButtonLogin)
ON_BN_CLICKED(IDC_BUTTON_LOGOUT, &CClientDlg::OnClickedButtonLogout)
ON_BN_CLICKED(IDC_BUTTON_SPEAK, &CClientDlg::OnClickedButtonSpeak)
ON_WM_DESTROY()
END_MESSAGE_MAP()
// CClientDlg 消息处理程序
BOOL CClientDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
//手动添加如下初始化代码:
m_strUserName=_T("I024Ha");
m_strServerName=_T("localhost");
m_nServerPort=8888;
UpdateData(FALSE);//更新对应控件数据
GetDlgItem(IDC_EDIT_SPEAKING)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_LOGOUT)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_SPEAK)->EnableWindow(FALSE);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CClientDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标显示。
HCURSOR CClientDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
//以下所有函数的框架由类向导生成,其实现代码需要手动添加
void CClientDlg::OnClickedButtonLogin()
{
// TODO: 在此添加控件通知处理程序代码
m_pSocket=new CClientSocket(this);//创建套接字
if (!m_pSocket->Create())
{
//错误处理
delete m_pSocket;
m_pSocket=NULL;
AfxMessageBox(_T("创建连接服务器的套接字错误,登录失败!"));
return;
}
if (!m_pSocket->Connect(m_strServerName,m_nServerPort))
{
//错误处理
delete m_pSocket;
m_pSocket=NULL;
AfxMessageBox(_T("连接服务器错误,登录失败!"));
return;
}
m_pFile=new CSocketFile(m_pSocket);
m_pArchiveIn=new CArchive(m_pFile,CArchive::load);
m_pArchiveOut=new CArchive(m_pFile,CArchive::store);
//向服务器发送消息,表明新客户进入聊天室
UpdateData(TRUE);//更新控件成员变量
CString strTemp;
strTemp=m_strUserName+_T(":昂首挺胸进入聊天室!!!");
SendMyMessage(strTemp,FALSE);
GetDlgItem(IDC_EDIT_SPEAKING)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_LOGOUT)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_SPEAK)->EnableWindow(TRUE);
GetDlgItem(IDC_EDIT_USERNAME)->EnableWindow(FALSE);
GetDlgItem(IDC_EDIT_SERVERNAME)->EnableWindow(FALSE);
GetDlgItem(IDC_EDIT_SERVERPORT)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_LOGIN)->EnableWindow(FALSE);
}
//单击退出按钮的响应函数
void CClientDlg::OnClickedButtonLogout()
{
// TODO: 在此添加控件通知处理程序代码
CString strTemp;
strTemp=m_strUserName+_T(":大步流星离开聊天室......");
SendMyMessage(strTemp,TRUE);
//删除对象,释放空间
delete m_pArchiveIn;
delete m_pArchiveOut;
delete m_pFile;
delete m_pSocket;
m_pArchiveIn=NULL;
m_pArchiveOut=NULL;
m_pFile=NULL;
m_pSocket=NULL;
//清除聊天室内容
while (m_listCRoom.GetCount()!=0)
m_listCRoom.DeleteString(0);
GetDlgItem(IDC_EDIT_SPEAKING)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_LOGOUT)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_SPEAK)->EnableWindow(FALSE);
GetDlgItem(IDC_EDIT_USERNAME)->EnableWindow(TRUE);
GetDlgItem(IDC_EDIT_SERVERNAME)->EnableWindow(TRUE);
GetDlgItem(IDC_EDIT_SERVERPORT)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_LOGIN)->EnableWindow(TRUE);
}
//单击发言按钮的响应函数
void CClientDlg::OnClickedButtonSpeak()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);//更新控件成员变量,取回用户输入的数据
if (!m_strSpeaking.IsEmpty()) //发言输入框不空
{
SendMyMessage(m_strUserName+"大声说:"+m_strSpeaking,FALSE);
m_strSpeaking=_T("");
UpdateData(FALSE);//更新用户界面,发言框清空
}
}
//关闭客户机时的善后处理函数
void CClientDlg::OnDestroy()
{
CDialogEx::OnDestroy();
// TODO: 在此处添加消息处理程序代码
if ((m_pSocket!=NULL) && (m_pFile!=NULL) && (m_pArchiveOut!=NULL))
{
CMessage msg;
CString strTemp;
strTemp=_T("广而告之:")+m_strUserName+_T("所在客户机已关闭");
msg.m_strMessage=strTemp;
msg.m_bClosed=TRUE;
msg.Serialize(*m_pArchiveOut);
m_pArchiveOut->Flush();
//删除对象,释放空间
delete m_pArchiveIn;
delete m_pArchiveOut;
delete m_pFile;
m_pArchiveIn=NULL;
m_pArchiveOut=NULL;
m_pFile=NULL;
if (m_pSocket!=NULL)
{
BYTE buffer[100];
m_pSocket->ShutDown();
while (m_pSocket->Receive(buffer,100)>0);
}
delete m_pSocket;
m_pSocket=NULL;
}
}
//当套接字收到FD_READ消息时,它的OnReceive函数调用此函数
void CClientDlg::onReceive(void)
{
do {
ReceiveMessage();//接收消息
if (m_pSocket==NULL) return;
}while(!m_pArchiveIn->IsBufferEmpty());
}
//接收消息处理函数
void CClientDlg::ReceiveMessage(void)
{
CMessage msg;
TRY {
msg.Serialize(*m_pArchiveIn);//接收
m_listCRoom.AddString(msg.m_strMessage);//显示在大厅
}CATCH(CFileException, e) {
CString strTemp;
strTemp=_T("与服务器连接已断开,连接关闭!");
m_listCRoom.AddString(strTemp);
msg.m_bClosed=TRUE;
m_pArchiveOut->Abort();
delete m_pArchiveIn;
delete m_pArchiveOut;
delete m_pFile;
delete m_pSocket;
m_pArchiveIn=NULL;
m_pArchiveOut=NULL;
m_pFile=NULL;
m_pSocket=NULL;
}
END_CATCH
}
//发送消息的处理函数
void CClientDlg::SendMyMessage(CString& strMessage,BOOL bClosed)
{
if (m_pArchiveOut!=NULL) {
CMessage msg;
msg.m_strMessage=strMessage;
msg.m_bClosed=bClosed;
msg.Serialize(*m_pArchiveOut);
m_pArchiveOut->Flush();
}
}
ClientSocket.h:
#pragma once
class CClientDlg; //对话框类声明,手动添加
class CClientSocket : public CSocket
{
public:
CClientSocket(CClientDlg* pDlg);//为构造函数添加入口参数,手动添加
virtual ~CClientSocket();
//下面两行由类向导生成
CClientDlg* m_pDlg;//成员变量
virtual void OnReceive(int nErrorCode);
};
ClientSocket.cpp:
// ClientSocket.cpp : 实现文件
#include "stdafx.h"
#include "Client.h"
#include "ClientSocket.h"
#include "ClientDlg.h" //手动添加的包含语句
CClientSocket::CClientSocket(CClientDlg* pDlg)
{
m_pDlg=pDlg;
}
CClientSocket::~CClientSocket()
{
m_pDlg=NULL;
}
// CClientSocket 成员函数
//事件处理函数,当客户端套接字收到FD_READ消息时,执行此函数
void CClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CSocket::OnReceive(nErrorCode);
//调用CClientDlg类的相应函数处理
if (nErrorCode==0) m_pDlg->onReceive();
}
Message.h:
// CMessage定义
#pragma once
class CMessage : public CObject
{
public:
CMessage();
virtual ~CMessage();
CString m_strMessage;
BOOL m_bClosed;
virtual void Serialize(CArchive& ar);
};
Message.cpp:
// Message.cpp : 实现文件
#include "stdafx.h"
#include "Client.h"
#include "Message.h"
CMessage::CMessage()
{
m_strMessage = _T("");
m_bClosed=FALSE;
}
CMessage::~CMessage()
{}
// CMessage 成员函数
void CMessage::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{ // 发送数据
ar<<(WORD)m_bClosed;
ar<<m_strMessage;
}
else
{ // 接收数据
WORD wd;
ar>>wd;
m_bClosed=(BOOL)wd;
ar>>m_strMessage;
}
}
服务器
CClientSocket.h:
// CClientSocket定义
#pragma once
class CServerDlg;
class CMessage;
class CClientSocket : public CSocket
{
public:
CClientSocket(CServerDlg* pDlg);//为构造函数增加入口参数
virtual ~CClientSocket();
//重载回调函数,套接字收到数据时,自动调用此函数
virtual void OnReceive(int nErrorCode);
CServerDlg* m_pDlg;//定义指向主对话框类的指针
CSocketFile* m_pFile; //定义指向CSocketFile对象的指针
CArchive* m_pArchiveIn;//定义指向输入CArchive对象的指针
CArchive* m_pArchiveOut;//定义指向输出CArchive对象的指针
void SendMessage(CMessage* pMsg);//发送消息
void ReceiveMessage(CMessage* pMsg);//接收消息
void Init(void);//初始化
};
ClientSocket.cpp:
// ClientSocket.cpp : 实现文件
#include "stdafx.h"
#include "Server.h"
#include "ClientSocket.h"
#include "ServerDlg.h" //手动添加包含语句
#include "Message.h" //手动添加包含语句
// CClientSocket
CClientSocket::CClientSocket(CServerDlg* pDlg)//增加入口参数,手动添加
{ //初始化成员变量,手动添加
m_pDlg=pDlg;
m_pFile=NULL;
m_pArchiveIn=NULL;
m_pArchiveOut=NULL;
}
CClientSocket::~CClientSocket()
{
//置空或释放成员变量,手动添加
m_pDlg=NULL;
if (m_pFile!=NULL) delete m_pFile;
if (m_pArchiveIn!=NULL) delete m_pArchiveIn;
if (m_pArchiveOut!=NULL) delete m_pArchiveOut;
m_pFile=NULL;
m_pArchiveIn=NULL;
m_pArchiveOut=NULL;
}
// CClientSocket 成员函数
//套接字收到数据时,自动调用此函数
void CClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CSocket::OnReceive(nErrorCode);
m_pDlg->onReceive(this);//调用主对话框中的处理函数,手动添加
}
void CClientSocket::Init(void)
{ //手动添加初始化代码
m_pFile=new CSocketFile(this,TRUE);
m_pArchiveIn=new CArchive(m_pFile,CArchive::load);
m_pArchiveOut=new CArchive(m_pFile,CArchive::store);
}
//发送消息
void CClientSocket::SendMessage(CMessage* pMsg)
{
//手动添加
if (m_pArchiveOut!=NULL)
{
pMsg->Serialize(*m_pArchiveOut);
m_pArchiveOut->Flush();
}
}
//接收消息
void CClientSocket::ReceiveMessage(CMessage* pMsg)
{
pMsg->Serialize(*m_pArchiveIn);
}
Message.h:
// CMessage定义
#pragma once
class CMessage : public CObject
{
public:
CMessage();
virtual ~CMessage();
CString m_strMessage;//字符串消息
BOOL m_bClosed;//是否关闭
virtual void Serialize(CArchive& ar);//重载基类序列化函数
};
Message.cpp:
// Message.cpp : 实现文件
#include "stdafx.h"
#include "Server.h"
#include "Message.h"
CMessage::CMessage()
{
m_strMessage = _T("");//类向导自动添加
m_bClosed=FALSE;//手动添加
}
CMessage::~CMessage()
{}
// CMessage 成员函数
//类向导自动添加
void CMessage::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{ // 发送数据代码,手动添加
ar<<(WORD)m_bClosed;
ar<<m_strMessage;
}
else
{ // 接收数据代码,手动添加
WORD wd;
ar>>wd;
m_bClosed=(BOOL)wd;
ar>>m_strMessage;
}
}
Server.h:
// Server.h : PROJECT_NAME 应用程序的主头文件
#pragma once
#ifndef __AFXWIN_H__
#error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件"
#endif
#include "resource.h" // 主符号
// CServerApp:
// 有关此类的实现,请参阅 Server.cpp
//
class CServerApp : public CWinApp
{
public:
CServerApp();
// 重写
public:
virtual BOOL InitInstance();
// 实现
DECLARE_MESSAGE_MAP()
};
extern CServerApp theApp;
Server.cpp:
// Server.cpp : 定义应用程序的类行为。
#include "stdafx.h"
#include "Server.h"
#include "ServerDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CServerApp
BEGIN_MESSAGE_MAP(CServerApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
// CServerApp 构造
CServerApp::CServerApp()
{
// 支持重新启动管理器
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
// TODO: 在此处添加构造代码,
// 将所有重要的初始化放置在 InitInstance 中
}
// 唯一的一个 CServerApp 对象
CServerApp theApp;
// CServerApp 初始化
BOOL CServerApp::InitInstance()
{
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// 创建 shell 管理器,以防对话框包含
// 任何 shell 树视图控件或 shell 列表视图控件。
CShellManager *pShellManager = new CShellManager;
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
CServerDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用
// “确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
// TODO: 在此放置处理何时用
// “取消”来关闭对话框的代码
}
// 删除上面创建的 shell 管理器。
if (pShellManager != NULL)
{
delete pShellManager;
}
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
// 而不是启动应用程序的消息泵。
return FALSE;
}
ServerDlg.h:
// ServerDlg.h : 头文件
#pragma once
#include "ServerSocket.h"
#include "ClientSocket.h"
class CMessage;
// CServerDlg 对话框
class CServerDlg : public CDialogEx
{
public:
CServerDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_SERVER_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
//以下代码通过类向导手动添加
public:
int m_nServerPort;
CListBox m_listSroom;
CStatic m_staOnline;
afx_msg void OnClickedButtonStart();
afx_msg void OnClickedButtonStop();
CServerSocket* m_pServerSocket;//侦听套接字指针变量
CPtrList m_ClientsList; //在线客户机链表
void onAccept(void);//处理客户机连接请求,从CServerSocket类的OnAccept函数转到此处执行
void onReceive(CClientSocket* pSocket);//获取客户机发送的数据,从CClientSocket类的OnReceive函数转到此处执行
void sendToClients(CMessage* pMsg);//服务器向所有客户机转发消息
};
ServerDlg.cpp:
// ServerDlg.cpp : 实现文件
#include "stdafx.h"
#include "Server.h"
#include "ServerDlg.h"
#include "afxdialogex.h"
#include "Message.h" //手动添加包含语句
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// 对话框数据
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CServerDlg 对话框
CServerDlg::CServerDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CServerDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_nServerPort = 0;//类向导添加的成员变量初始化代码
m_pServerSocket=NULL;//手动添加
}
void CServerDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_SERVERPORT, m_nServerPort);
DDV_MinMaxInt(pDX, m_nServerPort, 1024, 49151);
DDX_Control(pDX, IDC_LIST_SROOM, m_listSroom);
DDX_Control(pDX, IDC_STATIC_ONLINE, m_staOnline);
}
BEGIN_MESSAGE_MAP(CServerDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_START, &CServerDlg::OnClickedButtonStart)
ON_BN_CLICKED(IDC_BUTTON_STOP, &CServerDlg::OnClickedButtonStop)
END_MESSAGE_MAP()
// CServerDlg 消息处理程序
BOOL CServerDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
m_nServerPort=8888;
UpdateData(FALSE);//用成员变量值更新界面
GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(FALSE);
return TRUE; // 除非将焦点设置到控件,否则返回TRUE
}
void CServerDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
void CServerDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标显示。
HCURSOR CServerDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
//单击启动服务器按钮的事件处理函数
void CServerDlg::OnClickedButtonStart()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);//获得用户输入给成员变量
//创建服务器套接字对象,用于在指定端口侦听
m_pServerSocket=new CServerSocket(this);
if (!m_pServerSocket->Create(m_nServerPort))
{
//错误处理
delete m_pServerSocket;
m_pServerSocket=NULL;
AfxMessageBox(LPCTSTR("创建服务器侦听套接字出现错误!"));
return;
}
//启动服务器侦听套接字,可以随时接收来自客户机的请求
if (!m_pServerSocket->Listen())
{
//错误处理
delete m_pServerSocket;
m_pServerSocket=NULL;
AfxMessageBox(LPCTSTR("启动服务器侦听套接字出现错误!"));
return;
}
GetDlgItem(IDC_EDIT_SERVERPORT)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_START)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(TRUE);
}
//单击停止服务器按钮的事件处理函数
void CServerDlg::OnClickedButtonStop()
{
// TODO: 在此添加控件通知处理程序代码
CMessage msg;
msg.m_strMessage="服务器已停止侦听服务!";
delete m_pServerSocket;//释放服务器侦听套接字
m_pServerSocket=NULL;
//清除客户机链接列表
while(!m_ClientsList.IsEmpty())
{
//向每一个客户机发送"服务器已停止侦听服务!"这个消息并从列表中删除链接,释放资源
CClientSocket* pSocket=(CClientSocket*)m_ClientsList.RemoveHead();
pSocket->SendMessage(&msg);
delete pSocket;
}
//清除服务器聊天室大厅
while(m_listSroom.GetCount()!=0)
m_listSroom.DeleteString(0);
GetDlgItem(IDC_EDIT_SERVERPORT)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_START)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(FALSE);
}
//服务器处理来自客户机的连接请求并在服务器端维护一个链接列表
void CServerDlg::onAccept(void)
{
//创建服务器端连接客户机的套接字
CClientSocket* pSocket=new CClientSocket(this);
if (m_pServerSocket->Accept(*pSocket))
{
//建立客户机连接,加入客户机链接列表
pSocket->Init();
m_ClientsList.AddTail(pSocket);
//更新在线人数
CString strTemp;
strTemp.Format(_T("当前在线人数:%d"),m_ClientsList.GetCount());
m_staOnline.SetWindowTextW(strTemp);
}else
{
delete pSocket;
pSocket=NULL;
}
}
//服务器处理来自客户机的消息
void CServerDlg::onReceive(CClientSocket* pSocket)
{
static CMessage msg;
do {
pSocket->ReceiveMessage(&msg);//接收消息
m_listSroom.AddString(msg.m_strMessage);//加入服务器列表框
sendToClients(&msg);//转发给所有客户机
//如果客户机关闭,从链接列表删除服务器端与之会话的链接套接字
if (msg.m_bClosed)
{
//pSocket->Close();
POSITION pos,temp;
for(pos=m_ClientsList.GetHeadPosition();pos!=NULL;)
{
temp=pos;
CClientSocket* pTempSocket=(CClientSocket*)m_ClientsList.GetNext(pos);
if (pTempSocket==pSocket)
{
m_ClientsList.RemoveAt(temp);
CString strTemp;
//更新在线人数
strTemp.Format(_T("当前在线人数:%d"),m_ClientsList.GetCount());
m_staOnline.SetWindowTextW(strTemp);
break;
}//end if
}//end for
delete pSocket;
pSocket=NULL;
break;
}//end if
}while(!((pSocket->m_pArchiveIn)->IsBufferEmpty()));
}
//服务器向所有客户机转发来自某一客户机的消息
void CServerDlg::sendToClients(CMessage* pMsg)
{
for (POSITION pos=m_ClientsList.GetHeadPosition();pos!=NULL;)
{
CClientSocket* pSocket1=(CClientSocket*)m_ClientsList.GetNext(pos);
pSocket1->SendMessage(pMsg);
}
}
ServerSocket.h:
// CServerSocket定义
#pragma once
class CServerDlg; //声明服务器对话框类
class CServerSocket : public CSocket
{
public:
CServerSocket(CServerDlg* pDlg);//添加入口参数
virtual ~CServerSocket();
//回调函数,套接字收到连接请求时,自动调用此函数
virtual void OnAccept(int nErrorCode);
CServerDlg* m_pDlg; //指向服务器对话框类的指针
};
ServerSocket.cpp:
// ServerSocket.cpp : 实现文件
#include "stdafx.h"
#include "Server.h"
#include "ServerSocket.h"
#include "ServerDlg.h" //手动添加
CServerSocket::CServerSocket(CServerDlg* pDlg)
{
m_pDlg=pDlg;//初始化成员变量
}
CServerSocket::~CServerSocket()
{
m_pDlg=NULL;
}
// CServerSocket 成员函数
void CServerSocket::OnAccept(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CSocket::OnAccept(nErrorCode);
m_pDlg->onAccept();//调用主对话框中的处理函数
}
运行结果截图:
服务器:
客户机:
聊天室: