自定义网络协议与序列化/反序列化

自定义网络协议与序列化/反序列化

如果你做过网络编程,可能会遇到这样的问题:用TCP发了一个"1+1"的计算请求,对方却只收到了"1";或者一次收到了"1+12+3"这种拼接的内容------这时候怎么判断哪个是完整的请求?又怎么把这些字符串解析成能计算的操作数和运算符?这篇文章就从TCP的"天生缺陷"说起,带你搞懂自定义协议的必要性、序列化与反序列化的核心逻辑,最后通过一个网络计算器实例,把这些知识点落地成可运行的代码。

一、TCP通信的字节流边界问题

先问一个问题:TCP是"面向字节流"的协议,这意味着什么?

你可能知道,发送端调用write,只是把数据拷贝到操作系统的内核缓冲区,什么时候发、发多少,全由TCP协议自己决定(比如根据网络带宽、接收方缓冲区大小调整);接收端调用read,可能只读到部分数据(比如对方只发了一半),也可能读到多个拼接的数据(比如对方连续发了两个请求,被TCP打包成一次发送)。

举个例子:你发了两次请求"1+1"和"2+3",接收端read时可能拿到"1+12+3"------这时候你怎么区分这是两个请求,而不是一个"1+12+3"的请求?又怎么保证每次处理的都是一个完整的请求?

这就是TCP字节流的"边界问题"------它没法保证应用层能读到"完整的报文"。要解决这个问题,光靠TCP协议不够,必须在应用层自定义协议

二、应用层协议定制的必要性

协议本质是"通信双方的约定"------比如约定"数据格式是什么""每个字段代表什么意思""怎么判断一个报文结束"。没有约定,接收端根本不知道怎么解析数据。

以"网络计算器"为例,我们需要客户端给服务器发"计算请求",服务器返回"计算结果"。这时候协议该怎么定?

至少要明确两件事:

  1. 请求格式(Request) :需要包含两个操作数(比如xy)、一个运算符(比如op,支持+ - * / %)。
  2. 响应格式(Response) :需要包含计算结果(result)、状态码(code,比如0表示成功,1表示除0错误,2表示非法运算符)。

如果没有这个约定,客户端发个"1+1",服务器可能以为是字符串"1+1",而不是"x=1,op=+,y=1"------根本没法计算。

所以,应用层协议的核心作用是:给字节流"贴标签",让接收端能正确识别数据结构和字段含义

三、关键技术:序列化与反序列化

协议定好了Request和Response的结构,接下来的问题是:怎么把这些结构化数据(比如C++的类/结构体)通过网络发送?

直接发结构体行不行?比如客户端定义一个struct Request { int x; int y; char op; },然后把结构体直接write到socket------这在同平台(比如都是Linux)可能暂时能用,但跨平台(比如客户端Windows,服务器Linux)一定会出问题。

为什么?因为结构体的内存对齐 。不同编译器(比如VS和GCC)对结构体的内存对齐规则可能不同:比如一个struct { char a; int b; },VS可能按4字节对齐(总大小8字节),GCC可能按自然对齐(总大小5字节)。直接传结构体,接收端解析时字段会错位(比如把a的内存当成b的一部分),结果完全错误。

这时候就需要"序列化"和"反序列化":

  • 序列化(Serialization) :把结构化数据(比如Request类对象)转换成通用的字符串或字节流(比如"10 + 20"),消除平台差异。
  • 反序列化(Deserialization) :把接收到的字符串/字节流还原成结构化数据(比如把"10 + 20"还原成x=10op=+y=20)。

举个生活中的例子:你在QQ发消息,消息里包含"昵称、时间、内容"------这其实是一个结构化数据。QQ会把这三个字段打包成一个字符串(比如"小明|2024-05-20 10:00|你好"),这个过程就是序列化;接收方拿到字符串后,按"|"分割,还原出昵称、时间、内容,这就是反序列化。

四、实战:网络计算器的协议设计与实现

下面我们从0实现一个网络计算器,把"协议定制""序列化/反序列化"落地。整个过程分三步:设计协议结构、实现序列化/反序列化、保障报文完整性。

4.1 第一步:定义协议结构(Request/Response)

首先,我们用C++类定义请求和响应的结构(代替结构体,方便封装方法):

cpp 复制代码
// protocol.hpp
#pragma once
#include <string>
#include <iostream>

// 计算请求:x op y(比如10 + 20)
class Request {
public:
    int x;       // 左操作数
    int y;       // 右操作数
    char op;     // 运算符:+ - * / %
    std::string sep = " ";  // 字段分隔符(空格)

    // 构造函数
    Request(int x_ = 0, int y_ = 0, char op_ = 0) 
        : x(x_), y(y_), op(op_) {}

    // 序列化:把Request对象转成字符串(比如"10 + 20")
    bool Serialize(std::string& out_str);

    // 反序列化:把字符串转成Request对象
    bool Deserialize(const std::string& in_str);
};

// 计算响应:result(结果) + code(状态码)
class Response {
public:
    int result;  // 计算结果(仅当code=0时有效)
    int code;    // 状态码:0=成功,1=除0错误,2=模0错误,3=非法运算符
    std::string sep = " ";  // 字段分隔符(空格)

    // 构造函数
    Response(int res_ = 0, int code_ = 0) 
        : result(res_), code(code_) {}

    // 序列化:把Response对象转成字符串(比如"30 0")
    bool Serialize(std::string& out_str);

    // 反序列化:把字符串转成Response对象
    bool Deserialize(const std::string& in_str);
};

4.2 第二步:实现序列化与反序列化

接下来实现SerializeDeserialize方法,核心是"按约定格式处理字符串"。

4.2.1 Request的序列化与反序列化
  • 序列化:把xopy用空格拼接成字符串(比如x=10op='+'y=20 → "10 + 20")。
  • 反序列化:把字符串按空格分割,提取xopy(比如"10 + 20" → x=10op='+'y=20)。
cpp 复制代码
// Request序列化
bool Request::Serialize(std::string& out_str) {
    // 拼接格式:x + " " + op + " " + y
    out_str = std::to_string(x) + sep + op + sep + std::to_string(y);
    return true;  // 简单场景,暂不处理异常
}

// Request反序列化
bool Request::Deserialize(const std::string& in_str) {
    // 1. 找第一个分隔符(空格),提取x
    size_t first_sep = in_str.find(sep);
    if (first_sep == std::string::npos) {
        return false;  // 没找到分隔符,格式错误
    }
    std::string x_str = in_str.substr(0, first_sep);
    x = std::stoi(x_str);  // 字符串转整数

    // 2. 找第二个分隔符,提取op
    size_t second_sep = in_str.find(sep, first_sep + 1);
    if (second_sep == std::string::npos) {
        return false;
    }
    op = in_str[first_sep + 1];  // 分隔符后第一个字符是op

    // 3. 提取y
    std::string y_str = in_str.substr(second_sep + 1);
    y = std::stoi(y_str);

    return true;
}
4.2.2 Response的序列化与反序列化

逻辑类似,只是字段变成resultcode

cpp 复制代码
// Response序列化
bool Response::Serialize(std::string& out_str) {
    // 拼接格式:result + " " + code
    out_str = std::to_string(result) + sep + std::to_string(code);
    return true;
}

// Response反序列化
bool Response::Deserialize(const std::string& in_str) {
    // 找分隔符,分割result和code
    size_t sep_pos = in_str.find(sep);
    if (sep_pos == std::string::npos) {
        return false;
    }
    std::string res_str = in_str.substr(0, sep_pos);
    std::string code_str = in_str.substr(sep_pos + 1);

    result = std::stoi(res_str);
    code = std::stoi(code_str);

    return true;
}

4.3 第三步:保障报文完整性------加"长度头"和"分隔符"

现在我们能把Request/Response转成字符串了,但还有个问题:接收端怎么知道一个报文的结束?比如客户端发了"10 + 20",接收端可能只读到"10 + "(半包),或者读到"10 + 2020 - 5"(粘包)。

解决方案:给每个报文加一个"长度头",格式约定为"长度\n有效载荷"------比如"5\n10+20"(长度5表示后面的有效载荷是5个字符)。接收端先读"长度",再按长度读"有效载荷",就能保证拿到完整的报文。

我们写两个通用函数Encode(加长度头)和Decode(解长度头):

cpp 复制代码
// protocol.hpp 中添加
#include <cstring>

// 协议分隔符:用于分割长度头和有效载荷(换行符\n)
const std::string PROTOCOL_SEP = "\n";

// Encode:给有效载荷加长度头(格式:长度\n有效载荷)
std::string Encode(const std::string& payload) {
    int len = payload.size();
    return std::to_string(len) + PROTOCOL_SEP + payload;
}

// Decode:从报文中提取有效载荷(输入是接收缓冲区,输出是有效载荷)
// 返回值:true=解析成功,false=报文不完整
bool Decode(std::string& in_buf, std::string& out_payload) {
    // 1. 找长度头的分隔符\n
    size_t sep_pos = in_buf.find(PROTOCOL_SEP);
    if (sep_pos == std::string::npos) {
        return false;  // 没找到分隔符,报文不完整
    }

    // 2. 提取长度头,转成整数
    std::string len_str = in_buf.substr(0, sep_pos);
    int payload_len = std::stoi(len_str);

    // 3. 检查缓冲区是否包含完整的有效载荷
    int total_len = len_str.size() + PROTOCOL_SEP.size() + payload_len;
    if (in_buf.size() < total_len) {
        return false;  // 有效载荷不完整
    }

    // 4. 提取有效载荷,并从缓冲区中移除已处理的报文
    out_payload = in_buf.substr(sep_pos + 1, payload_len);
    in_buf.erase(0, total_len);  // 移除已处理的部分(长度头+有效载荷)

    return true;
}

五、工程化优化:套接字封装与服务端框架

现在协议和序列化都搞定了,接下来要实现网络通信。为了避免重复写socket代码,我们先封装一个Socket类;再写一个TCP服务器,用多进程处理并发连接。

5.1 套接字封装(Socket类)

把socket的创建、绑定、监听、accept、connect等操作封装成类,方便客户端和服务器复用:

cpp 复制代码
// socket.hpp
#pragma once
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <cerrno>
#include <cstring>
#include <iostream>

class Socket {
private:
    int sock_fd_;  // 套接字文件描述符

public:
    Socket() : sock_fd_(-1) {}
    ~Socket() { if (sock_fd_ != -1) close(sock_fd_); }

    // 1. 创建流式套接字(TCP)
    bool Create() {
        sock_fd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sock_fd_ < 0) {
            std::cerr << "Socket create failed: " << strerror(errno) << std::endl;
            return false;
        }
        return true;
    }

    // 2. 绑定端口(服务器用,IP默认INADDR_ANY)
    bool Bind(uint16_t port) {
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);  // 主机字节序转网络字节序
        addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 绑定所有网卡IP

        if (bind(sock_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            std::cerr << "Socket bind failed: " << strerror(errno) << std::endl;
            return false;
        }
        return true;
    }

    // 3. 监听(服务器用)
    bool Listen(int backlog = 10) {
        if (listen(sock_fd_, backlog) < 0) {
            std::cerr << "Socket listen failed: " << strerror(errno) << std::endl;
            return false;
        }
        return true;
    }

    // 4. 接受连接(服务器用,返回新的通信套接字)
    int Accept(std::string& client_ip, uint16_t& client_port) {
        struct sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        int conn_fd = accept(sock_fd_, (struct sockaddr*)&client_addr, &client_addr_len);
        if (conn_fd < 0) {
            std::cerr << "Socket accept failed: " << strerror(errno) << std::endl;
            return -1;
        }

        // 提取客户端IP和端口
        client_ip = inet_ntoa(client_addr.sin_addr);  // 网络字节序转IP字符串
        client_port = ntohs(client_addr.sin_port);    // 网络字节序转主机字节序

        return conn_fd;
    }

    // 5. 连接服务器(客户端用)
    bool Connect(const std::string& server_ip, uint16_t server_port) {
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(server_port);
        // IP字符串转网络字节序
        if (inet_pton(AF_INET, server_ip.c_str(), &server_addr.sin_addr) <= 0) {
            std::cerr << "Invalid server IP: " << server_ip << std::endl;
            return false;
        }

        if (connect(sock_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Socket connect failed: " << strerror(errno) << std::endl;
            return false;
        }
        return true;
    }

    // 获取套接字文件描述符
    int GetFd() const { return sock_fd_; }
};

5.2 多进程TCP服务器实现

服务器的逻辑:主进程监听端口,接受新连接;每收到一个连接,fork一个子进程处理业务(计算请求),主进程继续监听。同时忽略SIGCHLD信号,避免僵尸进程。

cpp 复制代码
// server.cpp
#include "socket.hpp"
#include "protocol.hpp"
#include <signal.h>
#include <sys/wait.h>
#include <functional>

// 忽略SIGCHLD信号,避免僵尸进程
void IgnoreSigchld() {
    signal(SIGCHLD, SIG_IGN);
}

// 处理客户端请求:读取请求→计算→返回响应
void HandleClient(int conn_fd) {
    std::string in_buf;  // 接收缓冲区(累积数据,处理粘包/半包)
    char buf[1024] = {0};

    while (true) {
        // 1. 读取客户端数据
        ssize_t n = read(conn_fd, buf, sizeof(buf) - 1);
        if (n < 0) {
            std::cerr << "Read failed: " << strerror(errno) << std::endl;
            break;
        } else if (n == 0) {
            std::cout << "Client closed connection" << std::endl;
            break;
        }

        // 2. 把读取到的字节流追加到缓冲区
        buf[n] = '\0';  // 确保字符串结束
        in_buf += buf;
        memset(buf, 0, sizeof(buf));

        // 3. 解析缓冲区中的完整报文(可能有多个)
        std::string payload;
        while (Decode(in_buf, payload)) {  // 循环解析,直到没有完整报文
            // 4. 反序列化:payload→Request对象
            Request req;
            if (!req.Deserialize(payload)) {
                std::cerr << "Invalid request format: " << payload << std::endl;
                continue;
            }

            // 5. 执行计算,生成Response
            Response resp;
            switch (req.op) {
                case '+':
                    resp.result = req.x + req.y;
                    resp.code = 0;
                    break;
                case '-':
                    resp.result = req.x - req.y;
                    resp.code = 0;
                    break;
                case '*':
                    resp.result = req.x * req.y;
                    resp.code = 0;
                    break;
                case '/':
                    if (req.y == 0) {
                        resp.code = 1;  // 除0错误
                    } else {
                        resp.result = req.x / req.y;
                        resp.code = 0;
                    }
                    break;
                case '%':
                    if (req.y == 0) {
                        resp.code = 2;  // 模0错误
                    } else {
                        resp.result = req.x % req.y;
                        resp.code = 0;
                    }
                    break;
                default:
                    resp.code = 3;  // 非法运算符
                    break;
            }

            // 6. 序列化Response→字符串,加长度头
            std::string resp_str;
            resp.Serialize(resp_str);
            std::string encoded_resp = Encode(resp_str);

            // 7. 发送响应给客户端
            write(conn_fd, encoded_resp.c_str(), encoded_resp.size());
            std::cout << "Handled request: " << payload 
                      << " → Response: " << resp_str << std::endl;
        }
    }

    close(conn_fd);  // 关闭通信套接字
}

// TCP服务器类
class TcpServer {
private:
    Socket listen_sock_;
    uint16_t port_;

public:
    TcpServer(uint16_t port) : port_(port) {}

    // 初始化服务器:创建→绑定→监听
    bool Init() {
        if (!listen_sock_.Create()) return false;
        if (!listen_sock_.Bind(port_)) return false;
        if (!listen_sock_.Listen()) return false;
        std::cout << "Server init success, listening on port " << port_ << std::endl;
        return true;
    }

    // 启动服务器:接受连接→fork子进程处理
    void Start() {
        IgnoreSigchld();  // 忽略SIGCHLD,避免僵尸进程

        while (true) {
            std::string client_ip;
            uint16_t client_port;
            int conn_fd = listen_sock_.Accept(client_ip, client_port);
            if (conn_fd < 0) continue;

            // fork子进程处理客户端请求
            pid_t pid = fork();
            if (pid < 0) {
                std::cerr << "Fork failed: " << strerror(errno) << std::endl;
                close(conn_fd);
                continue;
            } else if (pid == 0) {
                // 子进程:处理请求(关闭监听套接字,只保留通信套接字)
                listen_sock_.~Socket();  // 子进程不需要监听套接字,手动析构
                HandleClient(conn_fd);
                exit(0);  // 处理完请求,子进程退出
            } else {
                // 父进程:关闭通信套接字(子进程会复制一份)
                close(conn_fd);
            }
        }
    }
};

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);

    TcpServer server(port);
    if (!server.Init()) return 1;
    server.Start();

    return 0;
}

5.3 客户端实现

客户端逻辑:创建套接字→连接服务器→构造请求→发送→接收响应→解析。

cpp 复制代码
// client.cpp
#include "socket.hpp"
#include "protocol.hpp"
#include <random>
#include <unistd.h>

// 随机生成计算请求(模拟用户输入)
Request GenerateRandomRequest() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> num_dist(1, 100);  // 1~100的随机数
    std::uniform_int_distribution<> op_dist(0, 4);     // 0~4对应5种运算符

    int x = num_dist(gen);
    int y = num_dist(gen);
    char ops[] = "+-*/%";
    char op = ops[op_dist(gen)];

    // 避免除0/模0(简化测试)
    if ((op == '/' || op == '%') && y == 0) {
        y = 1;
    }

    return Request(x, y, op);
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建套接字并连接服务器
    Socket sock;
    if (!sock.Create()) return 1;
    if (!sock.Connect(server_ip, server_port)) return 1;
    std::cout << "Connected to server: " << server_ip << ":" << server_port << std::endl;

    // 2. 发送10个随机请求
    for (int i = 0; i < 10; ++i) {
        // 生成请求
        Request req = GenerateRandomRequest();
        std::string req_str;
        req.Serialize(req_str);
        std::string encoded_req = Encode(req_str);

        // 发送请求
        write(sock.GetFd(), encoded_req.c_str(), encoded_req.size());
        std::cout << "Sent request: " << req_str << std::endl;

        // 接收响应
        char buf[1024] = {0};
        ssize_t n = read(sock.GetFd(), buf, sizeof(buf) - 1);
        if (n < 0) {
            std::cerr << "Read response failed: " << strerror(errno) << std::endl;
            break;
        }
        buf[n] = '\0';
        std::string in_buf = buf;

        // 解析响应
        std::string payload;
        if (Decode(in_buf, payload)) {
            Response resp;
            if (resp.Deserialize(payload)) {
                if (resp.code == 0) {
                    std::cout << "Received response: " << req_str << " = " << resp.result << std::endl;
                } else {
                    std::string err_msg;
                    switch (resp.code) {
                        case 1: err_msg = "division by zero"; break;
                        case 2: err_msg = "mod by zero"; break;
                        case 3: err_msg = "invalid operator"; break;
                    }
                    std::cout << "Received error: " << req_str << " → " << err_msg << std::endl;
                }
            }
        }

        sleep(1);  // 每隔1秒发一次,避免请求太密集
    }

    // 3. 关闭套接字
    close(sock.GetFd());
    std::cout << "Disconnected from server" << std::endl;

    return 0;
}

六、从手写到底层到通用方案------引入JSON

手写序列化/反序列化虽然能帮我们理解原理,但实际项目中很繁琐(比如字段多了要写大量字符串分割代码)。工业界常用JSON作为通用序列化方案------它是跨语言的文本格式,支持各种数据类型(整数、字符串、数组、对象),还有成熟的库(比如C++的jsoncpp)。

6.1 为什么用JSON?

  • 跨语言:Python、Java、Go都支持JSON,不同语言的服务能轻松通信。
  • 易读易调试 :JSON格式直观(比如{"x":10,"y":20,"op":"+"}),方便打印日志和调试。
  • 无需手写代码:库会自动处理序列化/反序列化,减少重复工作。

6.2 用jsoncpp替换手写序列化

首先安装jsoncpp库(Ubuntu为例):

bash 复制代码
sudo apt-get install libjsoncpp-dev

然后修改protocol.hpp,用JSON实现序列化/反序列化(保留原手写代码,用条件编译切换):

cpp 复制代码
// protocol.hpp 中添加
#include <json/json.h>  // jsoncpp头文件

// 条件编译:定义USE_JSON则用JSON,否则用手写
#ifdef USE_JSON
// JSON版Request序列化
bool Request::Serialize(std::string& out_str) {
    Json::Value root;
    root["x"] = x;
    root["y"] = y;
    root["op"] = op;  // jsoncpp会自动把char转成字符串

    Json::FastWriter writer;  // 快速序列化(无格式)
    out_str = writer.write(root);
    // 去掉JSON字符串末尾的换行符(FastWriter会加)
    if (!out_str.empty() && out_str.back() == '\n') {
        out_str.pop_back();
    }
    return true;
}

// JSON版Request反序列化
bool Request::Deserialize(const std::string& in_str) {
    Json::Value root;
    Json::Reader reader;
    if (!reader.parse(in_str, root)) {
        std::cerr << "JSON parse failed: " << in_str << std::endl;
        return false;
    }

    // 检查字段是否存在
    if (!root.isMember("x") || !root["x"].isInt()) return false;
    if (!root.isMember("y") || !root["y"].isInt()) return false;
    if (!root.isMember("op") || !root["op"].isString()) return false;

    x = root["x"].asInt();
    y = root["y"].asInt();
    op = root["op"].asString()[0];  // 字符串转char
    return true;
}

// JSON版Response序列化
bool Response::Serialize(std::string& out_str) {
    Json::Value root;
    root["result"] = result;
    root["code"] = code;

    Json::FastWriter writer;
    out_str = writer.write(root);
    if (!out_str.empty() && out_str.back() == '\n') {
        out_str.pop_back();
    }
    return true;
}

// JSON版Response反序列化
bool Response::Deserialize(const std::string& in_str) {
    Json::Value root;
    Json::Reader reader;
    if (!reader.parse(in_str, root)) {
        std::cerr << "JSON parse failed: " << in_str << std::endl;
        return false;
    }

    if (!root.isMember("result") || !root["result"].isInt()) return false;
    if (!root.isMember("code") || !root["code"].isInt()) return false;

    result = root["result"].asInt();
    code = root["code"].asInt();
    return true;
}
#else
// 原手写序列化/反序列化代码(略)
#endif

6.3 编译与测试

-DUSE_JSON启用JSON模式,链接jsoncpp库:

bash 复制代码
# 编译服务器
g++ server.cpp -o server -DUSE_JSON -ljsoncpp
# 编译客户端
g++ client.cpp -o client -DUSE_JSON -ljsoncpp

启动服务器和客户端,会看到请求格式变成JSON:

复制代码
# 服务器输出
Handled request: {"op":"+","x":10,"y":20} → Response: {"code":0,"result":30}

七、总结

这篇文章从TCP的边界问题出发,讲了自定义协议、序列化/反序列化的核心逻辑,最后用实战代码落地。复习时可以重点关注这几个点:

  1. TCP边界问题:面向字节流导致读不完整/粘包,必须在应用层解决。
  2. 协议定制:约定数据格式(Request/Response)和报文结构(长度头+有效载荷)。
  3. 序列化/反序列化
    • 作用:消除跨平台差异,把结构化数据转通用格式。
    • 手写实现:按约定分隔符处理字符串,加长度头保障完整性。
    • 通用方案:用JSON等成熟格式,减少重复代码。
  4. 工程化:套接字封装复用代码,多进程处理并发,忽略SIGCHLD避免僵尸进程。
相关推荐
Doro再努力2 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
senijusene2 小时前
Linux软件编程:IO编程,标准IO(1)
linux·运维·服务器
忧郁的橙子.2 小时前
02-本地部署Ollama、Python
linux·运维·服务器
chian-ocean2 小时前
深入 CANN:使用 `tbe-op` 构建自定义高性能算子
网络
醇氧2 小时前
【linux】查看发行版信息
linux·运维·服务器
中议视控2 小时前
可编程网络中央控制系统主机通过红外发射棒控制空调电视等红外设备
网络·物联网·5g
No8g攻城狮2 小时前
【Linux】Windows11 安装 WSL2 并运行 Ubuntu 22.04 详细操作步骤
linux·运维·ubuntu
XiaoFan0123 小时前
免密批量抓取日志并集中输出
java·linux·服务器
souyuanzhanvip3 小时前
ServerBox v1.0.1316 跨平台 Linux 服务器管理工具
linux·运维·服务器
数据安全科普王3 小时前
打破中心枷锁:P2P网络如何用“去中心化”重构互联网通信
网络·去中心化·p2p