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。
相关推荐
HLC++10 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室11 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy14 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕15 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生16 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1117 小时前
linux基础IO
linux·运维·服务器
BelongPanda19 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯19 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983420 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器