C++打造局域网聊天室第十一课: 程序关闭及线程的结束

发布于:2024-12-23 ⋅ 阅读:(12) ⋅ 点赞:(0)


前言

C++打造局域网聊天室第十一课: 程序关闭及线程的结束


一、单击发送消息的MFC消息映射机制函数的补充

上节课建立的函数void CchartroomDlg::OnBnClickedButton5()实现了此时程序为客户端或服务端时的消息发送过程,但是还有一种情况,即该程序既不是客户端也不是服务端,对应成员变量m_bIsServer为-1的情况,这个状态不允许消息的发送。

void CchartroomDlg::OnBnClickedButton1() // 单击连接服务器的MFC消息映射机制
{
	// TODO: 在此添加控件通知处理程序代码
	m_hConnectThread = CreateThread(NULL, 0, ConnectThreadFunc, this, 0, NULL); // 创建新线程函数,客户端连接服务端线程
}


void CchartroomDlg::OnBnClickedButton5() // 单击发送消息的MFC消息映射机制
{
	// TODO: 在此添加控件通知处理程序代码
	CString strMsg;
	GetDlgItemText(IDC_EDIT4, strMsg); // 获取输入信息编辑框内的输入信息
	if (m_bIsServer == TRUE) // 若本程序状态为服务器
	{
		strMsg = _T("服务器:>") + strMsg;
		ShowMsg(strMsg);
		SendClientMsg(strMsg, NULL); // 将信息发送给所有队列中的客户端
	}
	else if (m_bIsServer == FALSE) // 若本程序状态为客户端
	{
		CString strTmp = _T("本地客户端: > ") + strMsg;
		ShowMsg(strTmp);
		int iSend = send(m_ConnectSock, (char*)strMsg.GetBuffer(), strMsg.GetLength() * sizeof(TCHAR), 0);
		strMsg.ReleaseBuffer();
	}
	else // 既不是客户端也不是服务端
	{
		AfxMessageBox(_T("请您先进入聊天室!"));
	}
	SetDlgItemText(IDC_EDIT4, _T("")); // 将信息发送给服务端后清空发送内容编辑框

}

在这里插入图片描述

二、线程的结束

结束线程的方法:
1.调用TerminateThread() API,强制结束某一线程
2.ExitThread(),线程自己强制退出
3.线程函数返回:最好的方法,申请的资源全都得到释放
说明:例如对于客户端的线程函数DWORD WINAPI ConnectThreadFunc(LPVOID pParam),当用户点击连接服务器后,程序即会创建线程。当该函数运行到return TRUE;时,即为该函数返回。

使用前两种方法,线程的部分资源得不到释放,虽然进程结束时所有的资源都会被系统所回收,但是这不是一个良好的编程习惯。这里使用第三种方法。

三、客户端与服务端结束函数的封装

1.客户端线程结束函数

在chartroom.h头文件中声明客户端线程结束函数void StopClient();
在这里插入图片描述在chartroom.cpp源文件中实现客户端线程结束函数void StopClient();

客户端只有连接服务器的线程,在无限循环中,如果服务端不关闭,则一直不跳出循环;只有当服务端关闭了才跳出循环。那么如果服务端一直没有关闭,客户端会在死循环中一直循环,无法退出线程函数。为了解决这个问题,在chartroom.h头文件中声明一个布尔类型的变量
在这里插入图片描述

在chartroom.cpp源文件的构造函数处初始化bShutDown
在这里插入图片描述
由于当bShutDown为1时关闭客户端,那么在循环判断处还要加上bShutDown的影响
在这里插入图片描述
在chartroom.cpp源文件中实现客户端线程结束函数void StopClient(); 主要进行检查工作,代码如下:

void CchartroomDlg::StopClient()// 实现客户端线程结束函数
{
	bShutDown = TRUE; // 设置关闭客户端布尔变量
	// WaitForSingleObject等待内核对象被激发才返回值,否则函数阻塞在这参数2时间。这里指的是线程结束的时候表示该线程句柄被激发
	DWORD dwRet = WaitForSingleObject(m_hConnectThread, 1000); // 参数1为内核对象(线程、进程、文件等)句柄;参数2为等待时间
	if (dwRet != WAIT_OBJECT_0) // 如果内核对象没有被激发,即超时。表示线程没有正常结束
	{
		TerminateThread(m_hConnectThread, -1); // 强制线程结束
		closesocket(m_ConnectSock);
	}
	// 线程关闭后将一些参数初始化
	m_hConnectThread = NULL;
	m_ConnectSock = INVALID_SOCKET;
	m_bIsServer = -1;
	bShutDown = FALSE;
}

2.服务端线程结束函数

在chartroom.h头文件中声明服务端线程结束函数void StopServer();
在这里插入图片描述
在chartroom.cpp源文件中实现服务端线程结束函数void StopServer(); 。由于服务端针对每一个客户端都开启了一个线程,因此关闭时,要把这些线程全都关闭。

void CchartroomDlg::StopServer() // 实现服务端线程结束函数
{
	UINT nCount = m_ClientArray.GetCount(); // 得到服务端连接了多少个客户端
	HANDLE* m_pHandles = new HANDLE[nCount + 1]; // 由于个数是一个变量,需要用new的方式建立数组,+1是为了放监听线程
	m_pHandles[0] = m_hListenThread; // 数组的第一个位置放入监听线程句柄
	for (UINT idx = 0; idx < nCount; idx++)
	{
		m_pHandles[idx + 1] = m_ClientArray.GetAt(idx).hThread; // 后续每一个位置放入接收客户端线程句柄
	}
	bShutDown = TRUE; // 告诉服务端自己结束
	DWORD dwRet = WaitForMultipleObjects(nCount + 1, m_pHandles, TRUE, 1000); // 参数1为等待内核对象个数;参数2为内核对象(线程、进程、文件等)句柄数组首地址;
	//参数3为表示是否等待数组内全部内核对象句柄,设为1需要等数组内所有内核对象返回,设为0只要有一个内核对象返回即可;参数4为等待时间
	if (dwRet != WAIT_OBJECT_0) // 如果内核对象没有被激发,即超时。表示线程没有正常结束
	{
		for (INT_PTR i = 0; i < m_ClientArray.GetCount(); i++)
		{
			TerminateThread(m_ClientArray.GetAt(i).hThread, -1); // 强制与客户端连接线程结束
			closesocket(m_ClientArray.GetAt(i).m_Socket);
		}
		TerminateThread(m_hListenThread, -1); // 强制监听线程结束
		closesocket(m_ListenSock);
	}
	delete m_pHandles; //删除new创建的资源
	// 线程关闭后将一些参数初始化
	m_hListenThread = NULL;
	m_ListenSock = INVALID_SOCKET;
	m_bIsServer = -1;
	bShutDown = FALSE;

}

与关闭客户端类似,在服务端中加入与bShutDown有关的代码
在监听线程中加入
在这里插入图片描述
在与客户端通信的线程中加入
在这里插入图片描述

四、使用封装的客户端结束和服务端结束函数

停止按键的响应
在这里插入图片描述
停止客户端:创建停止客户端按键的MFC消息映射机制

void CchartroomDlg::OnBnClickedButton2() // 实现停止客户端按键的MFC消息映射机制
{
	// TODO: 在此添加控件通知处理程序代码
	INT iRet = MessageBox(_T("您真的想要停止吗?"), 0, MB_OKCANCEL);
	if (iRet == IDOK) // 如果用户真想关闭
	{
		StopClient(); // 停止客户端
		ShowMsg(_T("停止客户端成功!"));
	}
	
}

在这里插入图片描述
停止服务端:创建停止服务端按键的MFC消息映射机制

void CchartroomDlg::OnBnClickedButton4()// 实现停止服务端按键的MFC消息映射机制
{
	// TODO: 在此添加控件通知处理程序代码
	INT iRett = MessageBox(_T("您真的想要停止吗?"), 0, MB_OKCANCEL);
	if (iRett == IDOK) // 如果用户真想关闭
	{
		StopServer(); // 停止服务端
		ShowMsg(_T("停止服务端成功!"));
	}
	//StopServer(); // 停止服务端
}

总结

C++打造局域网聊天室第十一课: 程序关闭及线程的结束