Linux网络的应用层自定义协议

目录

1、应用层协议

2、NetCal

[2.1 大致思路](#2.1 大致思路)

[2.2 Socket.hpp](#2.2 Socket.hpp)

[2.3 Protocol.hpp](#2.3 Protocol.hpp)

[2.3.1 Jsoncpp](#2.3.1 Jsoncpp)

[2.3.2 代码](#2.3.2 代码)

[2.4 Tcpserver.hpp](#2.4 Tcpserver.hpp)

[2.5 TcpServer.cc](#2.5 TcpServer.cc)

[2.6 TcpClient.cc](#2.6 TcpClient.cc)

[2.7 守护进程化](#2.7 守护进程化)

[2.7.1 前台进程与后台进程](#2.7.1 前台进程与后台进程)

[2.7.2 进程组&&会话&&终端](#2.7.2 进程组&&会话&&终端)

[2.7.3 守护进程](#2.7.3 守护进程)

[2.8 示例及完整代码](#2.8 示例及完整代码)


1、应用层协议

  • 前面,我们写了一些Socket编程,都是按"字符串 "进行传输。但是,当我们想传输一些"结构化的数据"怎么办?
  • 可以将结构化数据 序列化字节序列进行传输,后面再反序列化。如:
  1. 文本 格式(如Json)序列化后,是人类可读的字符串 ,再转成字节(每个字符对应 ASCII/UTF-8 编码的字节)。
  2. 二进制 格式(如Protobuf)序列化后,是人类不可读但更紧凑的字节序列
  • 之前,我们说,协议 是一种约定 ,方便快速形成共识减少通信成本
  • 现在,一方对一个结构序列化并发送另一方收到并反序列化为同一个结构 ,这种约定,就是应用层协议

注意:

  • 不序列化,直接传结构化数据的内存字节不行吗 ?可以,但是不推荐,因为不兼容
  • 为什么要说字节呢?不是二进制吗?因为字节计算机硬件和网络协议能直接处理的 "最小实际单元"

2、NetCal

2.1 大致思路

  • 实现一个NetCal (网络计算器),TcpClientTcpServer发结构化的数据 (操作数+运算符),TcpServer 给TcpClient回结构化的数据(执行结果)。
  • 以TCP为例,TCP比UDP复杂一点,UDP类似。
  1. write/send将数据拷贝到TCP的发送缓存区 ,由TCP决定怎么发送;对于网络中的数据,由TCP决定怎么接收,再read/recv拷贝TCP的接收缓冲区的数据
  2. 主机之间的通信本质 是:把发送方的发送缓冲区的数据拷贝到对端的接收缓冲区
  3. 对于缓冲区 ,则是为内核和用户之间生产者和消费者模型
  4. 因为TCP有独立的发送和接收缓冲区 ,所以是全双工 (双方同时接收和发送 消息),tcpsockfd可以边读边写

2.2 Socket.hpp

  • socket****相关的接口 进行封装
  • 多态统一接口 (父类),屏蔽实现差异(子类);便于扩展新协议(子类继承父类,进行具体实现。"开闭原则":对扩展开放(能加 UdpSocket),对修改关闭(不用改已有代码))。
  • 像TcpScoket,这样的代码,这个类可以用于listen_sockfd,也可以是sockfd,但是,对于其中一个,也用不全,里面的函数,会不会冗余?函数不占空间?逻辑上存在 "用不上的函数" ,但物理上 (内存层面)几乎没有额外开销 ,工程中 "代码复用优先于极致精简" 。
cpp 复制代码
#pragma once

#include "Common.hpp"
#include "Log.hpp"
#include <memory>

using namespace LogModule;

const static int default_sockfd = -1;
const static int default_backlog = 16;

class Socket
{
public:
    virtual void SocketOrDie() = 0; // = 0, 不需要实现 
    virtual void BindOrDie(uint16_t port) = 0;
    virtual void ListenOrDie(int backlog) = 0;
    virtual void ConnectOrDie(std::string &server_ip, uint16_t server_port) = 0;
    virtual std::shared_ptr<Socket> Accept(InetAddr* client) = 0;
    virtual int Recv(std::string* out) = 0;
    virtual int Send(const std::string& in) = 0;
    virtual void Close() = 0;
public:

    void BuildTcpServer(uint16_t port)
    {
        SocketOrDie();
        BindOrDie(port);
        ListenOrDie(default_backlog);
    }

    void BuildTcpClient(std::string &server_ip, uint16_t server_port)
    {
        SocketOrDie();
        ConnectOrDie(server_ip,server_port);
    }
};

class TcpSocket : public Socket
{
public:
    TcpSocket(int sockfd = default_sockfd)
        : _sockfd(sockfd)
    {
    }
    virtual void Close() override
    {
        if (_sockfd != default_sockfd)
            ::close(_sockfd); // ::表示调用 全局作用域 中的 close 函数
    }
    virtual void SocketOrDie() override
    {
        _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(SOCKET_ERROR);
        }
        LOG(LogLevel::INFO) << "socket success, socket: " << _sockfd;
    }

    virtual void BindOrDie(uint16_t port) override
    {
        InetAddr local(port);
        int n = ::bind(_sockfd, CONST_CONV(local.Addr()), local.AddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error!";
            exit(BIND_ERROR);
        }
        LOG(LogLevel::INFO) << "bind success, socket: " << _sockfd;
    }

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

    virtual void ConnectOrDie(std::string &server_ip, uint16_t server_port) override
    {
        InetAddr server(server_ip, server_port);
        int n = ::connect(_sockfd, CONST_CONV(server.Addr()), server.AddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "connect error!";
            exit(CONNECT_ERROR);
        }
        LOG(LogLevel::INFO) << "connect success, sockfd: " << _sockfd;
    }

    virtual std::shared_ptr<Socket> Accept(InetAddr* client) override
    {
        struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        int fd = ::accept(_sockfd, CONV(addr), &len);
        if (fd < 0)
        {
            LOG(LogLevel::WARNING) << "accept failed";
            return nullptr;
        }
        client->SetAddr(addr);
        LOG(LogLevel::INFO) << "accept success, client: " << client->StringAddr();

        return std::make_shared<TcpSocket>(fd); // 这个server的sockfd就可以调用Recv和Send方法。
    }

    virtual int Recv(std::string* out) override
    {
        char buf[1024];
        ssize_t n = ::recv(_sockfd,buf,sizeof(buf)-1,0);
        if(n > 0)
        {
            buf[n] = 0;
            *out += buf; // += 可能要不断的读 
        }

        return n;
    }

    virtual int Send(const std::string& in) override
    {
        return ::send(_sockfd,in.c_str(),in.size(),0);
    }

private:
    int _sockfd; // 既可以是listen_sockfd,也可以是sockfd,复用代码。
};

2.3 Protocol.hpp

2.3.1 Jsoncpp
  • Jsoncpp是一个用于处理JSON数据C++库 。它提供了将JSON数据序列化为字符串 以及从字符串反序列化为C++数据结构的功能。Jsoncpp是开源的,广泛用于各种需要处理JSON数据的C++项目中。
  • 特性简单高性能支持JSON标准中的所有数据类型 ,包括对象、数组、字符串、数 字、布尔值和null;方便错误处理(日志清晰)。
  • 安装ubuntu : sudo apt install -y libjsoncpp-dev
  • 使用实例:
  • <jsoncpp/json/json.h> 的路径设计是 Jsoncpp 为了避免头文件命名冲突、保持内部结构清晰、遵循行业惯例而采用的方案。它通过 "目录前缀" 的方式,让编译器和开发者都能明确区分这是 Jsoncpp 库的核心头文件,同时兼容库的安装和引用逻辑。
cpp 复制代码
#include <iostream>
#include <json/json.h>
#include <string>

int main() {
    // -------------------------- 序列化(生成字符串) --------------------------
    Json::Value root;
    root["name"] = "David";
    root["score"] = 95.5;
    root["passed"] = true;

    Json::StreamWriterBuilder writer_builder;
    std::string json_str = Json::writeString(writer_builder, root); // 简洁写法
    std::cout << "序列化结果:\n" << json_str << std::endl;

// json_str
//{
//  "name": "David",
//  "passed": true,
//  "score": 95.5
//}

    // -------------------------- 反序列化(解析字符串) --------------------------
    Json::Value parsed_root;
    Json::CharReaderBuilder reader_builder;
    std::string err_msg; // 用于存储解析错误信息

    // 创建CharReader并解析字符串
    std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader());
    bool success = reader->parse(
        json_str.c_str(),                  // 字符串起始地址
        json_str.c_str() + json_str.length(), // 字符串结束地址
        &parsed_root,                      // 解析结果存储到parsed_root
        &err_msg                           // 错误信息存储到err_msg
    );

    if (success) {
        std::cout << "\n反序列化结果:" << std::endl;
        std::cout << "姓名:" << parsed_root["name"].asString() << std::endl;
        std::cout << "分数:" << parsed_root["score"].asDouble() << std::endl;
    } else {
        std::cerr << "\n解析失败!错误信息:" << err_msg << std::endl;
    }

    return 0;
}
2.3.2 代码
  • TCP面向字节流 的,双方的收发次数可能不一致要收到完整的报文 ,需要对json串加一些标记 (最主要的是在前面加上json串的长度)。
  • UDP面向数据报 的,双方收发次数一致收到的报文是完整的
  • 这里先处理读取方面,先弱化写的方面,后面再讲解。
cpp 复制代码
#pragma once

#include "Socket.hpp"
#include "Common.hpp"
#include <jsoncpp/json/json.h>
#include <functional>
#include <memory>

class Request
{
public:
    Request()
    {
    }

    Request(int x, int y, char oper)
        : _x(x), _y(y), _oper(oper)
    {
    }

    std::string Serialize()
    {
        // -------------------------- 序列化(生成字符串) --------------------------
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;

        Json::StreamWriterBuilder writer_builder;
        std::string json_str = Json::writeString(writer_builder, root); // 简洁写法

        return json_str;
    }

    bool Deserialize(std::string &json_str)
    {
        // -------------------------- 反序列化(解析字符串) --------------------------
        Json::Value parsed_root;
        Json::CharReaderBuilder reader_builder;
        std::string err_msg; // 用于存储解析错误信息

        // 创建CharReader并解析字符串
        std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader());
        bool success = reader->parse(
            json_str.c_str(),                     // 字符串起始地址
            json_str.c_str() + json_str.length(), // 字符串结束地址
            &parsed_root,                         // 解析结果存储到parsed_root
            &err_msg                              // 错误信息存储到err_msg
        );

        if (success)
        {
            _x = parsed_root["x"].asInt();
            _y = parsed_root["y"].asInt();
            _oper = parsed_root["oper"].asInt(); // char通过ASCII映射为整数
        }
        else
        {
            LOG(LogLevel::ERROR) << "\n解析失败! 错误信息: " << err_msg;
        }

        return success;
    }

    int GetX()
    {
        return _x;
    }
    int GetY()
    {
        return _y;
    }
    char GetOper()
    {
        return _oper;
    }

private:
    int _x;
    int _y;
    char _oper;
};

class Response
{
public:
    Response()
    {
    }

    Response(int result, int code)
        : _result(result), _code(code)
    {
    }

    std::string Serialize()
    {
        // -------------------------- 序列化(生成字符串) --------------------------
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        Json::StreamWriterBuilder writer_builder;
        std::string json_str = Json::writeString(writer_builder, root); // 简洁写法

        return json_str;
    }

    bool Deserialize(std::string &json_str)
    {
        // -------------------------- 反序列化(解析字符串) --------------------------
        Json::Value parsed_root;
        Json::CharReaderBuilder reader_builder;
        std::string err_msg; // 用于存储解析错误信息

        // 创建CharReader并解析字符串
        std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader());
        bool success = reader->parse(
            json_str.c_str(),                     // 字符串起始地址
            json_str.c_str() + json_str.length(), // 字符串结束地址
            &parsed_root,                         // 解析结果存储到parsed_root
            &err_msg                              // 错误信息存储到err_msg
        );

        if (success)
        {
            _result = parsed_root["result"].asInt();
            _code = parsed_root["code"].asInt();
        }
        else
        {
            LOG(LogLevel::ERROR) << "\n解析失败! 错误信息: " << err_msg;
        }

        return success;
    }

    void ShowResult()
    {
        std::cout << "result is: " << _result << '[' << _code << ']' << std::endl;
    }

    void SetResult(int result)
    {
        _result = result;
    }

    void SetCode(int code)
    {
        _code = code;
    }

private:
    int _result;
    int _code; // 0为正确,非0为各种错误
};

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

class Protocol
{
public:
    Protocol()
    {
    }

    Protocol(func_t func)
        : _func(func)
    {
    }

    std::string Encode(const std::string &json_str)
    {
        // TCP是面向字节流的,要读到一个完整的json串,需要做些标记
        // 这里使用格式:json串长度+\r\n+json串+\r\n,即"json串长度\r\njson串\r\n"
        int len = json_str.size();

        return std::to_string(len) + sep + json_str + sep;
    }

    bool Decode(std::string &buffer, std::string *package)
    {
        int pos = buffer.find("\r\n");
        if (pos == std::string::npos)
            return false;

        int str_len = std::stoi(buffer.substr(0, pos));
        int json_str_len = pos + sep.size() + str_len + sep.size();
        if (buffer.size() < json_str_len)
            return false;

        *package = buffer.substr(pos + sep.size(), str_len);
        buffer.erase(0, json_str_len);
        return true;
    }

    void GetRequest(std::shared_ptr<Socket> &sockfd, const InetAddr &client)
    {
        std::string buffer;

        while (true)
        {
            int n = sockfd->Recv(&buffer);

            if (n > 0)
            {
                std::string json_package;

                // 1. 解析报文,不完整,继续读(所以在Recv里面是+=)
                while (Decode(buffer, &json_package))
                {
                    // 2. 反序列化
                    Request req;
                    bool OK = req.Deserialize(json_package);
                    if (!OK)
                        continue; // 跳过当前无效的报文,继续处理后续可能有效的报文,防止buffer累积

                    // 3. 执行函数
                    Response resp = _func(req);

                    // 4. 序列化
                    std::string json_str = resp.Serialize();

                    // 5. Encode,加报头
                    std::string send_str = Encode(json_str);

                    // 6. 发送
                    sockfd->Send(send_str);
                }
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << client.StringAddr() << " 退出了!";
                sockfd->Close();
                break;
            }
            else // n < 0
            {
                LOG(LogLevel::WARNING) << client.StringAddr() << " 异常";
                sockfd->Close();
                break;
            }
        }
    }

    bool GetResponse(std::shared_ptr<Socket> &client, Response *resp)
    {
        std::string buffer;

        while (true)
        {
            int n = client->Recv(&buffer);

            if (n > 0)
            {
                std::string json_package;

                // 1. 解析报文,不完整,继续读(所以在Recv里面是+=)
                if (Decode(buffer, &json_package))
                {
                    // 2. 反序列化
                    return resp->Deserialize(json_package); // 只读取一次完整的报文
                }
            }
            else if (n == 0)
            {
                std::cout << "server quit" << std::endl;
                return false;
            }
            else // n < 0
            {
                std::cout << "server error" << std::endl;
                return false;
            }
        }
    }

    std::string BuildRequestString(int x, int y, char oper)
    {
        Request req(x, y, oper);

        // 1. 序列化
        std::string json_str = req.Serialize();

        // 2. Encode.加报头
        return Encode(json_str);
    }

private:
    func_t _func;
};

2.4 Tcpserver.hpp

cpp 复制代码
#pragma once

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

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

class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port,ioservice_t service)
        : _listen_socket(std::make_unique<TcpSocket>())
        , _running(false)
        ,_service(service)
    {   
        // 1. 创建套接字
        // 2. bind套接字
        // 3. 设置监听套接字
        _listen_socket->BuildTcpServer(port);
    }

    // version-多进程
    void Start()
    {
        _running = true;

        while (_running)
        {
            // 4. 创建已连接套接字
            InetAddr client;
            std::shared_ptr<Socket> sockfd = _listen_socket->Accept(&client);
            if(!sockfd)
                continue;

            pid_t pid = fork();
            if(pid < 0)
            {
                LOG(LogLevel::WARNING) << "fork failed";
                continue;
            }
            else if(pid == 0)
            {
                _listen_socket->Close(); // 关闭listen_sockfd
                // 子进程
                if(fork() > 0)
                    exit(OK);
                // 孙子进程
                _service(sockfd,client);
                exit(OK);
            }
            else
            {
                sockfd->Close(); // 关闭sockfd
                // 父进程
                waitpid(pid,nullptr,0);
            }
        }
    }

private:
    std::unique_ptr<Socket> _listen_socket;
    bool _running;
    ioservice_t _service;
};

2.5 TcpServer.cc

  • TCP/IP 五层协议 中,OSI 七层协议应用层表示层会话层 ,全部由应用程序自行实现 ,内核(操作系统)不介入 ------ 因为用户需求 (如数据格式、会话逻辑)千变万化,内核无法预先定义通用的应用层,表示层、会话层逻辑。
  • 但是OSI 七层协议 非常好 ,因为在应用层设计 的时候,这样分层,逻辑清晰,便于理解和模块化设
cpp 复制代码
#include "NetCal.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include "Daemon.hpp"
#include <memory>

// ./tcpserver server_port
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " server_port" << std::endl;
        exit(USAGE_ERROR);
    }

    std::cout << "服务器已经启动,已经是一个守护进程了" << std::endl;
    Daemon(0,0); // daemon(0,0);

    // Enable_Console_Log_Strategy();
    Enable_File_Log_Strategy(); // 向文件里打印日志

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

    // 应用层 具体功能的执行
    std::unique_ptr<NetCal> net_cal = std::make_unique<NetCal>();

    // 表示层 数据格式的转化
    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>([&net_cal](Request& req){
        return net_cal->Execute(req);
    });

    // 会话层 通信连接的管理
    std::unique_ptr<TcpServer> tcp_server = std::make_unique<TcpServer>(server_port,
    [&protocol](std::shared_ptr<Socket>& sockfd, const InetAddr &client){
        protocol->GetRequest(sockfd,client);
    });

    tcp_server->Start();

    return 0;
}

2.6 TcpClient.cc

cpp 复制代码
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"

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)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        exit(USAGE_ERROR);
    }

    Enable_Console_Log_Strategy();

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

    // 1. 创建套接字
    // 2. 设置连接套接字
    std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();
    client->BuildTcpClient(server_ip,server_port);

    auto protocol = std::make_unique<Protocol>();

    while (true)
    {
        int x, y;
        char oper;
        GetDataFromStdin(&x, &y, &oper);

        std::string request_string = protocol->BuildRequestString(x,y,oper);

        client->Send(request_string);

        Response resp;
        bool OK = protocol->GetResponse(client,&resp);
        if(!OK)
            break;
        resp.ShowResult();
    }

    client->Close();
    return 0;
}

2.7 守护进程化

2.7.1 前台进程与后台进程
  • 路径/可执行程序前台进程 标准输入(键盘 )中获取内容只能有一个,因为标准输入的内容要给到一个确定的进程。
  • 路径/可执行程序 &后台进程无法 从标准输入(键盘 )中获取内容可有多个
  • 前台进程和后台进程,都可以向标准输出(显示器)打印内容。
  • 一个现象,运行前台程序,父进程创建子进程,Ctrl+C父进程终止,父进程退出,子进程被一号进程"领养",变成后台进程,Ctrl+C无法杀死子进程。
  • jobs查看所有后台进程
  • fg 任务号指定进程提到前台
  • Ctrl+Z暂停前台进程提到后台
  • bg 任务号 。让后台进程恢复运行
2.7.2 进程组&&会话&&终端
  • 前面的任务 ,也称为作业
  • PGID,即进程组id ,为组长的pid,即使组长退了,也不变。作业进程组 (一个或多个进程)的形式运行
  • SID,即session(会话) id。进程组属于某一个会话。
  • TTY,即终端 。终端是会话的 "载体" 和 "控制界面"
2.7.3 守护进程
  • 守护进程 也称精灵进程
  • 关闭终端 时,在该会话里的进程可能会受影响 (如:还可能向该终端打印等),守护进程 就是让进程完全脱离终端在后台稳定运行
cpp 复制代码
#pragma once

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

using namespace LogModule;

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

void Daemon(int nochdir, int noclose)
{
    // 1. 设置信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCLD, SIG_IGN);

    // 2. 孤儿进程
    if (fork())
        exit(0);

    // 3. 创建新会话
    setsid();

    // 4. 更改目录
    if (nochdir == 0)
        chdir("/");

    // 5. 重定向标准输入,标准输出,标准错误
    if (noclose == 0)
    {
        int fd = open(dev.c_str(), O_RDWR); // 以读写的方式打开
        if (fd < 0)
        {
            LOG(LogLevel::FATAL) << "open " << dev << " error";
            exit(OPEN_ERROR);
        }
        else
        {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }
}
  • SIGPIPE:当一个进程向已经关闭的管道(pipe)或网络连接(如 TCP socket)写入数据时,默认处理方式是终止进程。守护进程通常作为服务端长期运行,可能会遇到 "客户端意外断开连接后,服务端仍尝试向其写数据" 的情况(比如客户端崩溃、网络中断)。此时若不处理 SIGPIPE,守护进程会被直接杀死,导致服务中断。signal(SIGPIPE, SIG_IGN) 确保服务端不会因 "向已关闭连接写数据" 而被终止
  • SIGCHLD 信号的触发场景:当子进程退出时,操作系统会向其父进程发送 SIGCHLD 信号,通知父进程 "子进程已终止",默认处理方式是忽略信号(什么都不做)。守护进程可能会创建子进程处理任务,如果不处理 SIGCHLD,大量子进程退出后会积累僵尸进程,耗尽系统的进程 ID 资源(PID 是有限的),最终导致无法创建新进程。signal(SIGCHLD, SIG_IGN),自动回收退出的子进程
  • 父进程创建子进程(子进程天生非进程组领头,满足setsid()条件 )→ 父进程退出子进程成孤儿被 init 收养,脱离原父进程控制 ,就变成后台进程了)→ 子进程调用 setsid()成功创建新会话,脱离原终端 )→ 最终成为独立的守护进程
  • 通常将工作目录切换到根目录/ ),避免守护进程占用某个挂载的文件系统 (如 U 盘),导致该文件系统无法卸载
  • 守护进程不需要与终端交互 ,因此关闭 标准输入,标准输出,标准错误/dev/null就是一个"黑洞",读不到数据,写入数据也被直接丢弃。重定向后所,确保所有标准流操作都合法守护进程可以通过向文件写日志 (注意守护进程的路径已经改变了)
  • 设置守护进程也有对应的接口:
cpp 复制代码
#include <unistd.h>

int daemon(int nochdir, int noclose);

If nochdir is zero, daemon() changes the process's current working directory to the root  directory  ("/");
  otherwise,  the  current working directory is left unchanged.

If  noclose  is  zero,  daemon() redirects standard input, standard output and standard error to /dev/null; 
  otherwise, no changes are made to these file descriptors.

RETURN VALUE
  (This function forks, and if the fork(2) succeeds, 
    the parent calls _exit(2), so that further errors are seen by the child only.)
  Onsuccess daemon() returns zero.
    If an error occurs, daemon() returns -1
    and sets errno to any of the errors specified for the fork(2) and setsid(2).

2.8 示例及完整代码

  • 示例:
  • 需要sudo,才能在**/var/log/创建my.log文件,并且需要sudo kill server_netcal**。
  • 完整代码:NetCal
相关推荐
lili-felicity3 小时前
解决VMware Workstation Pro 17中Ubuntu 24.04无法复制粘贴
linux·运维·ubuntu
虚伪的空想家3 小时前
HUAWEI A800I A2 aarch64架构服务器鲲鹏920开启虚拟化功能
linux·运维·服务器·显卡·npu·huawei·鲲鹏920
笨蛋少年派4 小时前
将 MapReduce 程序打成 JAR 包并在 Linux 虚拟机的 Hadoop 集群上运行
linux·jar·mapreduce
刚刚觉醒的小菜鸡4 小时前
ssh连接本地虚拟机
linux·服务器·ssh
持梦远方4 小时前
Linux之认识理解目录
linux·运维·服务器
瑶总迷弟4 小时前
使用 Docker 和 docker-compose 快速部署 openGauss
linux·数据库·云原生·eureka
BothSavage4 小时前
Ubuntu-8卡H20服务器升级nvidia驱动+cuda版本
linux·服务器·ubuntu·gpu·nvidia·cuda·nvcc
---学无止境---4 小时前
Linux中异常初始化和门设置函数的实现
linux
waves浪游4 小时前
基础开发工具(上)
linux