【通信协议及编码】实验3:MFC框架下聊天室客户机与服务器程序

文章目录


一、实验目的

  1. 掌握MFC套接字编程方法
  2. 借助VS2016的MFC应用程序向导创建程序框架;
  3. 从CSocket类派生用户自定义的套接字类;
  4. 通过CArchive类、CSocketFile类、CSocket类实现网络数据交换。

二、实验环境

  1. 操作系统:WINDOWS 7及以上
  2. 开发工具:Microsoft VisualBasic6.0
  3. 实验设备: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();//调用主对话框中的处理函数
}

运行结果截图:
服务器:
【通信协议及编码】实验3:MFC框架下聊天室客户机与服务器程序
客户机:
【通信协议及编码】实验3:MFC框架下聊天室客户机与服务器程序
聊天室:
【通信协议及编码】实验3:MFC框架下聊天室客户机与服务器程序
【通信协议及编码】实验3:MFC框架下聊天室客户机与服务器程序


上一篇:Kubernetes 1.20.5 安装traefik在腾讯云下的实践


下一篇:Kubernetes高可用部署之Kubeadm(二)阿里云的SLB负载API-SERVER