为SOCKET多线程通信添加线程清理与线程退出功能

38 篇文章 7 订阅

server_thread_w_clean_signal

#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();

// 信号2和信号15的处理函数
void mainExit(int sig);

int main(int argc, char *argv[])
{
    // 关闭全部的信号
    for (int i = 0; i < 100; i++)
    {
        signal(i, SIG_IGN);
    }
    // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
    // 但请不要用 "kill -9 +进程号" 强行终止
    signal(SIGINT, mainExit);
    signal(SIGTERM, mainExit);

    if (!server.initServer(6666))
    {
        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则会报段错误
            {
                break;
            }
            if (server.m_clientaddrs.find((*it).first) == server.m_clientaddrs.end())
            {
                tcp_map.erase((*it).first);
            }
        }
    }
}

void mainExit(int sig)
{
    cout << "服务端退出......" << endl;
    server.closeListen();
    server.m_clientaddrs.clear();
    tcp_map.clear();
    exit(0);
}
  • 一开始是把通信线程放到vector容器中,但问题是每来一个客户端就往vector里插一个thread,但是客户端断开连接后这个线程还存在vector容器里,这就导致vector容器的size一直在增大
  • 所以不仅要对server.m_clientaddrs这个map容器进行清理,还要对管理通信线程的vector容器进行清理
  • 我尝试了一下午,主要是从线程ID入手,但有一个问题是遍历vector容器时无论这个线程是否正在通信,都会返回non executing thread的thread::id,而且在线程中清理线程还会造成我杀我自己的问题,相当于一个悖论
  • 去健身房健身前突然想到了map容器,既然线程退出时清理了server.m_clientaddrsclientfd,那么如果将clientfd也作为通信thread的键,在新线程开始时遍历这个mapserver.m_clientaddrs里没有的clientfd而这个map有,清除掉即可,这样就可以实现已经挂掉的通信线程的清理
  • 用的时候发现vector容器的emplace_bak可以插入带参数的线程函数,而map容器的emplaceinsert不能插入带参数的线程函数,所以直接用全局变量serverm_connectfd作为tcpFunc()中局部变量clientfd
  • 在遍历tcp_map的时候会出现键为0的情况,这是个异常,因为并没有插入值为0的键,此时如果erase掉就会产生段错误,所以遍历到的话直接continue掉,具体原因还没有深究
  • 将程序部署到云服务器上编译时,会报很多错,搜了一下大概是gcc版本太低的问题。云服务器上式4.8.5而笔记本上是7.8.5,CentOS升级GCC又比较麻烦,所以直接重装系统,GCC版本直接升级到八点多,成功编译
  • 当键为0时如果continue还是会报段错误,改为break则没有问题
  • 信号退出很简单,关闭监听的socket,清空两个map容器
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值