设计一个简单的网络计算器并将其守护进程化

Mutex.hpp
cpp 复制代码
#pragma once

#include <iostream>
#include <mutex>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }
    void Lock()
    {
        pthread_mutex_lock(&_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    pthread_mutex_t *Get()
    {
        return &_lock;
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }
private:
    pthread_mutex_t _lock;
};

class LockGuard
{
public:
    LockGuard(Mutex *_mutex):_mutexp(_mutex)
    {
        _mutexp->Lock();
    }
    ~LockGuard()
    {
        _mutexp->Unlock();
    }
private:
    Mutex *_mutexp;
};
Logger.hpp
cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <filesystem> // C++17 文件操作
#include <fstream>
#include <ctime>
#include <unistd.h>
#include <memory>
#include <sstream>
#include "Mutex.hpp"

// 规定出场景的日志等级
enum class LogLevel
{
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

// 日志转换成为字符串
std::string Level2String(LogLevel level)
{
    switch (level)
    {
    case LogLevel::DEBUG:
        return "Debug";
    case LogLevel::INFO:
        return "Info";
    case LogLevel::WARNING:
        return "Warning";
    case LogLevel::ERROR:
        return "Error";
    case LogLevel::FATAL:
        return "Fatal";
    default:
        return "Unknown";
    }
}

// 根据时间戳,获取可读性较强的时间信息
// 20XX-08-04 12:27:03
std::string GetCurrentTime()
{
    // 1. 获取时间戳
    time_t currtime = time(nullptr);

    // 2. 如何把时间戳转换成为20XX-08-04 12:27:03
    struct tm currtm;
    localtime_r(&currtime, &currtm);

    // 3. 转换成为字符串
    char timebuffer[64];
    snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
             currtm.tm_year + 1900,
             currtm.tm_mon + 1,
             currtm.tm_mday,
             currtm.tm_hour,
             currtm.tm_min,
             currtm.tm_sec);

    return timebuffer;
}

// 策略模式,策略接口
// 1. 刷新的问题 -- 假设我们已经有了一条完整的日志,string->设备(显示器,文件)
// 基类方法
class LogStrategy
{
public:
    // 不同模式核心是刷新方式的不同
    virtual ~LogStrategy() = default;
    virtual void SyncLog(const std::string &logmessage) = 0;
};

// 控制台日志策略,就是日志只向显示器打印,方便我们debug
// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
    ~ConsoleLogStrategy()
    {
    }
    void SyncLog(const std::string &logmessage) override
    {
        {
            LockGuard lockguard(&_lock);
            std::cout << logmessage << std::endl;
        }
    }

private:
    // 显示器也是临界资源,保证输出线程安全
    Mutex _lock;
};

// 默认路径和日志名称
const std::string logdefaultdir = "log";
const static std::string logfilename = "test.log";

// 文件日志策略
// 文件刷新
class FileLogStrategy : public LogStrategy
{
public:
    // 构造函数,建立出来指定的目录结构和文件结构
    FileLogStrategy(const std::string &dir = logdefaultdir,
                    const std::string filename = logfilename)
        : _dir_path_name(dir), _filename(filename)
    {
        LockGuard lockguard(&_lock);
        if (std::filesystem::exists(_dir_path_name))
        {
            return;
        }
        try
        {
            std::filesystem::create_directories(_dir_path_name);
        }
        catch (const std::filesystem::filesystem_error &e)
        {
            std::cerr << e.what() << "\r\n";
        }
    }
    // 将一条日志信息写入到文件中
    void SyncLog(const std::string &logmessage) override
    {
        {
            LockGuard lockguard(&_lock);
            std::string target = _dir_path_name;
            target += "/";
            target += _filename;
            // 追加方式
            std::ofstream out(target.c_str(), std::ios::app); // append
            if (!out.is_open())
            {
                return;
            }
            out << logmessage << "\n"; // out.write
            out.close();
        }
    }

    ~FileLogStrategy()
    {
    }

private:
    std::string _dir_path_name; // log
    std::string _filename;      // hello.log => log/hello.log
    Mutex _lock;
};

// 具体的日志类
// 1. 定制刷新策略
// 2. 构建完整的日志
class Logger
{
public:
    Logger()
    {
    }
    void EnableConsoleLogStrategy()
    {
        _strategy = std::make_unique<ConsoleLogStrategy>();
    }
    void EnableFileLogStrategy()
    {
        _strategy = std::make_unique<FileLogStrategy>();
    }

    // 内部类,实现RAII风格的日志格式化和刷新
    // 这个LogMessage,表示一条完整的日志对象
    class LogMessage
    {
    public:
        // RAII风格,构造的时候构建好日志头部信息
        LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
            : _curr_time(GetCurrentTime()),
              _level(level),
              _pid(getpid()),
              _filename(filename),
              _line(line),
              _logger(logger)
        {
            // stringstream不允许拷贝,所以这里就当做格式化功能使用
            std::stringstream ss;
            ss << "[" << _curr_time << "] "
               << "[" << Level2String(_level) << "] "
               << "[" << _pid << "] "
               << "[" << _filename << "] "
               << "[" << _line << "]"
               << " - ";
            _loginfo = ss.str();
        }
        // 重载 << 支持C++风格的日志输入,使用模版,表示支持任意类型
        template <typename T>
        LogMessage &operator<<(const T &info)
        {
            std::stringstream ss;
            ss << info;
            _loginfo += ss.str();
            return *this;
        }
        // RAII风格,析构的时候进行日志持久化,采用指定的策略
        ~LogMessage()
        {
            if (_logger._strategy)
            {
                _logger._strategy->SyncLog(_loginfo);
            }
        }

    private:
        std::string _curr_time; // 日志时间
        LogLevel _level;        // 日志等级
        pid_t _pid;             // 进程pid
        std::string _filename;
        int _line;

        std::string _loginfo; // 一条合并完成的,完整的日志信息
        Logger &_logger;      // 引用外部logger类, 方便使用策略进行刷新
    };
    // 故意拷贝,形成LogMessage临时对象,后续在被<<时,会被持续引用,
    // 直到完成输入,才会自动析构临时LogMessage,至此也完成了日志的显示或者刷新
    // 同时,形成的临时对象内包含独立日志数据
    // 未来采用宏替换,进行文件名和代码行数的获取
    LogMessage operator()(LogLevel level, std::string filename, int line)
    {
        return LogMessage(level, filename, line, *this);
    }
    ~Logger()
    {
    }

private:
    // 写入日志的策略
    std::unique_ptr<LogStrategy> _strategy;
};

// 定义全局的logger对象
Logger logger;

// 使用宏,可以进行代码插入,方便随时获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)

// 提供选择使用何种日志策略的方法
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()
InetAddr.hpp
cpp 复制代码
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include "Logger.hpp"

using namespace std;

#define Conv(addr) ((struct sockaddr *)&addr)

class InetAddr
{
private:
    void Net2Host()
    {
        _port = ntohs(_addr.sin_port);
        // _ip = inet_ntoa(_addr.sin_addr);
        char ipbuffer[64];
        inet_ntop(AF_INET, &(_addr.sin_addr.s_addr), ipbuffer, sizeof(ipbuffer));
        _ip = ipbuffer;
    }
    void Host2Net()
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = htons(_port);
        // _addr.sin_addr.s_addr = inet_addr(_ip.c_str());
        inet_pton(AF_INET, _ip.c_str(), &(_addr.sin_addr.s_addr));
    }

public:
    InetAddr(){}
    // 默认ip为INADDR_ANY(0.0.0.0)
    InetAddr(uint16_t port, const string ip = "0.0.0.0")
        : _ip(ip),
          _port(port)
    {
        Host2Net();
    }

    InetAddr(struct sockaddr_in &addr)
    {
        _addr = addr;
        Net2Host();
    }

    void Init(const struct sockaddr_in peer)
    {
        _addr = peer;
        Net2Host();
    }

    struct sockaddr *Addr()
    {
        return Conv(_addr);
    }

    string IP()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    bool operator==(const InetAddr &addr)
    {
        return _ip == addr._ip && _port == addr._port;
    }

    socklen_t Length()
    {
        return sizeof(_addr);
    }

    string ToString()
    {
        return _ip + "-" + to_string(_port);
    }

    ~InetAddr()
    {
    }

private:
    // 网络风格地址
    struct sockaddr_in _addr;

    // 主机风格地址
    string _ip;
    uint16_t _port;
};
Socket.hpp
cpp 复制代码
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include<functional>
#include "Logger.hpp"
#include "InetAddr.hpp"
using namespace std;

static int gbacklog = 16; // 设置默认的backlog
static const int gsockfd = -1;

// 设置错误码
enum
{
    OK,
    CREATE_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR
};

// 定义一个抽象类,父类中定义算法的骨架,将某些步骤的具体实现延迟到子类中
class Socket
{
public:
    // 编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,
    // 所以基类的析构函数加了vialtual修饰,派生类的析构函数就构成重写
    // 基类必须有虚析构函数
    virtual ~Socket() {}
    // 纯虚函数强制派生类重写虚函数
    virtual void CreatSocket() = 0;
    virtual void BindSocket(int port) = 0;
    virtual void ListenSocket() = 0;
    virtual shared_ptr<Socket> Accept(InetAddr *clientaddr) = 0;
    virtual bool Connect(InetAddr &peer) = 0;
    virtual int SockFd() = 0;
    virtual void Close() = 0;
    virtual ssize_t Recv(std::string *out) = 0;
    virtual ssize_t Send(const std::string &in) = 0;

public:
    void BuildListenSocketMethod(int port)
    {
        CreatSocket();
        BindSocket(port);
        ListenSocket();
    }

    void BuildClientSocketMethod()
    {
        CreatSocket();
    }
};

class TcpSocket : public Socket
{
public:
    TcpSocket() : _sockfd(gsockfd)
    {
    }
    TcpSocket(int sockfd) : _sockfd(sockfd)
    {
    }
    // override帮助用户检测是否重写
    void CreatSocket() override
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "creat socket error";
            exit(CREATE_ERR);
        }
        LOG(LogLevel::DEBUG) << "creat socket success";
    }
    void BindSocket(int port) override
    {
        InetAddr local(port);
        if (bind(_sockfd, local.Addr(), local.Length()) != 0)
        {
            LOG(LogLevel::FATAL) << "bind socket error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG) << "bind socket success";
    }
    void ListenSocket() override
    {
        if (listen(_sockfd, gbacklog) != 0)
        {
            LOG(LogLevel::FATAL) << "listen socket error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::DEBUG) << "listen socket success";
    }
    shared_ptr<Socket> Accept(InetAddr *clientaddr) override
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int clientfd = accept(_sockfd, (sockaddr *)&peer, &len);
        if (clientfd < 0)
        {
            LOG(LogLevel::FATAL) << "accept error";
            return nullptr;
        }
        clientaddr->Init(peer);
        return make_shared<TcpSocket>(clientfd);
    }
    bool Connect(InetAddr &peer) override
    {
        if (connect(_sockfd, peer.Addr(), peer.Length()) != 0)
        {
            LOG(LogLevel::FATAL) << "connect error";
            return false;
        }
        LOG(LogLevel::DEBUG) << "connect " << peer.ToString() << " success";
        return true;
    }
    int SockFd() override
    {
        return _sockfd;
    }
    void Close() override
    {
        if (_sockfd >= 0)
            close(_sockfd);
    }
    // 今天重点放在读,通过读,理解序列和反序列和自定义协议的过程
    ssize_t Recv(std::string *out) override
    {
        // 只读一次
        char buffer[1024];
        ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            *out += buffer; // 故意+=
        }
        return n;
    }
    ssize_t Send(const std::string &in)
    {
        return send(_sockfd, in.c_str(), in.size(), 0);
    }

    ~TcpSocket() {}

private:
    int _sockfd;
};
Protocol.hpp
cpp 复制代码
#pragma once
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
using namespace std;

// 协议就是双方约定好的结构化的数据
// Requst:客户端发送的请求
class Request
{
public:
    Request()
    {
        _x = _y = _oper = 0;
    }
    // 对要发送的数据进行序列化
    bool Serialize(string *out)
    {
        // 1. 构建 Json::Value 对象
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;
        // 2. 使用 StreamWriterBuilder
        Json::StreamWriterBuilder writer; // StreamWriter 的工厂
        writer["emitUTF8"] = true;        // 允许输出UTF-8
        *out = Json::writeString(writer, root);
        if (out->empty())
            return false;
        return true;
    }
    // 对要接受的数据进行反序列化
    bool Deserialize(string &s)
    {
        // 解析JSON 字符串
        Json::Reader reader;
        Json::Value root;
        // 从字符串中读取JSON 数据
        bool ret = reader.parse(s, root);
        if (!ret)
            return false;
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _oper = root["oper"].asInt();
        return true;
    }
    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    char Oper()
    {
        return _oper;
    }
    ~Request() {}
//不做private:,方便客户端访问
//private:
    int _x;
    int _y;
    char _oper;
};

// Response:服务器的应答
class Response
{
public:
    Response()
    {
        _result = _code = 0;
    }
    bool Serialize(string *out)
    {
        // 1. 构建 Json::Value 对象
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        // 2. 使用 StreamWriterBuilder
        Json::StreamWriterBuilder writer; // StreamWriter 的工厂
        writer["emitUTF8"] = true;        // 允许输出UTF-8
        *out = Json::writeString(writer, root);
        if (out->empty())
            return false;
        return true;
    }
    // 对要接受的数据进行反序列化
    bool Deserialize(string &s)
    {
        // 解析JSON 字符串
        Json::Reader reader;
        Json::Value root;
        // 从字符串中读取JSON 数据
        bool ret = reader.parse(s, root);
        if (!ret)
            return false;
        _result = root["result"].asInt();
        _code = root["code"].asInt();
        return true;
    }
    void SetResult(int r)
    {
        _result = r;
    }
    void SetCode(int c)
    {
        _code = c;
    }
    void Print()
    {
        cout << _result << "[" << _code << "]" << endl;
    }
    ~Response() {}

private:
    int _result;
    int _code; // 判断_result是否可信
};
// 定义json中的分隔符
static const string sep = "\r\n";

class Protocol
{
public:
    // 包装jsonstr便于后续解析
    //  jsonstr -> len\r\njsonstr\r\n
    static string Package(const string &jsonstr)
    {
        if (jsonstr.empty())
            return string();
        int jsonstr_len = jsonstr.size();
        return to_string(jsonstr_len) + sep + jsonstr + sep;
    }
    // 检查长度字符串是否符合标准
    static bool DigitSafeCheck(const std::string str)
    {
        for (int i = 0; i < str.size(); i++)
        {
            if (!(str[i] >= '0' && str[i] <= '9'))
                return false;
        }
        return true;
    }

    // 收到的报文有任意种可能
    //  len\r\njsonstr\r\n
    //  len\r\njsonstr\r\nlen\r\njsonstr\r\n
    //  len\r\njsonstr\r\nlen\r
    //  len\r\n
    //  len\r\njs
    //  len\r
    //  len
    //  origin_str: 从网络中读取上来的字符串,输入输出
    //  package: 输出参数,如果有完整的json报文,就返回
     // UnPack对收到的报文进行解析以获得完整的json字符串
    static int UnPack(string &origin_str, string *package)
    {
       //解析工作完成时返回jsonstr的长度,没有完成时返回0 解析出错返回-1
        if (!package)
            return -1;
        auto pos=origin_str.find(sep);// 从左向右
        if(pos==string::npos)
        return 0;
        //此时,我们至少获得了jsonstr的长度
        string json_len=origin_str.substr(0,pos);
        if(!DigitSafeCheck(json_len))
        return -1;
        int length=stoi(json_len);
        // 如果我得到了当前报文的长度
        // 根据协议,我可以推测出,一个完整报文的长度是多少
        // len\r\njsonstr\r\n
        int target_len=json_len.size()+sep.size()*2+length;
        //如果没有一个完整的报文,就返回继续读,直至获得完整的的报文
        if(origin_str.size()<target_len)
        return 0;
    ////////////////////////////////////////////////////////////
    //截止目前,可以确定origin_str内部,一定有一个完整的报文请求!且我们没有对origin_str做任何修改
        *package=origin_str.substr(pos+sep.size(),length);
        origin_str.erase(0, target_len);
        return package->size();
    }
};
Parse.hpp
cpp 复制代码
#pragma once
#include <functional>
#include "Protocol.hpp"
#include "Logger.hpp"

using handler_t = function<Response(Request &req)>;

// 只负责对报文进行各种解析工作
class Parser
{
public:
    Parser(handler_t handler)
        : _handler(handler)
    {
    }
    //解析报文
    string Parse(string &inbuffer)
    {
        LOG(LogLevel::DEBUG) << "inbuffer: \r\n"<< inbuffer;
        string send_str;
        // 接受到的字符串可能包含多条报文请求
        while (true)
        {
            std::string jsonstr;
            // 1. 解析报文
            int n = Protocol::UnPack(inbuffer, &jsonstr);
            //解析出错,直接退出
            if (n < 0)
                exit(0);
            // 未获得一条完整的报文或已经将inbuffer内所有完整报文处理完毕,退出循环
            else if (n == 0)
                break; 
            else
            {
                //解析出的一条完整jsonstr
                LOG(LogLevel::DEBUG) << "jsonstr: \r\n" << jsonstr;
                Request req;
                // 2. 反序列化
                if (!req.Deserialize(jsonstr))
                {
                    return string();
                }
                // 3. 根据response, 处理具体的业务
                Response resp = _handler(req);

                // 4. 对resp在进行序列化
                string resp_json;
                if (!resp.Serialize(&resp_json))
                {
                    return string();
                }

                // 5. 打包
                send_str += Protocol::Package(resp_json);
            }
        }
        //若未获得一条完整的报文,返回的是空串
        //若已经将inbuffer内所有完整报文处理完毕,返回的是打包好的全部答复
        return send_str;
    }

    ~Parser() {}

private:
    handler_t _handler;
};
Calculator.hpp
cpp 复制代码
#pragma once

#include "Protocol.hpp"
#include <iostream>
#include <string>

class Calculator
{
public:
    Calculator() {}
    //Request进,Response出
    Response Exec(Request &req)
    {
        Response resp;
        switch (req.Oper())
        {
        case '+':
            resp.SetResult(req.X() + req.Y());
            break;
        case '-':
            resp.SetResult(req.X() - req.Y());
            break;
        case '*':
            resp.SetResult(req.X() * req.Y());
            break;
        case '/':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(1); // 1:div 0
            }
            else
            {
                resp.SetResult(req.X() / req.Y());
            }
        }
        break;
        case '%':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(2); // 2:mod 0
            }
            else
            {
                resp.SetResult(req.X() % req.Y());
            }
        }
        break;
        default:
            resp.SetCode(3); // 3: 非法操作
            break;
        }
        return resp;
    }
    ~Calculator() {}
};
TcpServer.hpp
cpp 复制代码
#pragma once
#include <iostream>
#include "Socket.hpp"
#include "InetAddr.hpp"

using callback_t = function<string(string &)>;

class TcpServer
{
public:
    TcpServer(int port, callback_t cb)
        : _port(port),
          _cb(cb),
          _listensocket(make_unique<TcpSocket>())
    {
        _listensocket->BuildListenSocketMethod(_port);
    }
    void HandlerRequest(shared_ptr<Socket> socket, InetAddr &peer)
    {
        string buffer;
        while (true)
        {
            ssize_t n = socket->Recv(&buffer);
            if (n > 0)
            {
                // 协议相关!!!
                // 1. 你怎么确定buffer里面有至少一个完整的报文呢?
                // 2. 如何把这个完整的报文,交给上层呢?
                // 3. 上层拿到了一个报文,该如何处理呢??
                LOG(LogLevel::DEBUG) << peer.ToString() << "# " << buffer;
                // 如何处理(检测? 解包? 序列..)收到的数据,和底层tcpserver没有关系
                string send_str = _cb(buffer);
                //如果返回的send_str是空串,则说明读到的inbuffer中尚未包含一条完整的报文,需继续读
                if (send_str.empty())
                    continue;
                socket->Send(send_str);
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.ToString() << " quit, me too!";
                break;
            }
            else
            {
                LOG(LogLevel::DEBUG) << peer.ToString() << " read error, quit!";
                break;
            }
        }
        socket->Close();
    }
    void Run()
    {
        while (true)
        {
            InetAddr clientaddr;
            auto clientsocket = _listensocket->Accept(&clientaddr);
            if (!clientsocket)
                continue;
            LOG(LogLevel::DEBUG) << "获取一个新连接: " << clientaddr.ToString()
                                 << ", sockfd : " << clientsocket->SockFd();
        //如果不fork,主线程去处理请求时其他客户端来发起连接就没有办法及时接受了
            if (fork() == 0)
            {
                _listensocket->Close();
                HandlerRequest(clientsocket, clientaddr);
                exit(0);
            }
            clientsocket->Close();
        }
    }

    ~TcpServer() {}

private:
    int _port;
    unique_ptr<Socket> _listensocket;
    callback_t _cb;
};
Server.cpp
cpp 复制代码
//三个互相解耦的模块分别对应OSI中上三层中不同的层级
#include "Calculator.hpp" // 业务  应用层
#include "Parser.hpp" // 报文解析,序列反序列化等 表示层
#include "TcpServer.hpp" // 网络通信开断连接 会话层

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " localport" << std::endl;
}

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

    EnableConsoleLogStrategy();

     // 计算器对象
    unique_ptr<Calculator> cal = make_unique<Calculator>();

    // 协议解析模块
    unique_ptr<Parser> parser = make_unique<Parser>([&cal](Request &req)->Response{
        return cal->Exec(req);
    });

    // 网络通信模块
    uint16_t serverport = stoi(argv[1]);
    std::unique_ptr<TcpServer> tsock = make_unique<TcpServer>(
        serverport, [&parser](string &inbuffer)->string{
            return parser->Parse(inbuffer);
    });

    tsock->Run();

    return 0;
}
Client.cpp
cpp 复制代码
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include "InetAddr.hpp"
#include "Protocol.hpp"
using namespace std;
void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " serverip serverport" << std::endl;
}

// ./Client serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    EnableConsoleLogStrategy();

    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);
    // 创建socket
    unique_ptr<Socket> sockptr = make_unique<TcpSocket>();
    sockptr->BuildClientSocketMethod();
    InetAddr server(serverport, serverip);
    // 连接
    if (sockptr->Connect(server))
    {
        string inbuffer;
        while (true)
        {
            // 1. 构建请求
            Request req;
            cout << "Please Enter X: ";
            cin >> req._x;
            cout << "Please Enter Y: ";
            cin >> req._y;
            cout << "Please Enter Operator: ";
            cin >> req._oper;

            // 2. 序列化
            string jsonstr;
            req.Serialize(&jsonstr);

            cout << "jsonstr: \r\n"
                 << jsonstr << endl;

            // 3. 打包
            // len\r\njsonstr\r\n
            string sendstr = Protocol::Package(jsonstr);

            cout << "sendstr:\r\n"
                 << sendstr << endl;

            // 4. 发送
            sockptr->Send(sendstr);

            // 5. 接收
            sockptr->Recv(&inbuffer);

            // 6. 报文解析
            string package;
            int n = Protocol::UnPack(inbuffer, &package);
            if (n > 0)
            {
                Response resp;
                // 7. 反序列化
                bool r = resp.Deserialize(package);
                if (r)
                {
                    resp.Print();
                }
            }
        }
    }
    return 0;
}
Makefile
cpp 复制代码
.PHONY:all
all:Server Client

Client::Client.cpp
	g++ -o $@ $^ -std=c++17 -ljsoncpp 

Server:Server.cpp
	g++ -o $@ $^ -std=c++17 -ljsoncpp

.PHONY:clean
clean:
	rm -f Server Client 

守护进程化

Daemon.hpp
cpp 复制代码
#pragma once

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

void Daemon()
{
    // 1. 忽略可能引起程序异常退出的信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2. 让进程不要是组长
    if (fork() > 0)
        exit(0);

    // 3. 每一个进程都有自己的CWD,建议将当前进程的CWD 更改成为/根目录
    chdir("/");
    
    // 4. 将自己设计成为新的会话,后面的代码其实是子进程在走
    setsid();

    // 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了 两种方法
    // a. 关闭 b. 标准输入,标准输出,标准错误 -> 重定向 -> /dev/null(最佳实践)
    int fd = open("/dev/null", O_RDWR);
    if(fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
    }
}

server.cpp中作些许改变,其他不变

输出的日志文件

相关推荐
中议视控16 分钟前
可编程网络中央控制系统主机通过红外发射棒控制空调电视等红外设备
网络·物联网·5g
No8g攻城狮30 分钟前
【Linux】Windows11 安装 WSL2 并运行 Ubuntu 22.04 详细操作步骤
linux·运维·ubuntu
XiaoFan0121 小时前
免密批量抓取日志并集中输出
java·linux·服务器
souyuanzhanvip1 小时前
ServerBox v1.0.1316 跨平台 Linux 服务器管理工具
linux·运维·服务器
数据安全科普王2 小时前
打破中心枷锁:P2P网络如何用“去中心化”重构互联网通信
网络·去中心化·p2p
爱吃烤鸡翅的酸菜鱼2 小时前
CANN ops-nn激活函数与池化算子深度解析
网络·开源·aigc
HalvmånEver2 小时前
Linux:线程互斥
java·linux·运维
番茄灭世神3 小时前
Linux应用编程介绍
linux·嵌入式
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [drivers][mmc][mmc_sdio]
linux·笔记·学习
Forsete3 小时前
LINUX驱动开发#9——定时器
linux·驱动开发·单片机