Linux 基于TCP实现服务端客户端通信(守护进程版)

1.前台进程和后台进程

当用户通过Xshell等工具登录云服务器时,Linux系统会为用户创建一个会话(session)。每个会话会默认创建一个bash命令行解释器进程,它初始是前台进程,负责接收键盘输入、执行命令并返回结果。

这个bash进程现在也就是前台进程,前台进程只能有一个,后台进程可以有很多个。

前台进程与后台进程的区别

先看代码:创建代码,每隔一秒打印hello world字符串。

cpp 复制代码
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
    while(true)
    {
        cout<<"hello world"<<endl;
        sleep(1);
    }
    return 0;
}

运行结果:

可以发现当我们运行进程后,输入ls,pwd这些指令bash命令行进程都不会执行了,因为这个时候着进程就是一个前台进程了,bash进程已经被切换到后台进程了,这个时候是进程在获取键盘输入,而不是bash进程,所以bash进程也就是不能执行对应的指令了。

前台进程:能获取键盘输入(标准输入),一个会话中只能有一个前台进程。

后台进程:无法获取键盘输入,但可以向显示器输出结果(标准输出/标准错误),一个会话中可以同时存在多个后台进程。

注意:前台进程拥有键盘输入权限,后台进程没有,但两者都能向显示器输出内容。

进程切换逻辑

当运行一个程序(如 ./process )时,该程序会成为前台进程,原本的bash会被切换到后台。此时bash无法接收键盘输入,也就无法执行新命令;若用 Ctrl+C 终止前台进程,bash会自动回到前台。

进入后台运行程序

在命令末尾加上 & ,例如 ./process & ,可以让程序以后台进程运行,此时bash仍保持为前台进程,可继续执行其他命令。

使用 & 可以让程序后台运行,不影响bash接收新命令。

当我们将test进程变为后台进程后,因为打印是向显示器上面打印,所以会一直打印"hello world"字符串,但是输入指令还是会被执行,说明这个时候bash是前台进程,test为后台进程。

前台进程和后台进程数量

因为前台进程和后台进程都可以向显示器打印数据,所以,我们将后台进程打印的数据重定向到了一个文件中

查看后台进程数量:

cpp 复制代码
ps ajx|head -1&&ps ajx|grep test|grep -v grep

可以看到后台进程被创建了多个,一个会话中前台进程只能有1个,后台进程可以有多个。

后台进程的操作

当我们运行一个后台进程的时候,可以看到会出现一个关于后台进程的信息。

第一个数字表示的是后台进程的任务号,第二个表示的是进程的pid。

查看后台进程的状态

cpp 复制代码
jobs

使用jobs可以查看后台进程的状态

第一列就是任务号,后面就是运行状态了,running表示正在运行。

切换进程状态

cpp 复制代码
fg+任务号

fg加任务号可以切换该任务号的进程为前台进程。

输入fg 3就将后台进程切换到了前台,这个时候bash为后台进程就不能收到我们的指令了。

暂停进程和终止进程

暂停进程

可以使用fg+任务号来时后台进程切换到前台进程,输入ctrl+z来暂停进程,ctrl+z是向进程发送了20号信号。

使用ctrl+z暂停进程后,在查看进程状态,就可以看到后台进程被暂停了,bash也就自动切换回到前台进程了。

终止进程

和暂停进程的方法一样的,通过fg切换到前台进程,使用ctrl+c来终止进程

恢复暂停的后台进程

cpp 复制代码
bg+任务号//恢复后台进程

bg+任务号恢复暂停的后台进程

2.Linux的进程关系

向创建一个test后台进程,然后创建3个sleep后台进程

cpp 复制代码
sleep 1000|sleep 2000|sleep 3000

那么三个sleep之间是采用两个管道连接起来的,本质上还是创建了3个sleep进程,然后第一个sleep进程休眠结束后,结果是什么都没有,然后将这个结果通过管道交给第二个sleep进程,然后第二个sleep进程开始休眠,同理结束后通过管道交给第三个sleep进程,所以这三个sleep进程合起来在完成同一个任务,process这个后台进程自己在完成一个任务

所以我们使用如下指令查找一行运行的进程中,带有sleep或者process的进程,grep -E '内容|内容' 是进行正则匹配对应的内容

cpp 复制代码
ps ajx|head -1&&ps ajx|grep -E 'test|sleep'|grep -v grep

先解释各字段的意思:

PPID 父进程 PID。这些进程都由 Shell(bash)创建,所以 PPID 就是 bash 的 PID。

PID 进程自身的唯一 ID。

PGID 进程组 ID。- 3 个 属于同一进程组,PGID 等于第一个 的 PID(它是组长)。- 单独成组,PGID 就是它自己的 PID。

SID 会话 ID,等于当前登录 Shell(bash)的 PID。同一终端下的所有进程共享同一个 SID。

TTY 关联的终端设备。

STAT 进程状态(如 休眠、 运行等)。

进程组和任务的关系

一个进程组也就是完成的一个任务,当我们启动了两次test进程,也就是代表着我们启动了两个不同的任务,当我们通过管道创建的3个sleep进程也就是属于一个任务组,所以3个sleep的PGID是系统的,也就意味着他们属于同一个任务组。

同时属于同一个进程组的进程,他们的PGID进程组ID会等于进程组中的第一个进程的PID,上图中第一个sleep是第一个启动的进程,所以他也就相当于这个进程组的队长,进程组ID就保持和队长的PID一样。

session会话ID对于bash的PID

当我们打开xshell去连接云服务器时,登录成功后会fork出一个bash进程,bash进程会使用

setsid()来创建一个会话,那么bash进程也就是会话的首进程。

内核规定:

调用 setsid() 的进程 = 会话首进程

会话首进程的 PID = 整个会话的 SID

所以bash的PID会对于会话ID

3.守护进程

后台进程会受到用户的登录和退出的影响的,当我们关闭xshell,在打开时,后台进程就被清理了。

如果我们想要一个后台进程不受用户退出和登录的影响,该怎么办?

守护进程

当张三登录 Linux,李四也登录 Linux,系统会给每个人单独开一个房间:张三的房间 = 张三的 Session,李四的房间 = 李四的 Sessio,这里的房间也就是一个会话,两个会话互不打扰,张三退出,就会关闭这个会话,将这个会话的所有进程终止。

但是李四的会话是不受影响的。我们把一个进程自成一个会话叫做守护进程,给一个进程单独开一个会话,是它不会受其他会话的影响。

linux操作系统中的可能有很多个用户在使用,所以操作系统就会为这些用户每一个用户创建一个对应的session会话,所以系统中就会同时存在多个session会话,那么linux操作系统要不要将这些session会话管理起来?要,那么如何进行管理呢?

先描述再组织,先使用struct结构体将session会话描述起来,然后采用一定的数据结构,例如链表,将这些struct结构体实例化的session会话的描述对象组织起来,从此以后对session会话的管理就转化成了对链表的增删查改

所以系统中一定有对应的系统调用可以创建session会话,所以这也就使得让服务器成为守护进程有了可能

4.TCP服务器与客户端通信(守护进程版)

setsid创建一个会话。

Dame.hpp

cpp 复制代码
#include <iostream>
#include <string>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
const string nullfile="/dev/null";
void Daemon(const string&cwd="")
{
    //忽略其它信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    //创建子进程,然后终止父进程,子进程会拷贝父进程资源,例如文件描述符,页表,地址空间,代码等,
    //然后由子进程继续向后执行代码
    if(fork() > 0)
        exit(0);
    //创建session会话,子进程成为服务器,然后守护进程化
    setsid();

    //更改当前子进程代表的服务器进程的工作目录
    if(!cwd.empty())
        chdir(cwd.c_str());

    //打开 /dev/null 文件
    int fd = open(nullfile.c_str(), O_WRONLY);

    //重定向 标准输入,标准输出,标准错误
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

但是不能直接调用 setsid,因为进程组组长 不能调用 setsid 创建新会话,只有组员才能调用。

但是服务器一运行:自己就是一个进程组,自己就是组长,直接调用 setsid 会失败。所以使用fork 创建子进程,父进程直接退出,由子进程来创建会话,子进程彻底脱离原来的终端,自成会话。

父进程死了,子进程还活着,子进程变成 孤儿进程被系统 1 号进程领养

所以守护进程本质就是孤儿进程 + 自成会话。

因为守护进程已经:脱离终端,不跟用户交互,所以对于标准输入 0是没人输入,标准输出 1 是 没人看,标准错误 2是没人看,这些文件描述符留着占位置、可能出问题。

所以可以打开 /dev/null (黑洞文件,写进去就消失),然后用 dup2 ,把 0、1、2 全部重定向到 /dev/null
守护进程工作目录最好切换到根目录 /,避免:占用某个目录导致无法卸载,日志、文件路径混乱

所以可以使用chdir改变目录
以前日志需要打印到显示器,但是现在标准输入,输出,错误都被关闭了 ,但是我们又希望看到日志打印的信息

所以可以在将日志系统根据不同的错误等级往对应的里写文件,以后看日志直接去文件中看
同时我们希望守护进程不想被随便干扰:

用 signal 忽略 SIGPIPE (管道破裂)忽略 SIGCHLD (子进程退出)忽略 SIGTSTP (Ctrl+Z)忽略 SIGHUP (挂断)

main.cpp

改变日志打印方式,根据日志等级王不同的文件中打印

cpp 复制代码
#include <iostream>
#include <memory>
#include "server.hpp"
void Usage(const std::string str)
{
    std::cout << "\n\tUsage: " << str << " port[1024+]\n" << std::endl; 
}
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }
    lg.Enable(Classfile);
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> server(new TcpServer(port));
    server->init();
    server->StartServer();
    return 0;
}

server.hpp

启动进程守护

cpp 复制代码
 void StartServer()
    {
        Daemon();
        ThreadPool<Task>::GetInstance()->Start();
        lg(Info,"TcpServer is running");
        for(;;)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            //阻塞等待
            int sockfd=accept(listensock_,(struct sockaddr*)(&client),&len);
            if(sockfd<0)
            {
                lg(Warning,"accept error,errno:%d,errstring:%s",errno,strerror(errno));
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);
            string clientip=inet_ntoa(client.sin_addr);
            lg(Info,"get a new link...,clientip:%s,clientport:%d",clientip.c_str(),clientport);
            //多进程
            // pid_t id=fork();
            // if(id==0)
            // {
            //     //子进程
            //     close(listensock_);
            //     //子进程创建孙进程后直接退出
            //     if(fork()>0) exit(0);
            //     //孙进程
            //     Service(sockfd,clientip,clientport);
            //     close(sockfd);
            // }
            // close(sockfd);
            // pid_t rid=waitpid(id,nullptr,0);
            //多线程
            // ThreadData*td=new ThreadData(sockfd,clientip,clientport,this);
            // pthread_t id;
            // pthread_create(&id,nullptr,Routine,td);
            //线程池
            Task t(sockfd,clientip,clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

删除守护进程,查看进程pid,kill指令删除即可。

测试:

bash还是可以接收到指令。

客户端也可以正常运行。

源代码

client.cpp

cpp 复制代码
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
void Usage(const string& str)
{
    cout << "\n\tUsage: " << str << " serverip serverport" << endl; 
}

// 客户端启动格式:./tcpclient serverip serverport
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 0;
    }

    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);

    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_port=htons(serverport);
    server.sin_addr.s_addr=inet_addr(serverip.c_str());
    socklen_t len=sizeof(server);

    while(true)
    {
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0)
        {
            cerr << "socket create err" << endl;
            return 1;
        }
        bool isconnect=false;
        int cnt=5;
        do
        {

            int n=connect(sockfd,(struct sockaddr*)(&server),len);
            if(n<0)
            {
                //连接失败
                isconnect=true;
                cnt--;
                sleep(2);
                cerr<<"connect error......,reconnect: "<<cnt<<endl;
            }
            //连接成功,退出循环
            else
            {
                break;
            }
        }while(cnt&&isconnect);
        if(cnt==0)
        {
            cerr<<"user offline..."<<endl;
            return 2;
        }
        //客户端发起connect请求时,操作系统会自动进行端口号的随机bind绑定
        string message;
        char inbuffer[4096];
        cout << "Please Enter# ";
        getline(cin, message);

        int n = write(sockfd, message.c_str(), message.size());
        if(n < 0)
        {
            cerr << "write err" << endl;
            break;
        }

        n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            cout << inbuffer << endl;
        }
        //当读取返回值为0时,不要直接break退出
        // else
        // {
        //     break;
        // }
        close(sockfd);
    }
    return 0;
}

Daemon.hpp

cpp 复制代码
#include <iostream>
#include <string>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
const string nullfile="/dev/null";
void Daemon(const string&cwd="")
{
    //忽略其它信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    //创建子进程,然后终止父进程,子进程会拷贝父进程资源,例如文件描述符,页表,地址空间,代码等,
    //然后由子进程继续向后执行代码
    if(fork() > 0)
        exit(0);
    //创建session会话,子进程成为服务器,然后守护进程化
    setsid();

    //更改当前子进程代表的服务器进程的工作目录
    if(!cwd.empty())
        chdir(cwd.c_str());

    //打开 /dev/null 文件
    int fd = open(nullfile.c_str(), O_WRONLY);

    //重定向 标准输入,标准输出,标准错误
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

dict.txt

cpp 复制代码
apple:苹果
banana:香蕉
book:书
cat:猫
dog:狗
egg:鸡蛋
fish:鱼
girl:女孩
house:房子
ice:冰
juice:果汁
king:国王
lion:狮子
milk:牛奶
nest:鸟巢
orange:橙子
pen:钢笔
queen:女王
rice:米饭
sun:太阳
tree:树
umbrella:雨伞
van:货车
water:水
x-ray:X光
yellow:黄色
zoo:动物园
baby:婴儿
cake:蛋糕
door:门
ear:耳朵
face:脸
grass:草
hand:手
ice cream:冰淇淋
jacket:夹克
kite:风筝
lamp:灯
moon:月亮
nose:鼻子
one:一
pig:猪
queen bee:蜂王
red:红色
school:学校
toy:玩具
uncle:叔叔
vest:背心
window:窗户
box:盒子
car:汽车
dad:爸爸
eye:眼睛
fan:风扇
game:游戏
hat:帽子
iceberg:冰山
jeep:吉普车
key:钥匙
lion cub:幼狮
map:地图
night:夜晚
octopus:章鱼
pear:梨
queen size:大号
rain:雨
star:星星
table:桌子
umbrella stand:伞架
violin:小提琴
wolf:狼
apple pie:苹果派
ball:球
chair:椅子
doll:玩偶
elephant:大象
flower:花
goat:山羊
hen:母鸡
insect:昆虫
jar:罐子
kangaroo:袋鼠
leaf:叶子
monkey:猴子
nest egg:储备金
orange juice:橙汁
pencil:铅笔
queen ant:蚁后
rabbit:兔子
ship:船
tiger:老虎
umbrella hat:伞帽
van driver:货车司机
watch:手表
xylophone:木琴
yacht:游艇
zebra:斑马
air:空气
big:大的
cup:杯子

Init.hpp

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<fstream>
#include<unordered_map>
#include"log.hpp"
using namespace std;
const string dictname="./dict.txt";
const string sep=":";
bool Solit(string &s,string *part1,string*part2)
{
    auto pos=s.find(sep);
    if(pos==string::npos) return false;//npos就是无符号整型的最大值,string的findsize_t,算法头文件下的npos返回的则是迭代器。
    *part1=s.substr(0,pos);//substr左闭右开
    *part2=s.substr(pos+1);
    return true;
}
class Init
{
public:
    Init()
    {
        //ofstream从文件写入
        ifstream in(dictname);//从指定文件中读取
        if(!in.is_open())
        {
            lg(Fatal,"ifstream open %s error",dictname.c_str());
            exit(1);
        }
        string line;
        while(getline(in,line))//不断的从in中读取到line中
        {
            string part1,part2;
            Solit(line,&part1,&part2);
            dict.insert({part1,part2});
        }
        in.close();
    }
    string translation(const string&key)
    {
        auto iter=dict.find(key);
        if(iter==dict.end()) return "Unknow";
        else return iter->second;
    }
private:
    unordered_map<string,string> dict;
};

main.cpp

cpp 复制代码
#include <iostream>
#include <memory>
#include "server.hpp"
void Usage(const std::string str)
{
    std::cout << "\n\tUsage: " << str << " port[1024+]\n" << std::endl; 
}
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }
    lg.Enable(Classfile);
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> server(new TcpServer(port));
    server->init();
    server->StartServer();
    return 0;
}

makefile

cpp 复制代码
.PHONY:all
all:tcpserver client
tcpserver:main.cpp
	g++ -o $@ $^ -std=c++11 -lpthread -g
client:client.cpp
	g++ -o $@ $^ -std=c++11 -g
.PHONY:clean
clean:
	rm -f tcpserver client

server.hpp

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "log.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include "Daemon.hpp"
extern Log lg;
using namespace std;
const int backlog=10;
const string ip="0.0.0.0";
const uint16_t port=8080;
enum
{
    UsageError=1,
    SocketError,
    BindError,
    ListenError
};
class TcpServer;
struct ThreadData
{
public:
    ThreadData(int fd,const string&ip,const uint16_t&p,TcpServer*t)
    :sockfd(fd)
    ,clientip(ip)
    ,clientport(p)
    ,tsvr(t)
    {}
public:
    int sockfd;
    string clientip;
    uint16_t clientport;
    TcpServer*tsvr;
};
class TcpServer
{
public:
    TcpServer(uint16_t defaultport=port)
    :ip_(ip)
    ,port_(port)
    {}
    void init()
    {
        listensock_=socket(AF_INET,SOCK_STREAM,0);
        if(listensock_<0)
        {
            lg(Fatal,"create socket error,errno:%d,errstring:%s",errno,strerror(errno));
            exit(SocketError);
        }
        lg(Info,"socked create success,listensock_:%d",listensock_);
        //防止偶发性服务器无法重启
        int opt=1;
        setsockopt(listensock_,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_family=AF_INET;
        server.sin_port=htons(port_);
        server.sin_addr.s_addr=inet_addr(ip_.c_str());
        socklen_t len=sizeof(server);
        if(bind(listensock_,(struct sockaddr*)(&server),len)<0)
        {
            lg(Fatal,"bind socket error,errno:%d,errstring:%s",errno,strerror(errno));
            exit(BindError);
        }
        lg(Info,"socked bind success");
        if(listen(listensock_,backlog)<0)
        {
            lg(Fatal,"listen error,errno:%d,errstring:%s",errno,strerror(errno));
            exit(ListenError);
        }
        lg(Info,"listen success");
    }
    void Service(int sockfd,string clientip,uint16_t clientport)
    {
        while(true)
        {
            char buffer[4096];
            ssize_t n=read(sockfd,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                buffer[n]=0;
                cout<<"client say#"<<buffer<<endl;
                string server_echo="server say#";
                server_echo+=buffer;
                write(sockfd,server_echo.c_str(),server_echo.size());
            }
            else if(n==0)
            {
                //客户端断开连接
                lg(Info,"%s:%d quit ,server close sockfd:%d",clientip,clientport,sockfd);
                break;
            }
            else
            {
                //异常情况
                lg(Info,"read error,%s:%d quit,server close sockfd:%d",clientip,clientport,sockfd);
                break;
            }
        }
    }
    static void*Routine(void*args)
    {
        pthread_detach(pthread_self());
        ThreadData*td=static_cast<ThreadData*>(args);
        td->tsvr->Service(td->sockfd,td->clientip,td->clientport);
        close(td->sockfd);
        delete td;
        return nullptr;
    }
    void StartServer()
    {
        Daemon();
        ThreadPool<Task>::GetInstance()->Start();
        lg(Info,"TcpServer is running");
        for(;;)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            //阻塞等待
            int sockfd=accept(listensock_,(struct sockaddr*)(&client),&len);
            if(sockfd<0)
            {
                lg(Warning,"accept error,errno:%d,errstring:%s",errno,strerror(errno));
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);
            string clientip=inet_ntoa(client.sin_addr);
            lg(Info,"get a new link...,clientip:%s,clientport:%d",clientip.c_str(),clientport);
            //多进程
            // pid_t id=fork();
            // if(id==0)
            // {
            //     //子进程
            //     close(listensock_);
            //     //子进程创建孙进程后直接退出
            //     if(fork()>0) exit(0);
            //     //孙进程
            //     Service(sockfd,clientip,clientport);
            //     close(sockfd);
            // }
            // close(sockfd);
            // pid_t rid=waitpid(id,nullptr,0);
            //多线程
            // ThreadData*td=new ThreadData(sockfd,clientip,clientport,this);
            // pthread_t id;
            // pthread_create(&id,nullptr,Routine,td);
            //线程池
            Task t(sockfd,clientip,clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }
private:
    int listensock_;
    string ip_;
    uint16_t port_;
};

Task.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include "log.hpp"
#include "Init.hpp"
using namespace std;
extern Log lg;
class Task
{
public:
    Task(int sockfd, const string &clientip, const uint16_t clientport)
        : sockfd_(sockfd), clientip_(clientip), clientport_(clientport)
    {
    }
    // void run()//普通转发
    // {
    //     char buffer[4096];
    //     int n=read(sockfd_,buffer,sizeof(buffer)-1);
    //     if(n>0)
    //     {
    //         buffer[n]=0;
    //         cout<<"client say#"<<buffer<<endl;
    //         string echo_string="server say#";
    //         echo_string+=buffer;
    //         std::cout << echo_string << std::endl;
    //         write(sockfd_,echo_string.c_str(),echo_string.size());
    //     }
    //     else if (n == 0)
    //     {
    //         lg(Info, "%s:%d quit,server close sockfd:%d", clientip_.c_str(), clientport_, sockfd_);
    //     }
    //     else
    //     {
    //         lg(Warning, "read error,sockfd:%d,clientip:%s,clientport:%d", sockfd_, clientip_.c_str(), clientport_);
    //     }
    //     close(sockfd_);
    // }
    void run()
    {
        char buffer[4096];
        int n=read(sockfd_,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<"client say#"<<buffer<<endl;
            string echo_string="server say#";
            echo_string+=it.translation(buffer);
            write(sockfd_,echo_string.c_str(),echo_string.size());
        }
        else if (n == 0)
        {
            lg(Info, "%s:%d quit,server close sockfd:%d", clientip_.c_str(), clientport_, sockfd_);
        }
        else
        {
            lg(Warning, "read error,sockfd:%d,clientip:%s,clientport:%d", sockfd_, clientip_.c_str(), clientport_);
        }
        close(sockfd_);
    }
    void operator()()
    {
        run();
    }
    ~Task()
    {
    }

private:
    int sockfd_;
    string clientip_;
    uint16_t clientport_;
    Init it;
};

ThreadPool.hpp

cpp 复制代码
#include <iostream>
#include <vector>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <queue>
using namespace std;
struct ThreadInfo
{
    pthread_t tid;
    string name;
};
static const int defaultnum = 5;
template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void UnLock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void WakeUp()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    string GetThreadName(pthread_t tid)
    {
        for (auto &t : threads_)
        {
            if (t.tid == tid)
            {
                return t.name;
            }
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool *tp = static_cast<ThreadPool<T>*>(args);
        string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();
            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->UnLock();
            t();
        }
    }
    void Start()
    {
        int len = threads_.size();
        for (int i = 0; i < len; i++)
        {
            threads_[i].name = "thread -" + to_string(i);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this); // this指针指向的是调用成员函数的对象(ThreadPool)
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &in)
    {
        Lock();
        tasks_.push(in);
        WakeUp();
        UnLock();
    }
    static ThreadPool<T> *GetInstance() // 懒汉模式
    {
        if (tp_ == nullptr)//减少申请锁和释放锁的次数
        {
            pthread_mutex_lock(&lock_); // 避免出现多个线程抢夺同一份资源
            while (tp_ == nullptr)
            {
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }
        return tp_;
    }

private:
    ThreadPool(int num = defaultnum)
        : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }

private:
    vector<ThreadInfo> threads_;
    queue<T> tasks_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;
    static pthread_mutex_t lock_;
    static ThreadPool<T> *tp_; // 懒汉模式
};
template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
相关推荐
勇闯逆流河1 小时前
【Linux】Linux基础开发工具(git、dbg)
linux·运维·服务器·开发语言·c++·git
我爱学习好爱好爱1 小时前
ELK日志分析平台(五):Filebeat 部署与 Nginx 日志采集(输出至 Logstash)(基于Rocky Linux 9.6)
linux·nginx·elk
returnthem1 小时前
Docker数据卷
运维·docker·容器
繁华如雪亦如歌1 小时前
Linux:进程间通信
linux
芥子沫1 小时前
Memos捷径(Shortcuts)用法介绍
linux·服务器·windows
mengchanmian1 小时前
docker 国内云服务器开源镜像
运维·docker·容器
糟糕喔2 小时前
k8s运维-configmap和secret(4)
运维·容器·kubernetes
数据知道2 小时前
MongoDB自动化运维脚本:详细讲述日常维护任务批量化处理的实用技巧
运维·mongodb·自动化
会编程的土豆2 小时前
c语言时间戳从入门到精通
linux·c语言·算法