为socket多线程通信添加线程清理功能
server_thread_w_clean.cpp
-
server_thread_w_clean.cpp
#include "tcpserver.h" #include <thread> #include <mutex> TcpServer server; mutex tcp_mutex; map<int, thread> tcp_map; void tcpFunc(); void threadExit(int clientfd); void tcpMapClean(); int main(int argc, char *argv[]) { if (server.initServer(6666) == false) { cout << "服务端初始化失败!!!" << endl; return -1; } cout << "等待客户端连接......" << endl; while (true) { if (!server.tcpAccept()) { continue; } tcp_map.emplace(make_pair(server.m_connectfd, tcpFunc)); if (tcp_map.at(server.m_connectfd).joinable()) { cout << "Tcp thread " << tcp_map.at(server.m_connectfd).get_id() << " is joinable!" << endl; tcp_map.at(server.m_connectfd).detach(); } } return 0; } void tcpFunc() { int buf_len = 0; char buffer[1024]; int clientfd = server.m_connectfd; cout << "客户端" << server.getClientIP(clientfd) << "已连接" << endl; cout << "当前客户端数:" << server.m_clientaddrs.size() << endl; tcpMapClean(); while (true) { unique_lock<mutex> tcplck(tcp_mutex); memset(buffer, 0, sizeof(buffer)); if (!server.tcpRecv(clientfd, buffer, &buf_len, 5)) { cout << "接收客户端数据失败!" << endl; tcplck.unlock(); break; } cout << "服务端接收数据:" << buffer << endl; strcpy(buffer, "I am your father!"); if (!server.tcpSend(clientfd, buffer, sizeof(buffer))) { cout << "向客户端发送数据失败!" << endl; tcplck.unlock(); break; } tcplck.unlock(); usleep(100); //很重要,避免出现各线程争抢锁的情况 } cout << "客户端" << server.getClientIP(clientfd) << "通信异常!" << endl; threadExit(clientfd); return; } void threadExit(int clientfd) { close(clientfd); server.m_clientaddrs.erase(clientfd); } void tcpMapClean() { if (tcp_map.empty()) { cout << "TCP Thread Map is empty!" << endl; return; } else { for (auto it = tcp_map.begin(); it != tcp_map.end(); it++) { if ((*it).first == 0) //键为0是异常情况,若erase键0则会报段错误 { continue; } if (server.m_clientaddrs.find((*it).first) == server.m_clientaddrs.end()) { tcp_map.erase((*it).first); } } } }
-
一开始是把通信线程放到
vector
容器中,但问题是每来一个客户端就往vector
里插一个thread
,但是客户端断开连接后这个线程还存在vector
容器里,这就导致vector
容器的size
一直在增大 -
所以不仅要对
server.m_clientaddrs
这个map
容器进行清理,还要对管理通信线程的vector
容器进行清理 -
我尝试了一下午,主要是从线程ID入手,但有一个问题是遍历vector容器时无论这个线程是否正在通信,都会返回non executing thread的thread::id,而且在线程中清理线程还会造成我杀我自己的问题,相当于一个悖论
-
去健身房健身前突然想到了
map
容器,既然线程退出时清理了server.m_clientaddrs
的clientfd
,那么如果将clientfd
也作为通信thread
的键,在新线程开始时遍历这个map
,server.m_clientaddrs
里没有的clientfd
而这个map
有,清除掉即可,这样就可以实现已经挂掉的通信线程的清理 -
用的时候发现
vector
容器的emplace_bak
可以插入带参数的线程函数,而map
容器的emplace
和insert
不能插入带参数的线程函数,所以直接用全局变量server
的m_connectfd
作为tcpFunc()
中局部变量clientfd
-
在遍历
tcp_map
的时候会出现键为0
的情况,这是个异常,因为并没有插入值为0
的键,此时如果erase
掉就会产生段错误,所以遍历到的话直接continue
掉,具体原因还没有深究 -
将程序部署到云服务器上编译时,会报很多错,搜了一下大概是
gcc版本
太低的问题。云服务器上式4.8.5而笔记本上是7.8.5,CentOS升级GCC
又比较麻烦,所以直接重装系统,GCC
版本直接升级到八点多,成功编译
socket线程清理功能修正
void tcpMapClean()
{
if (tcp_map.empty())
{
cout << "TCP Thread Map is empty!" << endl;
return;
}
else
{
for (auto it = tcp_map.begin(); it != tcp_map.end(); it++)
{
if ((*it).first == 0) //键为0是异常情况,若erase键0则会报段错误
{
continue;
}
if (server.m_clientaddrs.find((*it).first) == server.m_clientaddrs.end())
{
tcp_map.erase((*it).first);
}
}
}
}
- 当键为
0
时如果continue
还是会报段错误,改为break
则没有问题