网络计算器实现 - 自定义套接字+序列化+守护进程

目录

  • [1. 网络计算器整体逻辑](#1. 网络计算器整体逻辑)
  • [2. 重点步骤实现思想](#2. 重点步骤实现思想)
    • [2.1 自己定制传输协议](#2.1 自己定制传输协议)
    • [2.2 关于流式数据的处理](#2.2 关于流式数据的处理)
  • [3. 完整版代码实现](#3. 完整版代码实现)
    • [1. 服务端实现 main.cc](#1. 服务端实现 main.cc)
    • [2. 客户端实现 TcpClient.cc](#2. 客户端实现 TcpClient.cc)
    • [3. 套接字自定义实现 Socket.hpp](#3. 套接字自定义实现 Socket.hpp)
    • [4. 计算器实现 NetCal.hpp](#4. 计算器实现 NetCal.hpp)
    • [5. 序列化与反序列化&编解码实现 Protocol.hpp](#5. 序列化与反序列化&编解码实现 Protocol.hpp)
    • [6. 自定义守护进程化 Daemon.hpp](#6. 自定义守护进程化 Daemon.hpp)
  • [4. 功能验证](#4. 功能验证)
  • [5. 完整代码](#5. 完整代码)

1. 网络计算器整体逻辑

1. 服务端执行流程main.cc

  1. 建立守护进程(调用自定义Daemon.hpp中的Daemon类)
  1. 忽略IO, 子进程退出等待相关的信号

  2. 父进程直接结束

  3. 进程组的组长(原父进程被杀死了),运行到此处的只能是变成孤儿的子进程,并且子进程的新父进程是"1"。

  4. 取消守护进程和键盘、显示器的关联

  1. 启用日志输出到文件模式
  2. 实例化计算器的类cal(NetCal.hpp中的Cal类)
  1. 在计算器类中实例化应答类Response(Protocol.hpp中的Response类)
  2. 实例化cal计算器类
  1. 实例化协议层的类protocol(Protocol.hpp中的Protocol类)

传入lambda函数给Protocol类中的_func(lambda函数创建Request &req对象,执行cal类中的Execute(req)函数之后返回)

  1. 实例化服务器类tsvr(TcpServer.hpp中的TcpServer类)
  1. 传入两个参数:一是端口号,二是lambda函数给TcpServer类中的_service(lambda函数执行protocol中的GetRequest函数)
  1. 调用tsvr的Start()函数运行程序
  1. TcpServer tsvr对象中的Start()函数:进行网络字节序转换之后,调用Accept函数进行阻塞等待接收链接。连接成功之后:关闭子进程的套接字文件描述符,创建孙子进程(孤儿)并执行_service()函数
  2. _service就是:调用protocol对象的GetRequest函数,来获取请求。(在Protocol.hpp中)
    • 循环执行sock套接字中的Recv函数,将接收到的数据存放在buffer_queue数据缓冲区中
    • 对数据进行解码、反序列化
    • 执行之前给protocol的_func传入的lambda函数:调用Cal类中的Execute函数计算结果并返回一个Response的对象resp
    • 将返回的resp结果对象序列化、编码并发送给客户端

2.客户端执行流程TcpClient.cc

  1. 创建客户端套接字(使用Socket类创建其子类TcpSocket的套接字,调用基类函数实现套接字的一系列基础创建功能)
  2. 与服务端通过Connect建立连接
  3. 实例化协议层的类protocol(Protocol.hpp中的Protocol类)
  1. 客户端的protocol协议不需要传入函数,因为客户端使用的Procotol类中的函数GetResponse函数没有使用_func函数

  2. 建立resp_buffer字符串接收缓冲区

  1. 进入while循环
  1. 调用TcpClient.cc文件中的GetDataFromStdin函数,输入需要进行计算的变量和计算方法(加减乘除)

  2. 调用protocol对象中的BuildRequestString函数,进行序列化和编码

  3. 通过client->Send给服务端发送请求

  4. 调用protocol中的GetResponse函数等待获取应答,GetResponse函数中:

    • 实例化Response resp对象,准备接收解码&反序列化之后的结果信息

    • 调用sock中的Recv函数阻塞等待应答信息,并使用resp_buffer缓冲区接收应答信息

    • 将缓冲区中的数据进行解码&反序列化,将结果传给输出型参数resp

  5. 调用resp.ShowResult函数进行数据输出

2. 重点步骤实现思想

2.1 自己定制传输协议

使用C++的jsoncpp库,将需要传输的数据转换成json数据,再对数据进行编码(添加数据长度和\r\n)

2.2 关于流式数据的处理

  • 你如何保证你每次读取就能读完请求缓冲区的所有内容?答:在Protocol.hpp中的Protocol类中定义的GetResponse和GetRequest函数中的解码读取步骤使用while循环,直到读取的不是一条完整数据为止!(完整数据,指的就是2.1所示)
  • 你怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢?答:通过多次判断,及字符串长度计算得到是否是2.1所示的一条完整数据请求

所以,处理TCP缓冲区中的数据,一定要保证正确处理请求。完整的处理过程应该是:

3. 完整版代码实现

1. 服务端实现 main.cc

TcpServer.hpp

cpp 复制代码
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>

using namespace SocketModule;
using namespace LogModule;

using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;

// 主要解决:连接问题,IO通信的问题
// 细节:TcpServer,需不需要关心自己未来传递的信息是什么?不需要关心
// 网络版本的计算器,长服务

class TcpServer
{
public:
    TcpServer(uint16_t port, ioservice_t service) 
    : _port(port),
    _listensockptr(std::make_unique<TcpSocket>()),
    _isrunning(false),
    _service(service)
    {
        // TcpSocket类将 套接字创建、绑定、监听封装,调用BuildTcpSocketMethod函数即可完成以上三步
        _listensockptr->BuildTcpSocketMethod(_port);
    }
    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            InetAddr client;
            auto sock = _listensockptr->Accept(&client);
            if(sock == nullptr)
            {
                continue;
            }
            LOG(LogLevel::DEBUG) << "accept success ...";

            // sock && client
            pid_t id = fork();
            if(id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error ...";
                exit(FORK_ERR);
            }
            else if(id == 0)
            {
                // 子进程 -> listensock
                _listensockptr->Close();
                if(fork() > 0)
                    exit(OK);
                _service(sock, client);
                exit(OK);
            }
            else
            {
                // 父进程 -> sock
                sock->Close();
                pid_t rid = ::waitpid(id, nullptr, 0);
                (void)rid;
            }
            _isrunning = false;
        }
    }
    ~TcpServer() {}

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensockptr;
    bool _isrunning;
    ioservice_t _service;
};

main.cc

cpp 复制代码
#include "NetCal.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include "Daemon.hpp"
#include <memory>

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

// 我的代码为什么要这一样写?分层解耦!
// ./tcpserver 8080

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::cout << "服务器已经启动,已经是一个守护进程了" << std::endl;

    Daemon(0, 0);
    // daemon(1, 1);

    // Enable_Console_Log_Strategy();
    Enable_File_Log_Strategy();


    // 1.顶层
    std::unique_ptr<Cal> cal = std::make_unique<Cal>();

    // 2.协议层
    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>(
        [&cal](Request &req)->Response
        {
            return cal->Execute(req);
        });

    // 3.服务器层
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]),
        [&protocol](std::shared_ptr<Socket> &sock, InetAddr &client)
        {
            protocol->GetRequest(sock, client);
        });
    
    tsvr->Start();
    
    return 0;
}

2. 客户端实现 TcpClient.cc

  1. 创建套接字

  2. 向目标服务器发起连接

  3. 连接成功:通过while循环进行消息的发送和接收

TcpClient.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"

// 1.创建套接字
// 2.向目标服务器发起连接
// 3.连接成功:通过while循环进行消息的发送和接收

using namespace SocketModule;

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

// 获取输入信息,使用输出型参数
void GetDataFromStdin(int *x, int *y, char *oper)
{
    std::cout << "Please Enter x: ";
    std::cin >> *x;
    std::cout << "Please Enter y: ";
    std::cin >> *y;
    std::cout << "Please Enter oper: ";
    std::cin >> *oper;
}

// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    std::shared_ptr<Socket> client = std::make_unique<TcpSocket>();
    client->BuildTcpClientSocketMethod();

    if(client->Connect(server_ip, server_port) != 0)
    {
        // 成功
        std::cerr << "Connect error" << std::endl;
        exit(CONNECT_ERR);
    }

    std::unique_ptr<Protocol> protocol =  std::make_unique<Protocol>();
    std::string resp_buffer;
    // 连接服务器成功

    while(true)
    {
        // 1.从标准输入中获取数据
        int x, y;
        char oper;
        GetDataFromStdin(&x, &y, &oper);

        // 2.构建一个请求(添加数据长度之后的完整报文,即可以直接发送的字符串)
        std::string req_str = protocol->BuildRequestString(x, y, oper);

        // 3.发送请求
        client->Send(req_str);

        // 4.获取应答
        Response resp;
        bool res = protocol->GetResponse(client, resp_buffer, &resp);
        if(res == false)
            break;

        // 5.显示结果
        resp.ShowResult();
    }
    client->Close();
    return 0;
}

3. 套接字自定义实现 Socket.hpp

Socket.hpp

  • 通过基类将子类重写(多态)
  • 基类通过一个函数,调用套接字创建、绑定、监听函数,一站式实现套接字创建与配置。
cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog = 16;
    // 模板方法模式
    // 积累socket,大部分方法,都是纯虚方法
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int backlog) = 0;
        virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;
        virtual void Close() = 0;
        virtual int Recv(std::string *out) = 0;
        virtual int Send(const std::string &message) = 0;
        virtual int Connect(const std::string &server_ip, uint16_t port) = 0;

    public:
        // 执行服务端的: 套接字创建、绑定、监听功能
        void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
    };
    const static int defaultfd = -1;

    // 创建Tcp通信Socket类
    class TcpSocket : public Socket
    {
    public:
        TcpSocket() : _sockfd(defaultfd)
        {}
        TcpSocket(int fd) : _sockfd(fd)
        {}
        ~TcpSocket() {}

        // 重写SocketOrDie套接字创建
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if(_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "socket error";
            }
            LOG(LogLevel::INFO) << "socket success";
        }
    
        // 重写BindOrDie套接字地址绑定
        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);   // 创建服务端套接字地址sockaddr_in
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if(n < 0)
            {
                LOG(LogLevel::FATAL) << "bind error";
                exit(BIND_ERR);
            }
            LOG(LogLevel::INFO) << "bind success";
        }

        // 重写ListenOrDie套接字监听
        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if(n < 0)
            {
                LOG(LogLevel::FATAL) << "listen error";
                exit(LISTEN_ERR);
            }
            LOG(LogLevel::INFO) << "listen success";
        }

        // 接收套接字地址
        std::shared_ptr<Socket> Accept(InetAddr *client) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = ::accept(_sockfd, CONV(peer), &len);
            if(fd < 0)
            {
                LOG(LogLevel::WARNING) << "accept warning ...";
                return nullptr;
            }
            client->SetAddr(peer);
            return std::make_shared<TcpSocket>(fd);
        }
        // n == read 的返回值 // out是输出型参数
        int 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;
        }
        int Send(const std::string &message) override
        {
            return send(_sockfd, message.c_str(), message.size(), 0);
        }
        // 关闭套接字文件描述符
        void Close() override
        {
            if(_sockfd >= 0)
                ::close(_sockfd);
        }
        // 客户端与服务端建立连接
        int Connect(const std::string &server_ip, uint16_t port) override
        {
            InetAddr server(server_ip, port);
            return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
        }

    private:
        int _sockfd;    // _sockfd
    };
}

4. 计算器实现 NetCal.hpp

  • 注意除法和求余数,两个计算时要考虑除0的问题

NetCal.hpp

cpp 复制代码
#pragma once
#include "Protocol.hpp"
#include <iostream>

class Cal
{
public:
    Response Execute(Request &req)
    {
        Response resp(0, 0);
        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); // 除零错误
            }
            else
            {
                resp.SetResult(req.X() / req.Y());
            }
        }
        break;
        case '%':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(2); // mod 0 错误
            }
            else
            {
                resp.SetResult(req.X() % req.Y());
            }
        }
        break;
        default:
            resp.SetCode(3); // 非法操作
            break;
        }
        return resp;
    }
};

5. 序列化与反序列化&编解码实现 Protocol.hpp

  • 三个类:请求类、应答类、序列化&反序列化类
cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>
#include <functional>
#include "Socket.hpp"

// 实现一个网络版本的计算器

using namespace SocketModule;

// 约定好各个字段的含义:本质就是定好"协议"
// 如何要做序列化和反序列化
// 1.我们自己写(怎么做) ---> 往往不具备很好的扩展性
// 2.使用现成的方案(这个是我们要写的) ---> json -> josncpp

// 请求类 // client -> server
// 1.请求信息 序列化
// 2.请求信息 反序列化
// 3.返回各个变量
class Request
{
public:
    Request() {}
    Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
    {
    }
    // 1.请求信息 序列化
    std::string Serialize()
    {
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;

        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s; // 返回序列化之后的字符串
    }
    // 2.请求信息 反序列化
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        // 将in字符串中的内容,反序列化(解析)到root中
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
        }
        return ok;
    }
    ~Request() {}
    // 3.返回各个变量
    int X() { return _x; }
    int Y() { return _y; }
    char Oper() { return _oper; }

private:
    int _x;
    int _y;
    char _oper; // 计算符号
};

// 应答类 server -> client
// 1.应答信息 序列化
// 2.应答信息 反序列化
// 3.输出型参数,返回结果
class Response
{
public:
    Response() {}
    Response(int result, int code) : _result(result), _code(code)
    {
    }
    // 1.应答信息 序列化
    std::string Serialize()
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        Json::FastWriter writer;
        return writer.write(root);
    }
    // 2.应答信息 反序列化
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
        }
        return ok;
    }
    ~Response() {}
    // 3.输出型参数,返回结果
    void SetResult(int res)
    {
        _result = res;
    }
    void SetCode(int code)
    {
        _code = code;
    }
    void ShowResult()
    {
        std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
    }

private:
    int _result; // 运算结果,无法区分清除是应答是: 计算结果,还是异常值
    int _code;   // 0:success, 1,2,3,4 -> 不同的预算暖异常情况
};

const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>;

// 协议 (基于TCP)需要解决两个问题
// 1.request和response必须得有序列化和反序列化功能
// 2.你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)

// Protocol协议功能
// 1.编码
// 2.解码
// 3.接收请求处理后回传结果
// 4.发送请求并接收结果
class Protocol
{
public:
    Protocol()
    {
    }
    Protocol(func_t func) : _func(func)
    {
    }
    // 1.编码格式
    std::string Encode(const std::string &jsonstr)
    {
        std::string len = std::to_string(jsonstr.size());
        return len + sep + jsonstr + sep;
    }
    // 编码格式: 字符串长度 + \r\n + 字符串 + \r\n
    // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n

    // 5
    // 50
    // 50\r
    // 50\r\n
    // 50\r\n{"x": 10, "
    // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
    // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n50\r\n{"x": 10, "y" : 20, "ope
    //.....
    // packge故意是&
    // a. 判断报文完整性
    // b. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个
    // 2.解码格式
    bool Decode(std::string &buffer, std::string *package)
    {
        // 1.报文长度len不完整,则退出
        ssize_t pos = buffer.find(sep);
        if (pos == std::string::npos) // 没有找到第一个\r\n
            return false;

        std::string package_len_str = buffer.substr(0, pos);
        int package_len_int = std::stoi(package_len_str);

        // 2.buffer一定有长度,但是不一定是完整的报文,还需要继续判断
        int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
        if (buffer.size() < target_len) // 报文不完整 len\r\n后面的信息不完整
            return false;

        // 3.buffer一定至少有一个完整的报文
        // 读取完整的一条报文数据:从数据头读取len个字符
        // *package为输出型参数
        *package = buffer.substr(pos + sep.size(), package_len_int);
        buffer.erase(0, target_len); // 头删读取完的字符串
        return true;
    }

    // 3.接收请求处理后回传结果
    void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 读取
        std::string buffer_queue; // 整个服务端的数据缓冲区
        while (true)
        {
            int n = sock->Recv(&buffer_queue); // 我认为: Recv函数应该返回buffer.size()才对
            if (n > 0)
            {
                std::cout << "-----------request_buffer--------------" << std::endl;
                std::cout << buffer_queue << std::endl;
                std::cout << "------------------------------------" << std::endl;

                // 1.解析报文,提取完整的json请求: 如果有一条完整的报文,则通过json_package输出;如果不完整,就让服务器继续读取
                // 指导解析完所有的报文为止
                std::string json_package;
                while (Decode(buffer_queue, &json_package))
                {
                    // std::cout << "-----------request_json--------------" << std::endl;
                    // std::cout << json_package << std::endl;
                    // std::cout << "------------------------------------" << std::endl;

                    // std::cout << "-----------request_buffer--------------" << std::endl;
                    // std::cout << buffer_queue << std::endl;
                    // std::cout << "------------------------------------" << std::endl;
                    LOG(LogLevel::DEBUG) << client.StringAddr() << "请求: " << json_package;

                    // 此处一定拿到了一条完整的报文
                    // 2.请求json,反序列化(一般不会失败)
                    Request req;
                    bool ok = req.Deserialize(json_package);
                    if (!ok)
                        continue;

                    // 3.我一定得到了一个内部属性已经被设置好的req请求了
                    // 将req中的数据通过_func函数进行处理,结果给resp应答对象
                    Response resp = _func(req);

                    // 4.序列化: 将应答对象resp序列化
                    std::string json_str = resp.Serialize();

                    // 5.编码格式: 添加自定义长度
                    std::string send_str = Encode(json_str); // 携带长度的应答报文了"len\r\n{result:XXX, code:XX}\r\n"

                    // 6.发送
                    sock->Send(send_str);
                }
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv, error";
                break;
            }
        }
    }

    // 4.发送请求并接收结果
    bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
    {
        // 面向字节流,怎么保证client读取到的 一个网络字符串,一定是一个完整的请求呢?
        while (true)
        {
            int n = client->Recv(&resp_buff); // 整个客户的接收应答结果的数据缓冲区
            if (n > 0)
            {
                // std::cout << "-----------resp_buffer--------------" << std::endl;
                // std::cout << resp_buff << std::endl;
                // std::cout << "------------------------------------" << std::endl;

                // 1.解析报文,提取完整的json请求: 如果有一条完整的报文,则通过json_package输出;如果不完整,就让服务器继续读取
                // 指导解析完所有的报文为止
                std::string json_package;
                while (Decode(resp_buff, &json_package))
                {
                    // std::cout << "----------response json---------------" << std::endl;
                    // std::cout << json_package << std::endl;
                    // std::cout << "--------------------------------------" << std::endl;

                    // std::cout << "-----------resp_buffer--------------" << std::endl;
                    // std::cout << resp_buff << std::endl;
                    // std::cout << "------------------------------------" << std::endl;

                    // 此处一定拿到了一条完整的报文
                    // 2.请求json,反序列化(一般不会失败)
                    resp->Deserialize(json_package);
                }
                return true;
            }
            else if (n == 0)
            {
                std::cout << "server quit " << std::endl;
                return false;
            }
            else
            {
                std::cout << "recv error" << std::endl;
                return false;
            }
        }
    }
    std::string BuildRequestString(int x, int y, char oper)
    {
        // 1.建立一个完整的请求
        Request req(x, y, oper);

        // 2.序列化
        std::string json_req = req.Serialize();

        // // 2.1 debug
        // std::cout << "------------json_req string------------" << std::endl;
        // std::cout << json_req << std::endl;
        // std::cout << "---------------------------------------" << std::endl;

        // 3.添加长度报文
        return Encode(json_req);
    }

    ~Protocol()
    {
    }

private:
    // 因为我们用的是多进程
    // Request _req;
    // Response _resp;
    func_t _func;
};

6. 自定义守护进程化 Daemon.hpp

  1. 忽略IO, 子进程退出等待相关的信号

  2. 父进程直接结束

  3. 进程组的组长(原父进程被杀死了),运行到此处的自能是变成孤儿的子进程,并且子进程的新父进程是"1"。

  4. 取消守护进程和键盘、显示器的关联

Daemon.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cstdint>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include "Log.hpp"
#include "Common.hpp"

using namespace LogModule;

const std::string dev = "/dev/null";

// 将服务进行守护进程化
// 1.忽略IO, 子进程退出等待相关的信号
// 2.父进程直接结束
// 3.进程组的组长(原父进程被杀死了),运行到此处的自能是变成孤儿的子进程,并且子进程的新父进程是"1"。
// 4.取消守护进程和键盘、显示器的关联
void Daemon(int nochdir, int noclose)
{
    // 1.忽略IO, 子进程退出等待相关的信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);   // SIG_DFL

    // 2.父进程直接结束
    if (fork() > 0)
        exit(0);

    // 3.进程组的组长(原父进程被杀死了),运行到此处的自能是变成孤儿的子进程,并且子进程的新父进程是"1"。
    setsid();

    if(nochdir == 0)    // 更改进程的工作路径(前一节已经讲了)
        chdir("/");

    // 4.依旧可能显示器、键盘,stdin、stdout、stderr关联的
    // 守护进程,不从键盘输入,也不需要向显示器打印
    // 方法1:关闭0,1,2 -- 不推荐
    // 方法2:打开/dev/null,重定向标准输入,标准输出,标准错误到/dev/null
    if(noclose == 0)
    {
        int fd = ::open(dev.c_str(), O_RDWR);
        if(fd < 0)
        {
            LOG(LogLevel::FATAL) << "open " << dev << " errno";
            exit(OPEN_ERR);
        }
        else
        {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }

}

4. 功能验证

启动ServerNetCald之后,启动一个守护进程(之前提到的孙子进程),然后父进程结束。

再查看ServerNetCald的状态,发现此时的孙子进程仍在运行,但不是进程组的组长。

补:按理说ServerNetCald运行之后,他应该只有一个孤儿进程,他的父进程应该是1。但是在我的虚拟机上运行,他的父进程不是1(我也验证了我老师的代码,运行之后和我的输出一样),但是我关闭ServerNetCald的父进程,虚拟机就关机了?

5. 完整代码

【免费】网络-网路计算器+序列化反序列化+守护进程资源-CSDN下载

相关推荐
三两肉3 小时前
HTTPS ECDHE 握手全解析
网络协议·https·github·rsa·echde
小宇的天下3 小时前
HBM(高带宽内存)深度解析:先进封装视角的技术指南
网络·人工智能
txinyu的博客3 小时前
HTTP服务实现用户级窗口限流
开发语言·c++·分布式·网络协议·http
ha20428941944 小时前
Linux操作系统学习记录之----自定义协议(网络计算器)
linux·网络·学习
糖~醋排骨4 小时前
DHCP服务的搭建
linux·服务器·网络
huohaiyu4 小时前
网络中的一些基本概念
运维·服务器·网络
llddycidy4 小时前
峰值需求预测中的机器学习:基础、趋势和见解(最新文献)
网络·人工智能·深度学习
蜂蜜黄油呀土豆4 小时前
计算机网络中的常见网络场景与问题排查
tcp/ip·计算机网络·网络安全·http请求与响应
小林一直冲4 小时前
华为设备配置与命令
网络