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

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中作些许改变,其他不变

输出的日志文件

相关推荐
星火开发设计2 小时前
枚举类 enum class:强类型枚举的优势
linux·开发语言·c++·学习·算法·知识
喜欢吃燃面7 小时前
Linux:环境变量
linux·开发语言·学习
Cisco_hw_zte9 小时前
小型网络中部署Aruba无线
网络
佑白雪乐10 小时前
<Linux基础第10集>复习前面内容
linux·运维·服务器
春日见10 小时前
自动驾驶规划控制决策知识点扫盲
linux·运维·服务器·人工智能·机器学习·自动驾驶
暮云星影10 小时前
四、linux系统 应用开发:UI开发环境配置概述 (三)
linux·ui·arm
迷途知返-11 小时前
服务器——那些年我踩过的坑
linux
landonVM12 小时前
Linux 上搭建 Web 服务器
linux·服务器·前端
学习中的DGR12 小时前
[极客大挑战 2019]Http 1 新手解题过程
网络·python·网络协议·安全·http