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

目录

一、Comm.hpp(公共数据)

cpp 复制代码
#pragma once

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

二、Log.hpp(日志)

cpp 复制代码
#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相关信息)

cpp 复制代码
#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(防拷贝)

cpp 复制代码
#pragma once

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

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

cpp 复制代码
#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(封装线程)

cpp 复制代码
#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(线程池)

cpp 复制代码
#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(配置文件、简单字典)

cpp 复制代码
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(提供翻译服务)

cpp 复制代码
#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(使进程变为守护进程)

cpp 复制代码
#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版服务端封装)

cpp 复制代码
#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版服务端封装)

cpp 复制代码
#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(服务端)

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(客户端)

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

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。

希望大家以后也能和我一起进步!!🌹🌹

如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

相关推荐
博语小屋10 分钟前
进程初识之进程状态
linux
惜.己1 小时前
appium中urllib3.exceptions.LocationValueError: No host specified. 的错误解决办法
网络·appium
吉凶以情迁1 小时前
window服务相关问题探索 go语言服务开发探索调试
linux·服务器·开发语言·网络·golang
专注VB编程开发20年1 小时前
UDP受限广播地址255.255.255.255的通信机制详解
网络·udp·智能路由器
柏木乃一2 小时前
Linux初步认识与指令与权限
linux·运维·服务器·shell·权限
189228048612 小时前
NX947NX955美光固态闪存NX962NX966
大数据·服务器·网络·人工智能·科技
Joemt2 小时前
ubuntu源码编译安装cmake高版本、pybind11安装、crow使用
linux·运维·ubuntu
huohuopro3 小时前
在linux(ubuntu)服务器上安装NTQQ并使用
linux·ubuntu
Jooolin3 小时前
Ubuntu?Centos?还是 redhat?Linux 系统选哪个?
linux·ubuntu·ai编程