【Linux | 网络】socket编程 - 使用TCP实现服务端向客户端提供简单的服务

发布于:2025-07-10 ⋅ 阅读:(17) ⋅ 点赞:(0)

在这里插入图片描述

一、Comm.hpp(公共数据)

#pragma once

enum {
    Socket_err = 1,
    Bind_err,
    Accept_err,
    Recvfrom_err
};

二、Log.hpp(日志)

#pragma once

#include <pthread.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>

using namespace std;

// 日志等级
enum
{
    Debug = 0, // 调试
    Info,      // 正常
    Warning,   // 警告
    Error,     // 错误,但程序并未直接退出
    Fatal      // 程序直接挂掉
};

enum
{
    Screen = 10, // 打印到显示器上
    OneFile,     // 打印到一个文件中
    ClassFile    // 按照日志等级打印到不同的文件中
};

string LevelToString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    default:
        return "Unknow";
    }
}

const char* default_filename = "log.";
const int default_style = Screen;
const char* defaultdir = "log";

class Log
{
public:
    Log()
        : style(default_style), filename(default_filename)
    {
        // mkdir(defaultdir,0775);
        pthread_mutex_init(&_log_mutex, nullptr);
    }

    void SwitchStyle(int sty)
    {
        style = sty;
    }

    void WriteLogToOneFile(const string& logname, const string& logmessage)
    {
        int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
        if (fd == -1)
            return;

        pthread_mutex_lock(&_log_mutex);
        write(fd, logmessage.c_str(), logmessage.size());
        pthread_mutex_unlock(&_log_mutex);

        close(fd);
    }

    void WriteLogToClassFile(const string& levelstr, const string& logmessage)
    {
        mkdir(defaultdir, 0775);

        string name = defaultdir;
        name += "/";
        name += filename;
        name += levelstr;

        WriteLogToOneFile(name, logmessage);
    }

    void WriteLog(int level, const string& logmessage)
    {
        switch (style)
        {
        case Screen:
        {
            pthread_mutex_lock(&_log_mutex);
            cout << logmessage;
            pthread_mutex_unlock(&_log_mutex);
        }
        break;
        case OneFile:
            WriteLogToClassFile("All", logmessage);
            break;
        case ClassFile:
            WriteLogToClassFile(LevelToString(level), logmessage);
            break;
        default:
            break;
        }
    }

    string GetTime()
    {
        time_t CurrentTime = time(nullptr);

        struct tm* curtime = localtime(&CurrentTime);
        char time[128];

        // localtime 的年是从1900开始的,所以要加1900, 月是从0开始的所以加1
        snprintf(time, sizeof(time), "%d-%d-%d %d:%d:%d",
            curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday,
            curtime->tm_hour, curtime->tm_min, curtime->tm_sec);

        return time;
        return "";
    }

    void LogMessage(int level, const char* format, ...)
    {
        char left[1024];
        string Levelstr = LevelToString(level).c_str();
        string Timestr = GetTime().c_str();
        string Idstr = to_string(getpid());
        snprintf(left, sizeof(left), "[%s][%s][%s] ",
            Levelstr.c_str(), Timestr.c_str(), Idstr.c_str());

        va_list args;
        va_start(args, format);
        char right[1024];
        vsnprintf(right, sizeof(right), format, args);

        string logmessage = left;
        logmessage += right;

        WriteLog(level, logmessage);

        va_end(args);
    }

    ~Log()
    {
        pthread_mutex_destroy(&_log_mutex);
    };

private:
    int style;
    string filename;

    pthread_mutex_t _log_mutex;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.SwitchStyle(Screen);
    }
    ~Conf()
    {
    }
};

Conf conf;

三、InetAddr.hpp(管理sockaddr_in相关信息)

#pragma once

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>

class InetAddr
{
public:
    InetAddr(struct sockaddr_in sock)
        :_sock(sock)
    {}

    std::string Ip()
    {
        return inet_ntoa(_sock.sin_addr);
    }

    uint16_t Port()
    {
        return ntohs(_sock.sin_port);
    }

    std::string PrintDebug()
    {
        std::string info = "[";
        info += Ip();
        info += ":";
        info += std::to_string(Port());
        info += "]";
        info += "# ";

        return info;
    }

    ~InetAddr()
    {}

private:
    struct sockaddr_in _sock;
};

四、NoCopy.hpp(防拷贝)

#pragma once

class Nocopy
{
public:
    Nocopy() {}
    ~Nocopy() {}
    Nocopy(const Nocopy&) = delete;
    const Nocopy& operator=(const Nocopy&) = delete;
};

五、Lockguard.hpp(自动管理锁)

#pragma once

#include <iostream>

class Mutex
{
public:
    Mutex(pthread_mutex_t* lock)
        :pmutex(lock)
    {}

    void Lock()
    {
        pthread_mutex_lock(pmutex);
    }

    void Unlock()
    {
        pthread_mutex_unlock(pmutex);
    }

    ~Mutex()
    {}
public:
    pthread_mutex_t* pmutex;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* lock)
        :mutex(lock)
    {
        mutex.Lock();
    }

    ~LockGuard()
    {
        mutex.Unlock();
    }
public:
    Mutex mutex;
};

六、Thread.hpp(封装线程)

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

using namespace std;

// typedef function<void()> func_t;
template<class T>
using func_t = function<void(T&)>;

template<class T>
class Thread
{
public:
    Thread(const string &threadname, func_t<T> func ,const T& data)
        : _pid(0), _threadname(threadname), _func(func), isrunning(false) , _data(data)
    {
    }

    // 线程需要执行的函数
    static void *ThreadRoutine(void *arg)
    {
        Thread *pt = (Thread *)arg;

        pt->_func(pt->_data);

        return nullptr;
    }

    // 线程开创建并执行
    bool Start()
    {
        int n = pthread_create(&_pid, nullptr, ThreadRoutine, this);
        if (n == 0)
        {
            isrunning = true;
            // cout << "is strat , is running : " << isrunning << endl;
            return true;
        }
        else
        {
            return false;
        }
    }

    // 线程等待
    bool Join()
    {
        if(!isrunning)  return false;
        return pthread_join(_pid, nullptr);
    }

    bool IsRunning()
    {
        return isrunning;
    }

    string ThreadName()
    {
        return _threadname;
    }

    ~Thread()
    {
    }

private:
    pthread_t _pid;
    string _threadname;
    bool isrunning;
    func_t<T> _func;
    T _data;
};

七、ThreadPool.hpp(线程池)

#pragma once

#include <string>
#include <queue>
#include <vector>
#include <pthread.h>
#include <functional>
#include "Thread.hpp"
#include "Log.hpp"
#include "LockGuard.hpp"

using namespace std;

const int default_threadnum = 3;

class ThreadDate
{
public:
    ThreadDate(const string &name)
        : threadname(name)
    {
    }
    ~ThreadDate()
    {
    }

public:
    string threadname;
};

template <class T>
class ThreadPool
{
public:
    static ThreadPool *GetInstance()
    {
        if (_psl == nullptr)
        {
            LockGuard lock(&_sig_lock);
            if (_psl == nullptr)
            {
                lg.LogMessage(Info, "create singleton is success\n");
                _psl = new ThreadPool<T>();
            }
        }
        return _psl;
    }

    static void DelInstance()
	{
		if (_psl)
		{
			LockGuard lock(&_sig_lock);
			if (_psl)
			{
				delete _psl;
				_psl = nullptr;
                
                lg.LogMessage(Info, "destroy singleton is success\n");
			}
		}
	}

    // 启动所有线程
    bool Start()
    {
        for (auto &t : _threads)
        {
            t.Start();
            lg.LogMessage(Info, "%s , is running...\n", t.ThreadName().c_str());
        }

        return true;
    }

    // 等待所有线程终止
    void Join()
    {
        for (auto &t : _threads)
        {
            t.Join();
        }
    }

    // 线程等待当前条件变量
    void Thread_Wait(const ThreadDate &td)
    {
        pthread_cond_wait(&_cond, &_mutex);
        lg.LogMessage(Debug, "no task , %s is sleeping...\n", td.threadname.c_str());
    }

    // 唤醒当前条件变量下的某个线程
    void Thread_Wakeup()
    {
        pthread_cond_signal(&_cond);
    }

    // 添加任务
    bool Push(T &in)
    {
        LockGuard lockguard(&_mutex);
        _q.push(in);
        Thread_Wakeup();

        return true;
    }

    // 线程需要执行的回调函数
    void ThreadRun(const ThreadDate &td)
    {
        while (1)
        {
            T t;

            // 取任务
            {
                LockGuard lockguard(&_mutex);

                while (_q.empty())
                {
                    Thread_Wait(td);
                    lg.LogMessage(Debug, "haven task , %s is wakeup\n", td.threadname.c_str());
                }
                t = _q.front();
                _q.pop();
            }
            // 执行任务
            t();
            // lg.LogMessage(Debug, "%s handler task %s done , result is %s\n",
            //               td.threadname.c_str(), t.PrintTask().c_str(), t.PrintResult().c_str());
        }
    }

private:
    ThreadPool(int num = default_threadnum)
        : _threadnum(num)
    {
        // 初始化锁和条件变量
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);

        // 创建线程
        for (int i = 0; i < _threadnum; i++)
        {
            string threadname = "thread-" + to_string(i + 1);
            ThreadDate td(threadname);
            // 由于Thread执行的是线程池的类内函数,而Thread调用的函数中并没有this指针
            // 所以这里就使用bind函数,调整一下Thread调用函数的参数,使函数可以多接收一个参数
            _threads.push_back(Thread<ThreadDate>(threadname, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td));
            lg.LogMessage(Info, "%s is create\n", threadname.c_str());
        }
    }

    // 销毁锁和条件变量
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    ThreadPool(const ThreadPool&) = delete;
    const ThreadPool& operator=(const ThreadPool&) = delete;

private:
    queue<T> _q;
    vector<Thread<ThreadDate>> _threads;
    int _threadnum;
    static ThreadPool<T> *_psl;
    static pthread_mutex_t _sig_lock;

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_psl = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::_sig_lock = PTHREAD_MUTEX_INITIALIZER;

八、dict.txt(配置文件、简单字典)

apple 苹果
book 书
cat 猫
dog 狗
eat 吃
fly 飞
happy 快乐
jump 跳
kiss 吻
love 爱
moon 月亮
night 夜
pen 笔
red 红色
run 跑
sad 悲伤
sing 唱歌
star 星星
tree 树
water 水
big 大的
boy 男孩
car 汽车
dance 跳舞
fast 快的
girl 女孩

九、Translate.hpp(提供翻译服务)

#pragma once

#include <unordered_map>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <string.h>
#include <error.h>

#include "Log.hpp"

const char* defalutpath = "resourse/dict.txt";
const static std::string Sep = " ";
const static std::string unknown = "unknown";

class Translate
{
public:
    Translate()
        :_dict_path(defalutpath)
    {
        Loaddict();
        Parse();
    }

    // 加载字典
    void Loaddict()
    {
        std::ifstream in(_dict_path.c_str(),std::ifstream::in);
        if(!in)
            lg.LogMessage(Fatal,"ifstream error , errno : %d , error string : %s\n",errno,strerror(errno));

        std::string line;
        while(getline(in,line))
        {
            _lines.push_back(line);
        }
    } 

    // 执行翻译
    std::string Execute(const std::string& word)
    {
        if(_dict.find(word) != _dict.end())
        {
            return _dict[word];
        }
        return "unknown";
    } 

    // 将读取到的一行中英文单词互译拆分为:英文和中文
    void Parse()
    {
        for(auto& line : _lines)
        {
            size_t pos = line.find(Sep);
            if(pos == std::string::npos)
            {
                continue;
            }
            std::string word = line.substr(0,pos);
            // 跳过空格
            while(line[pos] == ' ')
            {
                pos++;
            }
            std::string chinese = line.substr(pos);

            _dict.insert(std::make_pair(word,chinese));
        }
    } 

    // 用于测试
    void Debug()
    {
        for(auto& line:_lines)
        {
            std::cout << line << std::endl;
        }

        for(auto& tmp:_dict)
        {
            std::cout << tmp.first << " : " << tmp.second << std::endl;
        }
    }

    ~Translate()
    {}
private:
    std::string _dict_path;
    std::vector<std::string> _lines;
    std::unordered_map<std::string,std::string> _dict;
};

十、Daemon.hpp(使进程变为守护进程)

#pragma once

#include <signal.h>
#include <unistd.h>
#include <stdlib.h> 
#include <sys/types.h>
#include <fcntl.h>

const char* root = "/";
const char* dev_null = "/dev/null";

bool Daemon(bool nochdir, bool noclose)
{
    // 1、忽略可能引起程序异常退出的信号 SIGCHLD SIGPIPE
    signal(SIGCHLD,SIG_IGN);
    signal(SIGPIPE,SIG_IGN);
    // 2、创建子进程,让父进程退出,使得子进程不成为组长
    pid_t pid = fork();
    if(pid > 0) exit(0);
    // 3、设置自己成为一个新的会画,setsid
    setsid();
    // 4、每一个进程都有自己的CWD(当前工作路径),是否将当前进程的CWD改为根目录
    // ​	改为根目录以后,进程可以以绝对路径的方式找到操作系统中的文件
    if(nochdir)
        chdir(root);

    // 5、变成守护进程以后,就不需要与用户的输入、输出和错误进行关联了
    // ​	可以将它们全部关闭,但难免服务器中会有输入、输出和错误
    // ​	向关闭的文件描述符中写入可能会导致进程退出
    // ​	所以这里将它们关闭不是最优解,而是将它们重定向到/dev/null中
    // ​	因为写入到/dev/null的数据会被直接丢弃,而从/dev/null读取信息,会默认读取到文件结尾
    if(noclose)
    {
        int fd = open(dev_null,O_RDWR);
        if(fd > 0)
        {
            dup2(fd,0);
            dup2(fd,1);
            dup2(fd,2);
            close(fd);
        }
    }
    else  // 不推荐
    {
        close(0);
        close(1);
        close(2);
    }

    return true;
}

十一、TcpServer.hpp(V1~V4版服务端封装)

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <functional>

#include "Comm.hpp"
#include "Log.hpp"
#include "Nocopy.hpp"
#include "LockGuard.hpp"
#include "InetAddr.hpp"
#include "Thread.hpp"
#include "ThreadPool.hpp"

const static int default_backlog = 5;
using task_t = function<void(int,InetAddr&)>;

class TcpServer;

class ThreadDate
{
public:
    ThreadDate(TcpServer* pts,int sockfd,const InetAddr& addr)
        :_sockfd(sockfd),_pts(pts),_addr(addr)
    {}

    int Sockfd()
    {
        return _sockfd;
    }

    InetAddr& GetAddr()
    {
        return _addr;
    }

    TcpServer* GetPTcpserver()
    {
        return _pts;
    }
    ~ThreadDate()
    {}
private:
    int _sockfd;
    TcpServer* _pts;
    InetAddr _addr;
};

class TcpServer : public Nocopy
{   
public:
    TcpServer(uint16_t port)
        :_port(port),_isrunning(false)
    {}

    void Init()
    {
        // 创建套接字
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock < 0)
        {
            lg.LogMessage(Fatal,"create socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
            exit(Socket_err);
        }
        lg.LogMessage(Info , "create socket success , sockfd : %d\n",_listensock);

        int opt = 1;
        setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));

        // 填充网络信息,并绑定
        struct sockaddr_in ServerSockaddr;
        ServerSockaddr.sin_family = AF_INET;
        ServerSockaddr.sin_port = htons(_port);
        ServerSockaddr.sin_addr.s_addr = INADDR_ANY;
        socklen_t ServerLen = sizeof(ServerSockaddr);

        int n = bind(_listensock , (struct sockaddr*)&ServerSockaddr,ServerLen);
        if(n < 0)
        {
            lg.LogMessage(Fatal,"bind socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
            exit(Bind_err);
        }
        lg.LogMessage(Info , "bind socket success , sockfd : %d\n",_listensock);

        // 将listensock调节为监听模式
        int m = listen(_listensock,default_backlog);
        if(m < 0)
        {
            lg.LogMessage(Fatal,"listen socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
            exit(Listen_err);
        }
        lg.LogMessage(Info , "listen socket success , sockfd : %d\n",_listensock);
    }

    // v1、v2、v3版本Service
    // void Service(int sockfd)
    // {
    //     char buffer[1024];
    //     while(true)
    //     {
    //         int n = read(sockfd,buffer,sizeof(buffer) - 1);
    //         if(n > 0)
    //         {
    //             buffer[n] = 0;
    //             std::cout << "Client Say# " << buffer << std::endl;

    //             std::string response = buffer;
    //             int m = write(sockfd,response.c_str(),response.size());
                
    //         }
    //         else if(n == 0)
    //         {
    //             lg.LogMessage(Warning,"client quit\n");
    //             break;
    //         }
    //         else
    //         {
    //             lg.LogMessage(Fatal,"read socket error , errno : %d , error string : %s\n",errno,strerror(errno));
    //             break;
    //         }
    //     }
    // }

    // v4版本Service
    void Service(int sockfd,InetAddr& inetAddr)
    {
        char buffer[1024];
        while(true)
        {
            int n = read(sockfd,buffer,sizeof(buffer) - 1);
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout << inetAddr.PrintDebug() << buffer << std::endl;

                std::string response = buffer;
                int m = write(sockfd,response.c_str(),response.size());
                
            }
            else if(n == 0)
            {
                lg.LogMessage(Warning,"client quit\n");
                break;
            }
            else
            {
                lg.LogMessage(Fatal,"read socket error , errno : %d , error string : %s\n",errno,strerror(errno));
                break;
            }
        }
    }

    static void* HandlerRoutine(void* arg)
    {
        // 使主线程与新线程分离,使得主线程不需要等待新线程终止
        pthread_detach(pthread_self());

        ThreadDate* td = (ThreadDate*)arg;
        td->GetPTcpserver()->Service(td->Sockfd(),td->GetAddr());

        // 关闭文件描述符,delete ThreadDate
        close(td->Sockfd());
        delete td;

        return nullptr;
    }

    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            // 获取连接
            struct sockaddr_in ClientSockaddr;
            socklen_t ClientLen = sizeof(ClientSockaddr);
            int sockfd = accept(_listensock,CONV(&ClientSockaddr),&ClientLen);

            if(sockfd < 0)
            {
                lg.LogMessage(Warning,"accept socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
                break;
            }
            lg.LogMessage(Info , "accept socket success , get a new sockfd : %d\n",sockfd);

            // v3版本服务,忽略子进程退出时给父进程发送的信号
            signal(SIGCHLD,SIG_IGN);

            // 提供服务
            // v1版本,单进程版本
            // Service(sockfd);
            // close(sockfd);

            // v2版本,多进程版本 ,父进程负责接收连接,子进程(孙子进程)负责处理连接
            // 这里父进程和子进程需要关闭自己不需要的文件描述符
            // 防止多个客户端访问服务器时,导致父进程的文件描述符不够用的情况
            // int fd = fork();
            // if(fd < 0)
            // {
            //     lg.LogMessage(Fatal,"fork child process fail , errno : %d , error string : %s\n",errno,strerror(errno));
            //     break;
            // }
            // else if(fd == 0) // 子进程
            // {
            //     // 这里我们不想让父进程等待子进程退出
            //     // 我们创建一个孙子进程,子进程退出
            //     // 这样孙子进程变成了孤儿进程,被操作系统领养
            //     // 退出时,操作系统会回收孙子进程
            //     if(fork())  
            //         exit(0);

            //     Service(sockfd);
            //     close(sockfd);
                
            //     close(_listensock);
            // }
            // else // 父进程
            // {
            //     close(sockfd);
            // }

            // v3版本,多进程版本,忽略信号版本
            // 忽略子进程退出时给父进程发送的信号,父进程就不需要等待子进程退出了
            // int fd = fork();
            // if(fd < 0)
            // {
            //     lg.LogMessage(Fatal,"fork child process fail , errno : %d , error string : %s\n",errno,strerror(errno));
            //     break;
            // }
            // else if(fd == 0) // 子进程
            // {
            //     Service(sockfd);
            //     close(sockfd);
                
            //     close(_listensock);
            // }
            // else // 父进程
            // {
            //     close(sockfd);
            // }

            // v4版本,多线程版本
            // pthread_t pth;
            // ThreadDate* ptd = new ThreadDate(this,sockfd,InetAddr(ClientSockaddr));
            // pthread_create(&pth,nullptr,HandlerRoutine,ptd);
        }
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;
    int _listensock;
    bool _isrunning;
};

十二、TcpServer.hpp(最终版,V5版服务端封装)

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include <unordered_map>

#include "Comm.hpp"
#include "Log.hpp"
#include "Nocopy.hpp"
#include "LockGuard.hpp"
#include "InetAddr.hpp"
#include "Thread.hpp"
#include "ThreadPool.hpp"
#include "Translate.hpp"

const static int default_backlog = 5;
using callback_t = function<void(int,InetAddr&)>;  // 回调函数
using task_t = function<void(void)>;  

class TcpServer : public Nocopy
{   
public:
    TcpServer(uint16_t port)
        :_port(port),_isrunning(false)
    {}

    void Init()
    {
        // 创建套接字
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock < 0)
        {
            lg.LogMessage(Fatal,"create socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
            exit(Socket_err);
        }
        lg.LogMessage(Info , "create socket success , sockfd : %d\n",_listensock);

        int opt = 1;
        setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));

        // 填充网络信息,并绑定
        struct sockaddr_in ServerSockaddr;
        ServerSockaddr.sin_family = AF_INET;
        ServerSockaddr.sin_port = htons(_port);
        ServerSockaddr.sin_addr.s_addr = INADDR_ANY;
        socklen_t ServerLen = sizeof(ServerSockaddr);

        int n = bind(_listensock , (struct sockaddr*)&ServerSockaddr,ServerLen);
        if(n < 0)
        {
            lg.LogMessage(Fatal,"bind socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
            exit(Bind_err);
        }
        lg.LogMessage(Info , "bind socket success , sockfd : %d\n",_listensock);

        // 将listensock调节为监听模式
        int m = listen(_listensock,default_backlog);
        if(m < 0)
        {
            lg.LogMessage(Fatal,"listen socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
            exit(Listen_err);
        }
        lg.LogMessage(Info , "listen socket success , sockfd : %d\n",_listensock);

        // 启动线程池
        ThreadNS::ThreadPool<task_t>::GetInstance()->Start();
        // 添加默认任务
        RegisterFunc("defalutserver",bind(&TcpServer::DefalutServer,this,placeholders::_1,placeholders::_2));
    }

    // v4版本Service
    void Service(int sockfd,InetAddr& inetAddr)
    {
        char buffer[1024];
        while(true)
        {
            int n = read(sockfd,buffer,sizeof(buffer) - 1);
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout << inetAddr.PrintDebug() << buffer << std::endl;

                std::string response = buffer;
                int m = write(sockfd,response.c_str(),response.size());
                
            }
            else if(n == 0)
            {
                lg.LogMessage(Warning,"client quit\n");
                break;
            }
            else
            {
                lg.LogMessage(Fatal,"read socket error , errno : %d , error string : %s\n",errno,strerror(errno));
                break;
            }
        }
    }

    // 效果:| ping | translate | transform |
    // 获取任务列表
    void DefalutServer(int sockfd,InetAddr& inetaddr)
    {
        (void)inetaddr;  // 这里未使用inetaddr,强转防止警告
        string task_list = "| ";
        for(auto task : _task_list)
        {
            if(task.first != "defalutserver")
            {
                task_list += task.first;
                task_list += " | ";
            }
        }
        write(sockfd,task_list.c_str(),task_list.size());
    }

    // 读取客户端所需服务
    string Read(int sockfd,InetAddr& inetaddr)
    {
        char buffer[1024];
        int n = read(sockfd,buffer,sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = 0;
        }
        else if(n == 0)
        {
            lg.LogMessage(Warning,"client quit\n");
        }
        else
        {
            lg.LogMessage(Fatal,"read socket error , errno : %d , error string : %s\n",errno,strerror(errno));
        }
        return buffer;
    }

    // 任务转接
    void Routine(int sockfd,InetAddr& inetaddr)
    {
        _task_list["defalutserver"](sockfd,inetaddr);

        string type = Read(sockfd,inetaddr);
        lg.LogMessage(Info,"%s select %s\n",inetaddr.PrintDebug().c_str(),type.c_str());

        if(type == "ping")
        {
            _task_list[type](sockfd,inetaddr);
        }
        else if(type == "translate")
        {
            _task_list[type](sockfd,inetaddr);
        }
        else if(type == "transform")
        {
            _task_list[type](sockfd,inetaddr);
        }
        else
        {}

        close(sockfd);
    }

    // 注册任务
    void RegisterFunc(const string& type , callback_t cb)
    {
        _task_list[type] = cb;
    }

    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            // 获取连接
            struct sockaddr_in ClientSockaddr;
            socklen_t ClientLen = sizeof(ClientSockaddr);
            int sockfd = accept(_listensock,CONV(&ClientSockaddr),&ClientLen);

            if(sockfd < 0)
            {
                lg.LogMessage(Warning,"accept socket fail , errno : %d , error string : %s\n",errno,strerror(errno));
                break;
            }
            lg.LogMessage(Info , "accept socket success , get a new sockfd : %d\n",sockfd);

            // v5版本,线程池版本
            // 其他四个版本在TcpServer copy.hpp中
            
            // task_t task = bind(&TcpServer::Service,this,sockfd,InetAddr(ClientSockaddr));
            // 将任务添加到线程池中
            task_t task = bind(&TcpServer::Routine,this,sockfd,InetAddr(ClientSockaddr));
            ThreadNS::ThreadPool<task_t>::GetInstance()->Push(task);
        }
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;
    int _listensock;
    bool _isrunning;

    // 任务列表
    unordered_map<string,callback_t> _task_list;
};

十三、Main.cpp(服务端)

#include <iostream>
#include <string>
#include <memory>
#include <algorithm>
#include <cctype>
#include "TcpServer.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Translate.hpp"
#include "Daemon.hpp"

using namespace std;

Translate trans;

void Usage(const string& proc)
{
    cout << proc << " localport\n" << endl;
}

// 未来我们不知道我们的服务器在任意一个时刻是否是健康的
// 所以我们可以让客户端定期的向服务器发送最小请求,若服务器有回应,则说明服务正常
// 这种机制我们称之为心跳机制 
// ping服务,客户端发如何消息,服务端回复pong,表示服务器健康
void Ping(int sockfd,InetAddr& inetaddr)
{
    lg.LogMessage(Info,"%s select ping\n",inetaddr.PrintDebug().c_str());

    char buffer[1024];
    int n = read(sockfd,buffer,sizeof(buffer) - 1);
    if(n > 0)
    {
        string response = "Pong";
        write(sockfd,response.c_str(),response.size());
    }
    else if(n == 0)
    {
        lg.LogMessage(Warning,"client quit\n");
    }
    else
    {
        lg.LogMessage(Fatal,"read socket error , errno : %d , error string : %s\n",errno,strerror(errno));
    }
}

// 提供翻译服务,客户端发送一个单词,服务端返回单词的翻译
void Translate(int sockfd,InetAddr& inetaddr)
{
    lg.LogMessage(Info,"%s select translate\n",inetaddr.PrintDebug().c_str());
    
    char buffer[1024];
    int n = read(sockfd,buffer,sizeof(buffer) - 1);
    if(n > 0)
    {
        buffer[n] = 0;
        string word = buffer;
        string response = trans.Execute(word);
        write(sockfd,response.c_str(),response.size());
    }
    else if(n == 0)
    {
        lg.LogMessage(Warning,"client quit\n");
    }
    else
    {
        lg.LogMessage(Fatal,"read socket error , errno : %d , error string : %s\n",errno,strerror(errno));
    }
}

// 提供转大写服务,服务端将客户端发送的信息,全部转换为大写再发送给客户端
void TransForm(int sockfd,InetAddr& inetaddr)
{
    lg.LogMessage(Info,"%s select transform\n",inetaddr.PrintDebug().c_str());

    char buffer[1024];
    int n = read(sockfd,buffer,sizeof(buffer) - 1);
    if(n > 0)
    {
        buffer[n] = 0;
        string messages = buffer;
        string response(messages.size(),' ');
        std::transform(messages.begin(),messages.end(),response.begin(),[](unsigned char c){return toupper(c);});
        write(sockfd,response.c_str(),response.size());
    }
    else if(n == 0)
    {
        lg.LogMessage(Warning,"client quit\n");
    }
    else
    {
        lg.LogMessage(Fatal,"read socket error , errno : %d , error string : %s\n",errno,strerror(errno));
    }
}

int main(int argc , char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t ServerPort = stoi(argv[1]);

    Daemon(true,true);
    lg.SwitchStyle(OneFile);

    unique_ptr<TcpServer> uq = make_unique<TcpServer>(ServerPort);

    uq->RegisterFunc("ping",Ping);
    uq->RegisterFunc("translate",Translate);
    uq->RegisterFunc("transform",TransForm);

    uq->Init();
    uq->Start();

    return 0;
}

十四、TcpClient.cpp(客户端)

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include "Comm.hpp"

using namespace std;

const static int Retry_Count = 5;

void Usage(const string &proc)
{
    cout << proc << " serverip serverport\n" << endl;
}

// // v1~v4版本的客户端
// // v1~v4版本的服务器都是长服务,客户端发送什么消息,服务器添加部分消息后就直接转发服务
// bool VisitServer(const string &serverip, uint16_t serverport,int* count)
// {
//     int n = 0 , m = 0;
//     string buffer;
//     char inbuffer[1024];
//     bool ret = true;
//     // 填充服务端信息
//     struct sockaddr_in server;
//     server.sin_family = AF_INET;
//     server.sin_port = htons(serverport);
//     inet_pton(AF_INET, serverip.c_str(), &server.sin_addr.s_addr);

//     // 创建套接字
//     int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//     if (sockfd < 0)
//     {
//         cout << "create socket fail" << endl;
//         ret = false;
//         goto END;
//     }

//     // 建立连接
//     if (connect(sockfd, CONV(&server), sizeof(server)) != 0)
//     {
//         cout << "connect fail" << endl;
//         ret = false;
//         goto END;
//     }

//     *count = 1;

//     while(1)
//     {
//         // 向服务端发送信息
//         cout << "Please Enter# ";
//         getline(cin, buffer);
//         n = write(sockfd, buffer.c_str(), buffer.size());
//         if (n > 0)
//         {
//             m = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
//             if (m > 0)
//             {
//                 inbuffer[m] = 0;
//                 cout <<  "get a message : " <<  inbuffer << endl;
//             }
//             else 
//             {
//                 ret = false;
//                 goto END;
//             }
//         }
//         else
//         {
//             ret = false;
//             goto END;
//         }
//     }
// END:
//     close(sockfd);
//     return ret;
// }

// v5版本客户端
// 短服务,服务一次后就退出
bool VisitServer(const string &serverip, uint16_t serverport,int* count)
{
    int n = 0 , m = 0;
    string buffer;
    char inbuffer[1024];
    bool ret = true;
    // 填充服务端信息
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &server.sin_addr.s_addr);

    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        cout << "create socket fail" << endl;
        ret = false;
        goto END;
    }

    // 建立连接
    if (connect(sockfd, CONV(&server), sizeof(server)) != 0)
    {
        cout << "connect fail" << endl;
        ret = false;
        goto END;
    }

    *count = 1;

    // 获取服务列表
    m = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
    if (m > 0)
    {
        inbuffer[m] = 0;
        cout << "server list : " << inbuffer << endl;
    }
    else
    {
        ret = false;
        goto END;
    }

    // 选择服务
    cout << "Please Select Server# ";
    getline(cin, buffer);
    if(buffer == "quit")
    {
        ret = true;
        goto END;
    }
    n = write(sockfd, buffer.c_str(), buffer.size());

    // 向服务端发送信息
    cout << "Enter> ";
    getline(cin, buffer);
    n = write(sockfd, buffer.c_str(), buffer.size());
    if (n > 0)
    {
        m = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if (m > 0)
        {
            inbuffer[m] = 0;
            cout <<  inbuffer << endl;
        }
        else 
        {
            ret = false;
            goto END;
        }
    }
    else
    {
        ret = false;
        goto END;
    }

END:
    close(sockfd);
    return ret;
}




int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

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

    // 断线重连
    int count = 1;
    while(count <= Retry_Count)
    {
        if(VisitServer(serverip,serverport,&count))
        {
            break;
        }
        else
        {
            sleep(1);
            cout << "server offline , retrying... , count : " << count << endl;
            count++;
        }
    }

    if(count >= Retry_Count)
        cout << "server offline , retrying... , count : " << count << endl;

    return 0;
}

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到