Linux 基于TCP实现服务端客户端通信(线程池)

线程池在客户端的连接到来前已经提前创建了一批线程,然后线程池将这些线程管理起来,当客户端的连接服务器之后,线程池就会唤醒一个线程,然后让这个线程直接去和客户端进行通信,当通信完成之后,这个线程又会去条件变量下休眠,所以线程池中的线程执行通信任务在这个过程中省去了创建和释放的成本,而多线程版当连接到来的时候,需要创建线程进行通信,当通信完成之后,需要释放线程,所以多线程相较于线程池仍旧很重,所以我们可以采用线程池版在多线程版的基础。

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.线程池版

由于线程池是提前创建一批线程,那么也就注定了线程池中线程的数量是有上限的,所以如果类似于上文的多线程版的服务器创建线程,然后让线程为客户端提供长服务,即while(true)一直进行通信,那么也就注定了这一个线程会被一直被一个客户端所占用,所使用,那么一旦客户端较多,超过了线程池的线程数量的上限,所以服务器就会出现对于客户端的连接请求不响应的情况,那儿这不是我们想要看到的

所以我们该如何解决呢?

既然长服务不行, 那么就让线程池中的线程提供短服务不就好了,什么是短服务?即客户端向服务端发送数据,服务端接收然后响应将处理完成的数据发送回给客户端,客户端接收到处理完成的数据,完成一次短服务,所以这就是短服务

那么我们就将一次短服务看作一个任务,这个短服务的执行速度很快,如果短服务的任务很多的情况下,所以也就意味着线程池中的多个线程都可以来执行这个短服务,所以线程池中的多个线程都可以被调用起来

如果客户端想要进行长时间通信那么怎么办?

很简单,那么将短服务的代码逻辑构成一个死循环,每次用户想要进行通信的时候就会发送一个短服务,如果想要进行多次通信那么就会发送多次短服务,所以短服务可以有效的避免线程池中的一个线程被长时间占用而造成的线程池中的线程被调用占用至上限进而造成的服务器对于客户端的连接不响应的情况

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]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        cerr << "socket create err" << endl;
        return 1;
    }
    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);
    //客户端发起connect请求时,操作系统会自动进行端口号的随机bind绑定
    int n=connect(sockfd,(struct sockaddr*)(&server),len);
    if(n<0)
    {
        perror("connect error");
        cerr<<"connect error..."<<endl;
        return 2;
    }
    string message;
    char inbuffer[4096];
    while(true)
    {
        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;
        }
        else
        {
            break;
        }
    }
    close(sockfd);
    return 0;
}

原来的客户端的思想为,创建一个套接字,连接到服务端之后,while循环的与服务端进行通信,但是我们现在的要求是当客户都连接上服务端之后,进行一次通信就断开连接,需要再次通信就需要重新创建套接字,然后进行连接。

所以对客户端进行修改,将创建套接字和连接放入在while循环中,然后write发送,read读取服务端发送回来的数据,最后关闭连接,完成一次短服务。

修改后:

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;
        }
        //客户端发起connect请求时,操作系统会自动进行端口号的随机bind绑定
        int n=connect(sockfd,(struct sockaddr*)(&server),len);
        if(n<0)
        {
            perror("connect error");
            cerr<<"connect error..."<<endl;
            return 2;
        }
        string message;
        char inbuffer[4096];
        cout << "Please Enter# ";
        getline(cin, message);

        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;
        }
        else
        {
            break;
        }
        close(sockfd);
    }
    return 0;
}

server.hpp

对于与server.hpp中的StartServer接口,在开始时应该创建线程池,将通信包装为一个任务,分发给线程进行处理。

cpp 复制代码
void StartServer()
    {
        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,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);
        }
    }

Task.hpp

将通信封装为一个任务类,也就是读取客户都发送过来的数据,然后将客户端发送过来的数据再发送回给客户端。

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include "log.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));
        if(n>0)
        {
            buffer[n]=0;
            cout<<"client say#"<<buffer<<endl;
            string echo_string="server say#";
            echo_string+=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_;
};

测试:

在客户端连接上去之后,发送一条信息服务端就会显示有新的连接到来。

2.断线重连

对客户端的连接如果因为服务端的异常,改进为如果服务端连接不上就进行多次connect连接,如果多次的connect连接都连接不上,才停止连接。

  1. 核心控制:以 do-while 循环为骨架,保证至少执行一次连接尝试。

  2. 状态机与计数:通过 is_reconnect 标志位与 cnt 次数计数器配合,决定是否进入下一轮重试。

  3. 退出条件:两个关键退出分支------连接成功( ret >= 0 时 break ),或重试耗尽( cnt 归零)。

改进后代码:

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;
}

服务端关闭了之后,客户端发送数据,读取read的返回值变为0,之后重新创建套接字,进入do...while循环,发送连接不上,进行5次重连。

测试1:

服务端断开连接,客户端重连5次退出

测试2:

服务端断开连接,客户端重连的时候,重新启动服务端,客户端连接上服务端,继续通信。

3.实现字典翻译

其实也就是对任务池里面的任务变为根据客户端发送的英文单词,返回中文意思。

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

加载 dict.txt 中的翻译词条,实现"单词/短语 -> 译文"的映射查询。

  • 存储结构:使用 std::unordered_map<std::string, std::string> ,这是 C++ 中最常用的哈希表,提供 O(1) 时间复杂度的查找。

Init()

打开文件:使用 std::ifstream 打开 ./dict.txt ,如果打开失败(文件不存在或路径错),调用 lg 日志函数输出致命错误并退出程序,通过 while(std::getline(in, line)) 循环读取文件的每一行,解析行数据:调用 Split 方法对每一行进行分割,如果分割成功,将键值对插入到 dict 哈希表中。

bool Split(const std::string line, std::string* part1, std::string* part2)

查找 : 的位置 pos 。如果找不到 : ,返回 false (解析失败)。通过 substr 截取前后两部分,并通过传出参数 part1 和 part2 返回。

Translation(const std::string& key)

在 dict 中查找传入的 key ,如果找到,返回对应的译文 iter->second 。如果找不到,返回字符串 "unknow" 。

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;
};

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;
};

使用Init类中的translation接口对客户端发送的英文进行翻译,放回中文意思。

测试:

源代码:

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;
}

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);
    }
    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:server client
server:main.cpp
	g++ -o $@ $^ -std=c++11 -lpthread -g
client:client.cpp
	g++ -o $@ $^ -std=c++11 -g
.PHONY:clean
clean:
	rm -f server 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"
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()
    {
        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 小时前
【Java 基础】变量全解:定义、命名规范、作用域与常量(附代码示例+面试题)
java·作用域·java基础·java入门·变量·常量·java面试题
mldlds2 小时前
【异常解决】Unable to start embedded Tomcat Nacos 启动报错
java·tomcat
代码探秘者2 小时前
【Java】final、finally、finalize 区别
java·开发语言
代码探秘者2 小时前
【Java】浅拷贝 VS 深拷贝:核心差异 + 实现方式 + 避坑指南
java·开发语言
盐水冰2 小时前
【Redis】学习(3)Redis的Java客户端
java·redis·学习
阿星仔6662 小时前
claude code switch安装使用指南:一键切换多Claude API
java
北京耐用通信2 小时前
耐达讯自动化CC linkie转Devicenet网关:架起三菱PLC与电导率仪跨协议“沟通之桥”
人工智能·物联网·网络协议·自动化·信息与通信
weixin199701080162 小时前
淘宝客商品详情页前端性能优化实战
java·前端·python·性能优化