文章目录
前言
C++打造局域网聊天室第十一课: 程序关闭及线程的结束
一、单击发送消息的MFC消息映射机制函数的补充
上节课建立的函数void CchartroomDlg::OnBnClickedButton5()实现了此时程序为客户端或服务端时的消息发送过程,但是还有一种情况,即该程序既不是客户端也不是服务端,对应成员变量m_bIsServer为-1的情况,这个状态不允许消息的发送。
cpp
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(); 主要进行检查工作,代码如下:
cpp
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(); 。由于服务端针对每一个客户端都开启了一个线程,因此关闭时,要把这些线程全都关闭。
cpp
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消息映射机制
cpp
void CchartroomDlg::OnBnClickedButton2() // 实现停止客户端按键的MFC消息映射机制
{
// TODO: 在此添加控件通知处理程序代码
INT iRet = MessageBox(_T("您真的想要停止吗?"), 0, MB_OKCANCEL);
if (iRet == IDOK) // 如果用户真想关闭
{
StopClient(); // 停止客户端
ShowMsg(_T("停止客户端成功!"));
}
}
停止服务端:创建停止服务端按键的MFC消息映射机制
cpp
void CchartroomDlg::OnBnClickedButton4()// 实现停止服务端按键的MFC消息映射机制
{
// TODO: 在此添加控件通知处理程序代码
INT iRett = MessageBox(_T("您真的想要停止吗?"), 0, MB_OKCANCEL);
if (iRett == IDOK) // 如果用户真想关闭
{
StopServer(); // 停止服务端
ShowMsg(_T("停止服务端成功!"));
}
//StopServer(); // 停止服务端
}
总结
C++打造局域网聊天室第十一课: 程序关闭及线程的结束