Linux 序列化技术、自定义协议实现及守护进程

Linux-序列化与自定义协议

1. 场景

  1. 进程间通信:
    • 管道、消息队列、共享内存、网络套接字等,它们传递的都是原始的字节流。如果你想在两个进程间传递一个 struct student { int id; char name[20]; },你必须先将这个结构体序列化成字节流发送,接收方再反序列化回结构体。
  2. 数据持久化:
    • 将一些数据保存到文件中。关机后内存会丢失,但文件不会。下次启动时,从文件中读取字节流并反序列化,就能恢复到上次的状态。
  3. 网络通信(最常用):
    • 这是最典型的应用。客户端和服务器程序可能运行在不同的机器、不同的操作系统甚至由不同的编程语言编写。它们之间通信的唯一"通用语言"就是字节流。一个定义好的序列化协议确保了双方都能正确理解数据。

2. 序列化与反序列化

2.1 概念

你可以把它们想象成 "打包" 和 "拆包" 的过程。

  • 序列化:
    • 过程:将一个存在于内存中的、结构复杂的对象(例如,一个 C 语言的结构体 struct,或一个 C++ 的类对象)转换成一个平坦的、连续的字节序列(字节流)。
    • 目的:这个字节序列可以被轻松地存储到文件中,或者通过网络发送到另一台机器。
  • 反序列化:
    • 过程:将接收到的或从文件中读取的字节序列,重新构造成一个与原始对象一模一样的内存对象。
    • 目的:恢复数据的状态,使得程序可以像使用原始对象一样使用它。

2.2 字节流 VS 数据报

2.2.1 什么是字节流?

字节流是一个连续的、有序的字节序列,数据像流水一样,一个字节接一个字节的进行读取或写入。

重要特性:

  1. 无边界性
    • 字节流本身没有消息边界的概念。
    • 发送方写入10次的数据,接收方可能1次就全部读完。
    • 发送方写入1次的数据,接收方可能分10次读完。
  2. 有序性
    • 先发送的字节一定先到达,顺序不会错乱。
  3. 面向连接
    • 典型的字节流服务(如TCP)需要先建立连接。
2.2.2 什么是数据报?

数据报是一种在网络中传输的独立的数据单元。

重要特性:

  1. 有边界性
    • 数据报保留完整的消息边界。
    • 发送方写入N次的数据,接收方一定通过N次读取读完。
    • 每个数据报都是独立的,不会与其他数据报混合。
  2. 无序可能性
    • 先发送的数据报可能后到达,顺序无法保证。
  3. 无连接
    • 典型的数据报服务(如UDP)不需要先建立连接。

数据报传输的是一个个独立的包,而字节流传输的是一个连续的、没有固定分界的数据流。

2.3 字节流的发送方式

2.3.1 字符串方式

特点:

  • 数据以可读的文本形式组织。
  • 通常使用编码格式(如UTF-8、GBK)。

1.纯文本格式

c 复制代码
// 发送方
const char* message = "Hello,World,123\n";
send(sockfd, message, strlen(message), 0);

// 接收方
char buffer[1024];
recv(sockfd, buffer, sizeof(buffer), 0);
// 接收到的可能是: "Hello,World,123\n"

2.结构化文本格式(JSON、XML等)

c 复制代码
// 发送JSON格式
const char* json_str = "{\"name\":\"Alice\",\"age\":25,\"score\":95.5}\n";
send(sockfd, json_str, strlen(json_str), 0);

// 接收方需要解析JSON
// {"name":"Alice","age":25,"score":95.5}
2.3.2 二进制方式

特点:

  • 数据以原始的二进制格式组织。
  • 直接操作内存字节,效率高。
c 复制代码
#include <arpa/inet.h>

struct Data {
    uint32_t id;
    uint16_t age;
};

// 发送二进制数据
void send_binary(int sockfd) {
    struct Data data;
    data.id = htonl(1001);    // 转换字节序
    data.age = htons(20);     // 转换字节序
    
    send(sockfd, &data, sizeof(data), 0);  // 直接发送结构体
}
2.3.3 总结

字符串方式(文本协议)通常是更推荐的选择。

二进制方式会有如下问题:

  • 结构体内存对齐问题。
  • 语言不同问题。

2.4 TCP粘包问题

当TCP读取数据的时候,读取到的报文不完整或者多读,导致下一个报文不完整,这个问题被称为"粘包"问题。

TCP是字节流,本身没有"包"的概念,这本质是应用层的表述。

客户端发送了多个结构逻辑数据,在接收时被合并成了一个TCP字节流片段。 本质的原因是因为TCP没有消息边界。因此,我们需要自己在应用层维护。

常见的解决方案:长度前缀法 + 分隔符法

为序列化后的数据添加报头,报头中存在数据长度字段,报头与数据中可以通过分隔符间隔。

2.5 JSON

在C++中使用JSON,需要采用JsonCpp开源的第三方库,JSON主要被用于序列化和反序列化。

2.5.1 安装(ubuntu)
bash 复制代码
sudo apt-get install libjsoncpp-dev

注意:

  • 编译时需携带 -ljsoncpp
  • 头文件引入 #include <jsoncpp/json/json.h>
2.5.1 JSON序列化

1.使用Json::ValuetoStyledString方法:

  • 优点:将 Json::Value 对象直接转换为格式化的JSON字符串。
cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    std::string s = root.toStyledString();
    std::cout << s << std::endl;

    return 0;
}
// 结果:
{
    "name" : "joe",
    "sex" : "男"
}

2.使用Json::StreamWriter

  • 优点:提供了更多的定制选项,如缩进、换行符等。
cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>

int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";

    Json::StreamWriterBuilder wbuilder; // StreamWriter的工厂
    std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
    std::stringstream ss;
    writer->write(root, &ss);
    std::cout << ss.str() << std::endl;

    return 0;
}

3.使用 Json::FastWriterJson::StyledWriter

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    Json::FastWriter writer;
    std::string s = writer.write(root);
    std::cout << s << std::endl;

    return 0;
}
// 结果:
{"name":"joe","sex":"男"}

Json::StyledWriter writer;
// 结果:
{
    "name" : "joe",
    "sex" : "男"
}
2.5.2 JSON反序列化

使用 Json::Reader

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main() {
    // JSON 字符串
    std::string json_string = "{\"name\":\"张三\",\"age\":30,\"city\":\"北京\"}";
    Json::Reader reader;
    Json::Value root;

    // 从字符串中读取 JSON 数据
    bool parsingSuccessful = reader.parse(json_string, root);
    if(!parsingSuccessful) {
        std::cout << "解析失败" << std::endl;
        return 1;
    }

    // 访问 JSON 数据
    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    std::string city = root["city"].asString();
    // 输出结果
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "City: " << city << std::endl;

    return 0;
}

3. 自定义协议

3.1 概念理解

自定义协议是指在网络通信中,应用程序自己定义的数据格式和通信规则,而不是使用现有的标准协议(如 HTTP、FTP、SMTP 等)。

应用程序为了特定需求而设计的数据格式、消息结构和通信规则的集合。

自定义协议:通信双方约定的完整通信规则和数据格式。

只有序列化(缺少协议):

  • 这是什么类型的数据?
  • 数据有多长?
  • 如果处理这个数据?

序列化实际是自定义协议的一部分。

3.2 基于自定义协议的TCP网络版本计算器实现

传输的数据格式(协议):

text 复制代码
[Json字符串的长度]\r\n[Json字符串]\r\n
10\r\n{"x":10, "y":20, "oper":"+"}\r\n
3.2.1 Socket.hpp

Linux中原生Socket的封装。

cpp 复制代码
#pragma once

#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

namespace SocketMoudle{
    const static int default_backlog = 16;
    // 设计为虚基类,TCP和UDP的模板类
    class Socket{
        public:
            virtual ~Socket() {}
            virtual void socket() = 0;
            virtual void bind(uint16_t server_port) = 0;
            virtual void listen(int backlog) = 0;
            virtual std::shared_ptr<Socket> accept(InetAddr* client_addr) = 0;
            virtual void close() = 0;
            virtual int recv(std::string* res) = 0;
            virtual int send(const std::string& message) = 0;
            virtual void connect(const InetAddr& server_addr) = 0;
        public:
            void buildTcpListenSocket(uint16_t server_port , int backlog = default_backlog) {
                socket();
                bind(server_port);
                listen(default_backlog);
            }
            void buildTcpClientListendSocket() {
                socket();
            }
    };

    const static int default_sockfd = -1;
    class TcpSocket : public Socket{
        public: 
            TcpSocket() :_sockfd(default_sockfd) {}         // 初始化sockfd为listenfd
            TcpSocket(int acceptfd) :_sockfd(acceptfd) {}   // 初始化sockfd为accpetfd

            virtual void socket() override {
                _sockfd = ::socket(AF_INET , SOCK_STREAM , 0);
                if(_sockfd < 0) {
                    LogModule::LOG(LogModule::LogLevel::FATAL) << "socket create error";
                    exit(SOCKET_ERROR);
                }
                LogModule::LOG(LogModule::LogLevel::INFO) << "socket create success - sockfd: " << _sockfd;
            }

            virtual void bind(uint16_t server_port) override {
                InetAddr server_addr(server_port);
                int n = ::bind(_sockfd , server_addr.getSockaddr() , server_addr.getSockaddrLen());
                if(n < 0) {
                    LogModule::LOG(LogModule::LogLevel::FATAL) << "bind error";
                    exit(BIND_ERROR);
                }
                LogModule::LOG(LogModule::LogLevel::INFO) << "bind success - sockfd: " << _sockfd;
            }

            virtual void listen(int backlog) override {
                int n = ::listen(_sockfd , backlog);
                if(n < 0) {
                    LogModule::LOG(LogModule::LogLevel::FATAL) << "listen error";
                    exit(LISTEN_ERROR);
                }
                LogModule::LOG(LogModule::LogLevel::INFO) << "listen success - sockfd: " << _sockfd;
            }

            virtual std::shared_ptr<Socket> accept(InetAddr* client_addr) override {
                struct sockaddr_in client;
                socklen_t addrlen = sizeof(client);
                int acceptfd = ::accept(_sockfd , (struct sockaddr*)&client , &addrlen);
                if(acceptfd < 0) {
                    LogModule::LOG(LogModule::LogLevel::WARNING) << "accpet warning";
                    return nullptr;
                }
                client_addr->setSockaddrIn(client); // 输出型参数
                return std::make_shared<TcpSocket>(acceptfd);   // 多个服务器可以共享一个客户端的链接
            }

            virtual void close() override {
                if(_sockfd > 0)
                    ::close(_sockfd);   // close(listenfd) 或 close(accpetfd)
            }

            // recv返回值和::recv返回值保持一致
            virtual int recv(std::string* res) override {
                char buffer[1024];
                ssize_t n = ::recv(_sockfd , buffer , sizeof(buffer) , 0);
                *res += buffer; // 读取可能会不完整
                return n;
            }

            virtual int send(const std::string& message) override {
                ssize_t n = ::send(_sockfd , message.c_str() , message.size() , 0);
                return n;
            }

            virtual void connect(const InetAddr& server_addr) {
                int n = ::connect(_sockfd , server_addr.getSockaddr() , server_addr.getSockaddrLen());
                if(n < 0) {
                    LogModule::LOG(LogModule::LogLevel::FATAL) << "connect server error";
                    exit(CONNECT_ERROR);
                }
            }
        private:
            int _sockfd;    // listenfd 或 accpetfd 
    };
}
3.2.2 Common.hpp

存放公共代码和头文件的头文件。

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include <unistd.h>
#include <functional>

enum ExitCode{
    OK = 0,
    SOCKET_ERROR,
    BIND_ERROR,
    LISTEN_ERROR,
    USAGE_ERROR,
    CONNECT_ERROR,
    FORK_ERRO
};

class NoCopy{
    public:
        NoCopy() = default;
        ~NoCopy() = default;
        NoCopy(const NoCopy&) = delete;
        NoCopy& operator=(const NoCopy&) = delete;
};
3.2.3 InetAddr.hpp

网络序列与主机序列相互转换的头文件。

cpp 复制代码
#pragma once

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <cstring>

const std::string any_ip = "0.0.0.0";

// 解析IP地址和端口号的类
class InetAddr{
    public:
        InetAddr() = default;
        // 这个构造函数用来将 struct sockaddr_in 结构体转换为 
        //      - 1.本地序列的字符串风格的点分十进制的IP 
        //      - 2.本地序列的整数端口
        // 网络转主机
        InetAddr(const struct sockaddr_in& addr) 
            :_addr(addr)
        {
            _port = ntohs(addr.sin_port);
            char ip_buffer[64];
            inet_ntop(AF_INET , &addr.sin_addr , ip_buffer, sizeof(ip_buffer));
            _ip = ip_buffer;
        }

        void setSockaddrIn(const struct sockaddr_in& addr) {
            _addr = addr;
            _port = ntohs(addr.sin_port);
            char ip_buffer[64];
            inet_ntop(AF_INET , &addr.sin_addr , ip_buffer, sizeof(ip_buffer));
            _ip = ip_buffer;
        }

        // 主机转网络
        // #define INADDR_ANY 0
        InetAddr(const std::string ip , u_int16_t port) 
            :_ip(ip)
            ,_port(port)
        {
            memset(&_addr , 0 , sizeof(_addr));
            _addr.sin_family = AF_INET;
            _addr.sin_port = htons(_port);
            inet_pton(AF_INET , _ip.c_str() , &_addr.sin_addr);
        }

        InetAddr(u_int16_t port) 
            :_port(port)
            ,_ip(any_ip)
        {
            memset(&_addr , 0 , sizeof(_addr));
            _addr.sin_family = AF_INET;
            _addr.sin_port = htons(_port);
            _addr.sin_addr.s_addr = INADDR_ANY; 
            // inet_pton(AF_INET , _ip.c_str() , &_addr.sin_addr);
        }

        const std::string& getIP() const { return _ip; }
        u_int16_t getPort() const { return _port; }
        const struct sockaddr_in& getSockaddrin() const { return _addr; } 
        const struct sockaddr* getSockaddr() const { return (const struct sockaddr*)&_addr; }
        struct sockaddr* getSockaddr() { return (struct sockaddr*)&_addr; }
        socklen_t getSockaddrLen() const { return sizeof(_addr); }
        
        // 格式化显示IP + Port
        std::string showIpPort() const {
            return "[" + _ip + " : " + std::to_string(_port) + "]";
        }

        bool operator==(const InetAddr& addr) const {
            return _ip == addr.getIP() && _port == addr.getPort(); 
        }
    private:
        struct sockaddr_in _addr;
        std::string _ip;
        u_int16_t _port;
};
3.2.4 TcpServer.hpp

通过多进程实现的Tcp服务器。

cpp 复制代码
#pragma once

#include "Socket.hpp"
#include <sys/types.h>
#include <sys/wait.h>

using namespace SocketMoudle;

using callback_t = std::function<void(std::shared_ptr<Socket>& , const InetAddr&)>;

class TcpServer{
    public:
        TcpServer(u_int16_t port , callback_t callback)
            :_tcp_listensocket(std::make_unique<TcpSocket>(port))
            ,_port(port)
            ,_is_running(false)
            ,_callback(callback)
        {
            // 多态调用socket/bind/listen
            _tcp_listensocket->buildTcpListenSocket(_port);
        }

        void start() {
            _is_running = true;
            while(_is_running) {
                // 获取客户端的连接
                InetAddr client_addr;
                std::shared_ptr<Socket> tcp_acceptsocket = _tcp_listensocket->accept(&client_addr);
                if(tcp_acceptsocket == nullptr) {
                    continue;
                }
                // 多进程/多线程处理客户端连接
                pid_t pid = fork();
                if(pid < 0) {
                    LogModule::LOG(LogModule::LogLevel::FATAL) << "server fork error";
                    exit(FORK_ERRO);
                } else if(pid == 0) {
                    // child process
                    // 子进程关闭不需要的文件描述符
                    _tcp_listensocket->close(); // close(accpetfd), 这里子进程可能会写时拷贝
                    if(fork() > 0)
                        exit(OK);   //  子进程正常退出,父进程回收
                    // 孙子进程被系统领养,自动回收资源
                    // 回调函数处理服务器与客户端的连接
                    _callback(tcp_acceptsocket , client_addr); 
                    tcp_acceptsocket->close();
                    exit(OK);
                }
                // parent process
                // 父进程关闭不需要的文件描述符
                tcp_acceptsocket->close();  // close(listenfd)
                // 回收子进程,循环继续执行获取与客户端的连接
                waitpid(pid , nullptr , 0);
            }
            _is_running = false;    
        }
    private:
        std::unique_ptr<Socket> _tcp_listensocket;
        u_int16_t _port;
        bool _is_running;
        callback_t _callback;
};
3.2.5 Protocol.hpp

这是一个协议类,主要功能有提供结构化数据的序列化和反序列化,以及添加报头和分离报头的功能。接收(客户端/服务器)发送的数据,并将处理的结果进行返回。

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include "Socket.hpp"
#include <jsoncpp/json/json.h>  // 引入jsoncpp第三方库

using namespace SocketMoudle;

// 基于网络版本的计算器
class Request{
    public:
        Request() = default;

        Request(int x , int y , char op)
            :left(x)
            ,right(y)
            ,oper(op)
        {}

        std::string serialization() {
            Json::Value root;
            root["x"] = left;
            root["y"] = right;
            root["oper"] = oper;
            Json::StyledWriter writer;
            return writer.write(root);
        }

        bool deserialization(const std::string& data) {
            Json::Value root;
            Json::Reader reader;
            bool ok = reader.parse(data , root);
            if(!ok) {
                LogModule::LOG(LogModule::LogLevel::WARNING) << "request deserialization error";
                return false;
            }
            left = root["x"].asInt();
            right = root["y"].asInt();
            oper = root["oper"].asInt();
            return true;
        }

        int get_x() const { return left; }
        int get_y() const { return right; }
        char get_oper() const { return oper; }

    private:
        int left;
        int right;
        char oper;
};

class Response{
    public:
        Response(int res = 0 , bool _valid = false) 
            :result(res)
            ,valid(_valid)
        {}

        std::string serialization() {
            Json::Value root;
            root["result"] = result;
            root["valid"] = valid;
            Json::StyledWriter writer;
            return writer.write(root);
        }

        bool deserialization(const std::string& data) {
            Json::Value root;
            Json::Reader reader;
            bool ok = reader.parse(data , root);
            if(!ok) {
                LogModule::LOG(LogModule::LogLevel::WARNING) << "response deserialization error";
                return false;
            }
            result = root["result"].asInt();
            valid = root["valid"].asBool();
            return true;
        }

        void showResult() {
            std::cout << "result: " << result << " [valid:" << valid << "]" << std::endl;
        }
    private:
        int result;
        bool valid;
};

using calculate_t = std::function<Response(const Request&)>;

// 协议类,需要解决两个问题
//   1. 需要有序列化和反序列化功能
//   2. 对于Tcp必须保证读到完整的报文
const static std::string sep = "\r\n";
// format: 10\r\n{"x":10, "y":20, "oper":"+"}\r\n
class Protocol{
    public:
        Protocol() = default;   
        Protocol(calculate_t cal_handler) :_cal_handler(cal_handler) {}

        // 添加报头
        std::string encode(const std::string& jsonstr) {
            std::string json_len = std::to_string(jsonstr.size());
            return json_len + sep + jsonstr + sep;
        }

        // 分离报头
        bool decode(std::string& buffer_queue , std::string& res) {
            size_t pos = buffer_queue.find(sep);
            if(pos == std::string::npos) {
                return false;
            }
            std::string json_len = buffer_queue.substr(0 , pos); // 有效载荷总长度
            int packet_len = json_len.size() + std::stoi(json_len) +  2 * sep.size();
            if(packet_len > buffer_queue.size()) {
                return false;   //说明当前读取的数据不足一个完整的报文,读取失败,应该继续读取
            }
            // 来到这里,当前已经有一个完整的报文或者多个完整的报文,或者一个半报文
            res = buffer_queue.substr(json_len.size() + sep.size() , std::stoi(json_len));   //将有效载荷带回上层
            // 将整个报文从buffer_queue分离
            buffer_queue.erase(0 , packet_len);
            return true;
        }

        void getClientAccept(std::shared_ptr<Socket>& client_accpet , const InetAddr& client_addr) {
            LogModule::LOG(LogModule::LogLevel::INFO) << "accept success client: " << client_addr.showIpPort();
            std::string buffer;
            while(true) {
                int n = client_accpet->recv(&buffer);
                if(n > 0) {
                    // 1.可能读到不是一个完整的报文 decode为false内层循环不进去,执行外层循环继续读取
                    // 2.也可能读取到多个完整的报文 decode为true内层循环持续处理多个完整报文
                    std::string jsonstr;
                    while(decode(buffer , jsonstr)) {
                        // 3.服务端接收客户端的计算任务,需要反序列化
                        Request req;
                        req.deserialization(jsonstr);
                        // 4.将反序列化的结果交给上层计算模块处理
                        Response res = _cal_handler(req);
                        // 5.将计算后的结果序列化
                        std::string resp_json = res.serialization();
                        // 6.将序列化后的json字符串添加报头
                        std::string packet = encode(resp_json);
                        // 7.发送给客户端
                        client_accpet->send(packet);
                    }
                } else if(n == 0) {
                    // client quit
                    LogModule::LOG(LogModule::LogLevel::INFO) << "client: " << client_addr.showIpPort() << " quit";
                    break;
                } else {
                    LogModule::LOG(LogModule::LogLevel::ERROR) << "server recv error";
                    break;
                }
            }
        }

        std::string buildClientRequest(int x, int y , char oper) {
            // 1.构建客户端请求
            Request req(x , y , oper);
            // 2.序列化
            std::string json_req = req.serialization();
            // 3.添加报头   
            return encode(json_req);
        }

        bool getServerResponse(std::shared_ptr<Socket>& client ,std::string& buffer_queue, Response* resq) {
            while(true) {
                // 可能读取到不是一个完整的报文 
                int n = client->recv(&buffer_queue);
                if(n > 0) {
                    std::string json_response;
                    bool ok = decode(buffer_queue , json_response);
                    if(!ok)
                        continue;   //不是一个完整的报文,继续读取
                    while(ok) {
                        // 保证了肯定有一个完整的报文,但是可能会有多个,所以需要连续处理
                        // 4.反序列化
                        resq->deserialization(json_response);
                        // 5.显示结果
                        resq->showResult();
                        // sleep(100); debug
                        ok = decode(buffer_queue , json_response);
                    }
                    return true;
                } else if(n == 0) {
                    // server quit
                    LogModule::LOG(LogModule::LogLevel::INFO) << "server quit";
                    return false;
                } else {
                    LogModule::LOG(LogModule::LogLevel::INFO) << "recv error";
                    return false;
                }
            }
        }
    private:
        calculate_t _cal_handler;
};
3.2.6 Calculate.hpp

主要负责处理数据的类。

cpp 复制代码
#pragma once

#include "Protocol.hpp"

class Calculate{
    public:
        Response execute(const Request& req) {
            switch(req.get_oper()) {
                case '+':
                    return Response(req.get_x() + req.get_y() , true);
                case '-':
                    return Response(req.get_x() - req.get_y() , true);
                case '*':
                    return Response(req.get_x() * req.get_y() , true);
                case '/':
                    {
                        if(req.get_y() == 0)
                            return Response(0, false);
                        else 
                            return Response(req.get_x() / req.get_y() , true);
                    }
                default:
                    LogModule::LOG(LogModule::LogLevel::WARNING) << "未知的操作符";
            }
            return Response(0 , false);
        }
};
3.2.7 TcpServer.cc
cpp 复制代码
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"

// tcpserver port
int main(int argc , char* argv[]) {

    if(argc != 2) {
        LogModule::LOG(LogModule::LogLevel::FATAL) << argv[0] << " port";
        exit(USAGE_ERROR);
    }

    uint16_t server_port = std::stoi(argv[1]);

    // 1.应用层处理上层业务
    Calculate cal;
    // 2.表示层负责收发数据并进行序列化和反序列化
    Protocol protocol([&cal](const Request& req)->Response{
        return cal.execute(req);
    });
    // 3.会话层负责建立与客户端的链接
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(server_port , 
        [&protocol](std::shared_ptr<Socket>& client_accpet , const InetAddr& client_addr){
            protocol.getClientAccept(client_accpet,client_addr);
        });
    tsvr->start();  // 启动服务器
    
    return 0;
}
3.2.8 TcpClient.cc
cpp 复制代码
#include "Socket.hpp"
#include "Protocol.hpp"

int main(int argc , char* argv[]) {

    if(argc != 3) {
        std::cout << argv[0] << " server_ip server port" << std::endl;
        exit(USAGE_ERROR);
    }

    std::string server_ip = argv[1];
    u_int16_t server_port = std::stoi(argv[2]);
    InetAddr server_addr(server_ip , server_port);

    std::shared_ptr<SocketMoudle::Socket> sptr = std::make_shared<SocketMoudle::TcpSocket>();
    sptr->buildTcpClientListendSocket();
    sptr->connect(server_addr); //  与服务器建立连接

    Protocol protocol;

    std::string buffer_queue;
    while(true) {
        int x , y;
        char oper;
        std::cout << "Please input x: ";
        std::cin >> x;
        std::cout << "Please input y: ";
        std::cin >> y;
        std::cout << "Please input operator: ";
        std::cin >> oper;
        // debug
        // std::cout << "x: " << x << " y: " << y << " oper: " << oper << std::endl;
        // 1.构建客户端请求
        std::string packet = protocol.buildClientRequest(x , y , oper);
        // 2.向服务器发送   
        sptr->send(packet);
        // 3.接收服务器返回结果
        Response resq;
        bool ok = protocol.getServerResponse(sptr ,buffer_queue, &resq);
        if(!ok) {
            break;
        }
    }

    sptr->close();

    return 0;
}

应用层、表示层、会话层一般来说都是用户自己需要实现的部分,这三层经常经常被合并为一层就是应用层。代码中的体现:

  • Calculate.hpp 代表的是应用层处理业务的逻辑。
  • Protocol.hpp 代表表示层负责收发数据并进行序列化和反序列化。
  • TcpServer.hpp 代表会话层负责建立与客户端的连接。

4. 进程间关系与守护进程

4.1 进程组

每一个进程除了有一个进程唯一ID之外,还属于一个进程组。进程组是一个或者多个进程的集合,一个进程组可以包含多个进程。 每一个进程组也有一个唯一的ID,被称为PGID。

每一个进程组都有一个组长进程。组长进程的ID等于其进程组ID。

  • 进程组组长的作用:进程组组长可以创建一个进程组或者创建该组中的进程。
  • 进程组的生命周期:,从进程组创建开始到其中最后一个进程离开为止。注意: 主要某个进程组中,有一个进程存在,则该进程组就存在,这与其组长进程是否已经终止无关。
  • 通常通过管道将几个进程编成一个进程组。
  • 向一个前台进程组发送信号,该信号会被传递给组内的所有进程。

4.2 会话

4.2.1 什么是会话?

会话可以看成是一个或多个进程组的集合,一个会话可以包含多个进程组 。每一个会话也会有一个会话ID(SID)。会话ID是会话中首进程的进程ID

4.2.2 如何创建会话?

通过 setsid 函数来创建一个会话,前提是调用进程不能是一个进程组的组长

c 复制代码
#include <unistd.h>
/*
    功能:创建会话
    返回值:创建成功返回SID, 失败返回-1
*/
pid_t setsid(void);

该接口调用之后会发生:

  • 调用进程会变成新会话的会话首进程。 此时,新会话中只有唯一的一个进程。
  • 调用进程会变成进程组组长。 新进程组ID就是当前调用进程ID
  • 该进程没有控制终端。 如果在调用setsid之前该进程存在控制终端,则调用之后会切断联系。

需要注意的是: 这个接口如果调用进程是进程组组长,则会报错,,为了避免这种情况,通常使用方法是先调用fork创建子进程,父进程终止,子进程继续执行,因为子进程会继承父进程的进程组ID,而进程ID则是新分配的,就不会出现错误的情况。

  • 一个会话通常对应一个终端(如tty1, pts/0)。
  • 一个会话中,有且只有一个前台进程组,可以有多个后台进程组。
  • 只有前台进程组中的进程才能从终端读取输入并向终端输出。

当你通过SSH登陆Linux时,系统会为你创建一个新的会话,并启动bash命令行解释器(其实就是首进程),让bash进程成为前台任务并关联终端

4.3 守护进程

守护进程是Linux中一种特殊的后台进程 ,它独立于控制终端 并且长期运行 ,通常在操作系统启动时开启,操作系统关闭时终止。
Daemon.hpp

cpp 复制代码
#pragma once

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

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

// 将服务进行守护进程化的服务
void Daemon(int nochdir, int noclose)
{
    // 1. 忽略IO,子进程退出等相关的信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

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

    // 3. 只能是子进程,孤儿了,父进程就是1
    setsid(); // 成为一个独立的会话

    if(nochdir == 0)
        chdir("/");

    //  守护进程,不从键盘输入,也不需要向显示器打印
    //  打开/dev/null, 重定向标准输入,标准输出,标准错误到/dev/null
    if (noclose == 0)
    {
        int fd = ::open(dev.c_str(), O_RDWR);
        if (fd < 0)
        {
            exit(OPEN_ERR);
        }
        else
        {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }
}
  • 为什么忽略SIGPIPE
    • 当一个进程向一个已经关闭的管道、socket等写入会触发终止进程的行为,守护进程通常作为网络服务运行,可能会遇到客户端意外断开连接的情况,如果不忽略,可能会网络写入失败导致守护进程被意外终止。
  • 为什么忽略SIGCHLD
    • 守护进程通常不会创建子进程,或者创建了也不关系子进程的退出状态。设置为SIG_IGN可以让内核自动回收子进程,避免僵尸进程。
  • 为什么要将工作目录改为/?
    • 根目录总是存在的,确保守护进程中使用到的路径都是以根目录为相对路径开始的。

daemon函数(现成将当前进程变成守护进程的函数)

c 复制代码
#include <unistd.h>

int daemon(int nochdir, int noclose);

函数:

  • nochdir:是否改变工作目录到根目录。
    • 0:改变工作目录到 /
    • 非0:保持当前工作目录
  • noclose:是否重定向标准输入、输出、错误到 /dev/null。
    • 0:重定向到 /dev/null
    • 非0:保持原来的文件描述符

返回值:

  • 成功:返回0。
  • 失败:返回-1。
相关推荐
adnyting2 小时前
【Linux日新月异(十)】CentOS 7 文件系统结构深度解剖:从根到叶的完整指南
linux·运维·centos
大锦终2 小时前
【Linux】高级IO
linux·服务器·网络·c++
LCG元3 小时前
Linux 下高效开发环境搭建:VSCode Remote + 容器开发
linux
哈里谢顿3 小时前
深入理解 Linux 系统 PATH 目录:从理论到实践
linux
刘延林.4 小时前
树莓派 5 上 Ubuntu 24.04 LTS 自带 RDP 远程桌面重启密码就变
linux·运维·ubuntu
我不是醉忧4 小时前
RedHat系统搭建DNS主从服务器
linux·运维·服务器
大神的风范4 小时前
linux之ubuntu qt界面开发开发点菜系统
linux·qt·ubuntu
正在努力的小河4 小时前
Linux SPI 驱动实验
linux·运维·服务器
adnyting4 小时前
【Linux日新月异(九)】CentOS 7其他常用命令大全:系统操作与管理必备技巧
linux·运维·centos