【计算机网络_应用层】TCP应用与相关API守护进程

需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦)

文章目录

  • 1. 相关使用接口
  • 2. 代码实现
    • 2.1 日志组件
    • 2.2 Server端
    • 2.3 Client端
    • 2.3 bug解决
  • 3. 守护进程
    • 3.1 守护进程是什么
    • 3.2 守护进程相关的使用
    • 3.3 守护进程化的实现原理

1. 相关使用接口

tcp协议和udp协议的接口基本相似。使用逻辑也是:1. 创建对应的socket文件套接字对象; 2. bind自己的网络信息;3. 进行相关通信

只是由于tcp协议的相关特性,所以tcp通信方式有一些不同点。

1. 对于服务端

在创建对应socket文件套接字对象并bind完成后需要设置sockfd为监听状态,使用listen系统调用。

头文件:
	#include <sys/types.h>
	#include <sys/socket.h>
函数原型:
	int listen(int sockfd, int backlog);
参数解释:
	sockfd:要设置的文件套接字对象
	backlog:最多允许这么多个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5), 
函数描述:
	将sockfd文件套接对象设置为监听状态
返回值:
	调用成功返回0,失败返回-1同时设置错误码

在设置sockfd为监听状态之后,在底层进行”三次握手“之后,服务端需要调用accept接受客户端的连接。

头文件:
	#include <sys/types.h>
	#include <sys/socket.h>
函数原型:
	int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数解释:
	sockfd:要设置的文件套接字对象(这里传的是监听的sockfd)
	addr:接受的连接对应的相关网络属性
	addrlen:addr对应的对象的大小
函数描述:
	服务端调用accept接受客户端的连接。如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来
返回值:
	调用成功返回一个新的文件套接字,用于进行本次的客户端和服务端通信,调用失败返回-1同时设置错误码

2. 对于客户端

同样在初始化的时候需要创建socket文件套接字,同样的不需要程序员显示bind。也不需要listen和accept。接下来需要做的事情就是发送连接请求,使用connect系统调用

头文件:
	#include <sys/types.h>
	#include <sys/socket.h>
函数原型:
	int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数解释:
	sockfd:发送链接请求的文件套接字对象
	addr:连接对应的相关网络属性
	addrlen:addr对应的对象的大小
函数描述:
	客户端使用sockfd向指定服务器的指定端口发起TCP链接请求
返回值:
	调用成功返回0,调用失败返回-1同时设置错误码

2. 代码实现

2.1 日志组件

一般来说,服务器在运行的时候,不会在当前shell输出相关的运行结果,而是在日志中输出,所以,这里我们现在封装一个日志的小组件

1. 组件需求

  1. 使用logMessage函数可以将相关日志信息写入预设的文件中(在当前目录创建对应文件)
  2. 每条日志信息都会有相关的日志等级,不同等级在不同文件中
  3. 日志内容支持format和可变参数

2. 代码实现

#include <unistd.h>
#include <iostream>
#include <cstdio>
#include <ctime>
#include <cstdarg>

// 这里是日志等级对应的宏
#define DEBUG (1 << 0)
#define NORMAL (1 << 1)
#define WARNING (1 << 2)
#define ERROR (1 << 3)
#define FATAL (1 << 4)

#define NUM 1024 // 日志行缓冲区大小
#define LOG_NORMAL "log.txt" // 日志存放的文件名
#define LOG_ERR    "err.txt"

const char *logLevel(int level) // 把日志等级转变为对应的字符串
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return "UNKNOW";
    }
}
//[日志等级][时间][pid]日志内容
void logMessage(int level, const char *format, ...) // 核心调用
{
    char logprefix[NUM]; // 存放日志相关信息
    time_t now_ = time(nullptr);
    struct tm *now = localtime(&now_);
    snprintf(logprefix, sizeof(logprefix), "[%s][%d年%d月%d日%d时%d分%d秒][pid:%d]",
             logLevel(level), now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec, getpid());

    char logcontent[NUM];
    va_list arg; // 声明一个变量arg指向可变参数列表的对象
    va_start(arg, format); // 使用va_start宏来初始化arg,将它指向可变参数列表的起始位置。
    // format是可变参数列表中的最后一个固定参数,用于确定可变参数列表从何处开始
    vsnprintf(logcontent, sizeof(logcontent), format, arg); // 将可变参数列表中的数据格式化为字符串,并将结果存储到logcontent中

    FILE *log =  fopen(LOG_NORMAL, "a");
    FILE *err = fopen(LOG_ERR, "a");
    if(log != nullptr && err != nullptr)
    {
        FILE *curr = nullptr;
        if(level == DEBUG || level == NORMAL || level == WARNING) curr = log;
        if(level == ERROR || level == FATAL) curr = err;
        if(curr) fprintf(curr, "%s%s\n", logprefix, logcontent);

        fclose(log);
        fclose(err);
    }
}

2.2 Server端

/* tcpServer.hpp */
#pragma once

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

#include <string>

#include "log.hpp"

namespace Server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    void serviceIO(int sock) // 服务端调用
    {
        char buffer[1024];
        while (true)
        {
            ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // 目前我们把读到的数据当成字符串, 截止目前
                buffer[n] = 0;
                std::cout << "recv message: " << buffer << std::endl;

                std::string outbuffer = buffer;
                outbuffer += " server[echo]";

                write(sock, outbuffer.c_str(), outbuffer.size()); // 这里再把结果写进sock中,意为返回给客户端
            }
            else if (n == 0)
            {
                // 代表client退出
                logMessage(NORMAL, "client quit, me too!");
                break;
            }
        }
        close(sock);
    }

    class tcpServer
    {
    public:
        tcpServer(uint16_t &port) : _port(port)
        {
        }
        void initServer()
        {
            // 1. 创建socket文件套接字对象
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock == -1)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success:%d", _listensock);
            // 2.bind自己的网络信息
            sockaddr_in local;
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;
            int n = bind(_listensock, (struct sockaddr *)&local, sizeof local);
            if (n == -1)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");
            // 3. 设置socket为监听状态
            if (listen(_listensock, gbacklog) != 0) // listen 函数
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }
        void start()
        {
            while (true)
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next");
                    continue;
                }

                serviceIO(sock); // 使用

                close(sock); // 使用之后要关闭,否则会造成文件描述符泄露
            }
        }
        ~tcpServer() {}

    private:
        uint16_t _port;
        int _listensock;
    };

} // namespace Server

/* tcpServer.cc */
#include <iostream>
#include <memory>

#include "tcpServer.hpp"

using namespace Server;

static void Usage(const char *proc)
{
    std::cout << "\n\tUsage:" << proc << " local_port\n";
}

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer();
    tsvr->start();
    return 0;
}

2.3 Client端

/* tcpClient.hpp */
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <string>

#include "log.hpp"

namespace Client
{
    class tcpClient
    {
    public:
        tcpClient(uint16_t &port, std::string &IP) : _serverPort(port), _serverIP(IP), _sockfd(-1) {}

        void initClient()
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if(_sockfd == -1)
            {
                std::cerr << "create socket error" << std::endl;
                exit(2);
            }

        }

        void run()
        {
            struct sockaddr_in server;
            server.sin_family = AF_INET;
            server.sin_port = htons(_serverPort);
            server.sin_addr.s_addr = inet_addr(_serverIP.c_str());

            if(connect(_sockfd, (struct sockaddr*)&server, sizeof server) != 0)
            {
                // 链接失败
                std::cerr << "socket connect error" << std::endl;
            }
            else
            {
                std::string msg;
                while(true)
                {
                    std::cout << "Please Enter# ";
                    std::getline(std::cin, msg);
                    write(_sockfd, msg.c_str(), msg.size());

                    char buffer[NUM];
                    int n = read(_sockfd, buffer, sizeof(buffer) - 1); // 按照字符串的形式读取
                    if(n > 0)
                    {
                        // 目前先把读到的数据当作字符串处理
                        buffer[n] = 0;
                        std::cout << "Server 回显# " << buffer << std::endl;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        ~tcpClient()
        {
            if(_sockfd >= 0) close(_sockfd); // 使用完关闭,防止文件描述符泄露(当然这里也可以不写,当进程结束之后一切资源都将被回收)
        }

    private:
        uint16_t _serverPort;
        std::string _serverIP;
        int _sockfd;
    };

} // namespace Client
/* tcpClient.cc */
#include <memory>
#include <string>

#include "tcpClient.hpp"
using namespace Client;

static void Usage(const char *proc)
{
    std::cout << "\n\tUsage:" << proc << " server_ip server_port\n";
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string IP = argv[1];
    uint16_t port = atoi(argv[2]);
    std::unique_ptr<tcpClient> tclt(new tcpClient(port, IP));
    tclt->initClient();
    tclt->run();

    return 0;
}

image-20240226002443205

2.3 bug解决

这里会出现一个问题:在此时如果再有另一个客户端进行通信,就会出现其他客户端被阻塞的问题

image-20240226002542298

这是因为我们在服务端的serviceIO中的执行没有结束,而且由于实现的是死循环,所以也不可能结束,这就造成了服务端一直在阻塞的情况。那么如何解决呢?

1. 实现多进程版本

多进程的实现思想就是:每次收到新请求的时候,都创建一个子进程,让子进程来执行对应任务,父进程继续监听,但是由于创建的子进程需要被父进程等待回收,否则就会出现僵尸进程。那么这里的解决方案就是:让子进程再创建一个子进程,最终让孙子进程来执行本次请求对应的任务,父进程直接exit,爷爷进程等待父进程结束后继续监听。此时孙子进程就变成了孤儿进程,由OS直接接收管理。

这里需要更改的就只有tcpServer.hpp文件中的start函数,这里附上更改后的代码

void start()
{
    while (true)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof peer;
        int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
        {
            logMessage(ERROR, "accept error, next");
            continue;
        }
        pid_t id = fork();
        if (id == 0)
        {
            close(_listensock); // 子进程不会使用监听socket,但是创建子进程的时候写时拷贝会拷贝,这里先关掉
            // 子进程再创建子进程
            if (fork() > 0)
                exit(0); // 父进程退出
            // 走到当前位置的就是子进程
            serviceIO(sock); // 使用
            close(sock); // 关闭对应的通信socket(这里也可以不关闭,因为此进程在下个语句就会退出)
            exit(0); // 孙子进程退出
        }
        // 走到这里的是监听进程(爷爷进程)
        pid_t n = waitpid(id, nullptr, 0);
        if(n > 0)
        {
            logMessage(NORMAL, "wait success pid:%d", n);
        }
        close(sock); // 使用之后要关闭,否则会造成文件描述符泄露
    }
}

image-20240226082008918

现在再测试,服务器就能够同时处理多个客户端的请求。

2. 实现多线程版本

但是,我们知道OS在创建线程的时候,需要的成本是非常高的,但是线程就非常轻量级,所以使用线程来处理服务器请求是更加合理的,所以这里实现一下多线程的版本

void start()
{
    while (true)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof peer;
        int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
        {
            logMessage(ERROR, "accept error, next");
            continue;
        }
        // version 3:多线程版本
        pthread_t tid;
        pthread_create(&tid, nullptr, routine, new ThreadData(this, sock)); // 创建新线程,让新线程调用routine然后去执行serviceIO
    }
}        
static void *routine(void *arg)
{
    // 由于不能让主线程等待新线程执行完毕,所以这里进行线程分离
    pthread_detach(pthread_self());
    ThreadData* args = static_cast<ThreadData*>(arg);
    serviceIO(args->_sock);
    close(args->_sock); // 使用完之后回收sock
    delete args; // 回收空间
    return nullptr;
}

image-20240226083955051

3. 实现线程池版本

当然,上述的两种实现方式是具有一些优化空间的,因为每次在创建子进程/新线程的时候都会有消耗,这样会降低效率,而且当突然出现很多长时间的请求的时候,服务器就会同时接收到很多请求,会一直创建子进程/新线程,可能会导致服务器崩溃,所以可以使用我们之前写过的一个小组件线程池来改写

void start()
{
    ThreadPool<Task>::getInstance()->run(); // 初始化线程池,让他跑起来
    logMessage(NORMAL, "init thread pool success");

    while (true)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof peer;
        int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
        {
            logMessage(ERROR, "accept error, next");
            continue;
        }
        // version 4:线程池版本
        ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));
    }
}
/* 小组件 */
// Task.hpp
#pragma once

#include <string>
#include <iostream>
#include <functional>


class Task
{
public:
    using func_t = std::function<void(int)>;

public:
    Task() {}
    Task(int sock, func_t func)
        : _sock(sock), _callback(func)
    {
    }
    void operator()()
    {
        _callback(_sock);
    }

private:
    int _sock;
    func_t _callback;
};
// Thread.hpp
#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <cassert>

class Thread
{
public:
    using func_t = std::function<void *(void *)>; // 定义func_t类型
    static int number;                            // 线程编号,按照一次运行时的调用次数计数
public:
    Thread()
    {
        char *buffer = new char[64];
        name_ = "thread-" + std::to_string(++number);
    }
    static void *start_routine(void *args)
    {
        Thread *_this = static_cast<Thread *>(args);
        void *ret = _this->run(_this->args_);
        return ret;
    }
    void *run(void *arg)
    {
        return func_(arg);
    }
    void start(func_t func, void *args)
    {
        func_ = func;
        args_ = args;
        int n = pthread_create(&tid_, nullptr, start_routine, this);
        assert(n == 0);
        (void)n;
    }
    void join()
    {
        int n = pthread_join(tid_, nullptr);
        assert(n == 0);
        (void)n;
    }
    std::string GetTaskName()
    {
        return name_;
    }
    ~Thread() {}

private:
    std::string name_; // 线程名
    pthread_t tid_;    // 线程id
    func_t func_;      // 线程调用的函数
    void *args_;       // 线程调用函数的参数
};
int Thread::number = 0;
// ThreadPool.hpp
#pragma once
#include "LockGuard.hpp"
#include "Thread.hpp"
#include <vector>
#include <queue>
#include <string>
#include <iostream>
#include <mutex>

const int gnum = 5; // 线程池中默认的线程个数

template <class T>
class ThreadPool; // 线程池类的声明

/* 线程数据类,保存线程对应的内容包括线程池对象的指针和线程名 */
template <class T>
class ThreadData
{
public:
    ThreadData(ThreadPool<T> *tp, const std::string &n) : threadpool(tp), name(n){};

public:
    ThreadPool<T> *threadpool;
    std::string name;
};

/* 线程池类的实现 */
template <class T>
class ThreadPool
{
public:
    static void *handleTask(void *args) // 线程需要执行的回调函数
    {
        ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
        while (true)
        {
            T t; // 构建任务对象
            {
                LockGuard lockGuard(td->threadpool->mutex()); // 上锁
                while (td->threadpool->isQueueEmpty())
                {
                    // 如果任务队列为空,线程挂起,等待队列中被填充任务
                    td->threadpool->threadWait();
                }
                t = td->threadpool->pop(); // 如果队列中有任务,就拿出任务
            }
            // 任务在锁外执行
            t();
        }
        delete td;
        return nullptr;
    }

public: // 给handleTask调用的外部接口
    pthread_mutex_t *mutex() { return &_mutex; }
    bool isQueueEmpty() { return _task_queue.empty(); }
    void threadWait() { pthread_cond_wait(&_cond, &_mutex); }
    T pop() // 获取线程池中任务队列里需要执行的下一个任务
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

public:                               // 需要暴露给外部的接口

    void run() // 为所有线程对象创建真正的执行流,并执行对应的回调函数
    {
        for (const auto &thread : _threads)
        {
            ThreadData<T> *td = new ThreadData<T>(this, thread->GetTaskName()); // 构造handleTask的参数对象
            thread->start(handleTask, td);                                      // 调用该线程的start函数,创建新线程执行指定的handleTask任务
            // std::cout << thread->GetTaskName() << " start..." << std::endl;
        }
    }
    void push(const T &in) // 将指定任务push到队列中
    {
        // 加锁
        LockGuard lockGuard(&_mutex); // 自动加锁,在当前代码段结束之后调用LockGuard的析构函数解锁
        _task_queue.push(in);
        pthread_cond_signal(&_cond); // 发送信号表示此时task_queue中有值,让消费者可以使用
    }
    ~ThreadPool() // 析构函数,销毁互斥量和条件变量,delete所有thread对象指针,自动调用thread对象的析构函数
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for (auto &thread : _threads)
        {
            delete thread;
        }
    }

    static ThreadPool<T> *getInstance()
    {
        if(nullptr == tp)
        {
            std::lock_guard<std::mutex> lck(_singletonlock);
            if(nullptr == tp)
            {
                tp = new ThreadPool<T> ();
            }
        }
        return tp;
    }
private: // 单例模式需要私有化的接口
    ThreadPool(const int &num = gnum) // 构造函数,初始化互斥量和条件变量,构建指定个数的Thread对象
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        for (int i = 0; i < num; ++i)
        {
            _threads.push_back(new Thread());
        }
    }
    //delete拷贝构造和析构函数
    ThreadPool(const ThreadPool<T> &) = delete;
    ThreadPool<T> *operator=(const ThreadPool<T> &) = delete;

private:
    std::vector<Thread *> _threads; // 保存所有线程对象的指针
    std::queue<T> _task_queue;      // 需要被分配的任务队列
    pthread_mutex_t _mutex;         // 任务队列需要被互斥的访问
    pthread_cond_t _cond;           // 生产任务和消费任务之间需要进行同步

    static ThreadPool<T> *tp; // 静态成员,存放ThreadPool指针
    static std::mutex _singletonlock; // 创建线程安全的单例对象要加的锁
};
template<class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;
template<class T>
std::mutex ThreadPool<T>::_singletonlock;

image-20240226085928431

3. 守护进程

3.1 守护进程是什么

在我们之前实现的代码中,所有的Server端在运行的时候都会占用前台的Shell,当这个Shell退出之后,对应的进程也就会退出

image-20240226091312689

但是我们知道:在实际的应用环境中,是不会出现这种情况的,这是因为在实际部署服务的时候,会将对应的服务守护进程化,所谓的守护进程化就是让对应的进程不受当前会话的影响

守护进程的理解

我们是使用远程命令行工具来连接我们的云服务器的,这个工具在Windows下会使用Xshell,macOS下使用自带的终端或者iTerm,或者会使用VScode远程连接带有的shell…

在我们登录成功之后,OS在内部会创建一个会话,在此会话内部创建一个前台进程bash进行命令行解释,此时我们就可以想bash中输入命令,OS帮我们执行。

在一个会话(session)中,同一时间只能有一个前台进程但是可以有任意个后台进程的存在

当这个会话结束之后,会话内所有的进程都将会退出,这也就是为什么我们的服务不能长久的在服务器中运行

3.2 守护进程相关的使用

1. &jobs

&可以让一个命令在后台运行

jobs可以查看当前会话的所有作业(现在可以理解成进程)

image-20240226092835820

  • 作业前面的[]内部的数字就是作业号

为什么这个服务运行起来后还能够输入命令?

这是因为这个服务变成后台作业了,一个会话在同一时刻有且只有一个前台进程

  • 通过PGID可以确定同一个进程组
  • 通过SID可以确定同一个会话

image-20240226093227728

  • fg+作业号:把对应作业放在前台
  • CTRL+z:暂停作业(一个任务在前台如果暂停了会立马放在后台)
  • bg+作业号:启动作业

image-20240226093745860

2. daemon

OS提供了一个守护进程化的接口,但是我们不建议使用,因为这个接口会产生一些未定义行为,所以我们自己封装一个小组件用于守护进程化。

image-20240226094239043

3.3 守护进程化的实现原理

守护进程化的实现原理就是:让这个进程自己成为一个会话组,独立出来就可以不受当前会话的影响

头文件:
	#include <unistd.h>
函数原型:
	pid_t setsid();
函数解释:
	对于一个非会话组组长的进程,使其成为一个新的会话组,并且调用进程成为组长
返回值:
	如果调用成功,返回一个新的SID(SID就是当前会话组的组长的pid);调用失败返回-1同时设置错误码

守护进程化组件的实现

// daemon.hpp
#pragma once

#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV "/dev/null" // 这个路径是一个“黑洞”,写入的所有数据都会被“吃掉”,不会被读取

void deamonSelf(const char *curPath = nullptr) // 可选参数,如果传入非空,就更改“当前路径”
{
    // 1. 让调用进程忽略掉所有异常信号
    signal(SIGPIPE, SIG_IGN);
    // 2. 让当前进程成为非组长进程
    if (fork() > 0)
        exit(0); // 创建子进程,然后将父进程退出确保调用setsid的进程是非组长进程

    // 3. 调用setsid创建新的会话组
    pid_t n = setsid();
    assert(n != -1);

    // 4. 守护进程是脱离终端的,需要关闭或者重定向以前进程默认打开的文件,这里我们采用重定向的方法更安全
    int fd = open(DEV, O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
    else
    {
        close(0);
        close(1);
        close(2);
    }
    // 5. 可选:是否更改当前路径
    if (curPath != nullptr)
        chdir(curPath);
}
#include <iostream>
#include <memory>

#include "tcpServer.hpp"
#include "daemon.hpp"

using namespace Server;

static void Usage(const char *proc)
{
    std::cout << "\n\tUsage:" << proc << " local_port\n";
}

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer();
    deamonSelf(); // 当前进程守护进程化
    tsvr->start();
    return 0;
}

image-20240226100859218


本节完…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/422618.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】数据库的操作

【MySQL】数据库的操作 目录 【MySQL】数据库的操作创建数据库数据库的编码集和校验集查看系统默认字符集以及校验规则查看数据库支持的字符集查看数据库支持的字符集校验规则校验规则对数据库的影响数据库的删除 数据库的备份和恢复备份还原不备份整个数据库&#xff0c;而是备…

Qt 简约美观的加载动画 小沙漏风格 第六季

这次和大家分享一个沙漏风格的加载动画 效果如下: 这是本系列的第六季了, 本次内容的关键在于cubicTo函数的使用, 在这里分享一个非常好用的网站https://www.desmos.com/calculator/cahqdxeshd 在这上面可以手动拖动贝塞尔曲线的控制点, 并且显示了起终点和两个控制点的精确坐…

深入剖析k8s-Pod篇

为什么需要Pod&#xff1f; 进程是以进程组的方式组织在一起。受限制容器的“单进程模型”&#xff0c; 成组调用没有被妥善处理&#xff08;资源调用有限&#xff09;&#xff0c;使用资源囤积则导致复杂度上升。 在k8s项目中&#xff0c;Pod的实现需要使用一个中间容器——…

兰州理工大学数据科学与计算机科学20级学生实训项目正式开班

2月26日星期一&#xff0c;兰州理工大学数据科学与计算机科学20级学生实训项目正式开班。计通学院计算机科学与大数据专业相关领导及老师、泰迪智能科技华北区域负责人曹玉红参与本次开班仪式。 兰州理工大学计算机与通信学院&#xff08;软件学院&#xff09;肇始于1984年原甘…

Unity 游戏设计模式:单例模式

本文由 简悦 SimpRead 转码&#xff0c; 原文地址 mp.weixin.qq.com 单例模式 在 C# 游戏设计中&#xff0c;单例模式是一种常见的设计模式&#xff0c;它的主要目的是确保一个类只有一个实例&#xff0c;并提供一个全局访问点。单例模式在游戏开发中具有以下几个作用&#xf…

实例驱动计算机网络

文章目录 计算机网络的层次结构应用层DNSHTTP协议HTTP请求响应过程 运输层TCP协议TCP协议面向连接实现TCP的三次握手连接TCP的四次挥手断开连接 TCP协议可靠性实现TCP的流量控制TCP的拥塞控制TCP的重传机制 UDP协议 网际层IP协议&#xff08;主机与主机&#xff09;IP地址的分类…

软考53-上午题-【数据库】-关系模式的范式

一、范式 关系模式的规范化标准&#xff0c;达到范式的关系才是规范化的。 1-1、目前有6种范式&#xff1a; 第一范式&#xff1a;1NF&#xff1b;&#xff08;满足最低要求&#xff09;第二范式&#xff1a;2NF&#xff1b;&#xff08;在第一范式的基础上进一步的满足一些要…

2024最新版聚合支付彩虹易支付PHP源码

彩虹易支付是一种便捷的支付解决方案&#xff0c;属于聚合易支付平台的一部分。它提供了即时到账功能&#xff0c;无需签约即可使用。通过这个平台&#xff0c;您可以方便地接入多种支付方式&#xff0c;包括支付宝当面付、QQ钱包、财付通、微信扫码支付和个体商户聚合收款码等…

2024尼泊尔徒步旅行记

2.8日至2.24日这段时间去了尼泊尔徒步旅行&#xff0c;是的今年没有在家过春节。 加德满都 当我到了尼泊尔的首都加德满都&#xff0c;下飞机了坐在一辆狭小的出租车上&#xff0c;行驶在狭窄的路上。看到的是路上很多人&#xff0c;有很多摩托车&#xff0c;没有红绿灯&…

剑指 Offer 42. 连续子数组的最大和

目录 题目 代码实现 输出 题目 代码实现 #include <vector> #include <iostream> using namespace std;class Solution { public:int maxSubArray(vector<int>& nums) {vector<int> sum;sum.resize(nums.size() + 1);for (size_t i = 1; i <…

UCSF DOCK 分子对接详细案例(01)- rigid, fixed anchor, flexible dock

欢迎浏览我的CSND博客&#xff01; Blockbuater_drug …点击进入 文章目录 前言一、操作环境二、研究背景三、受体-配体结构文件准备3.1准备文件夹DOCK_workdir, 下载晶体结构3.1.1 来自湿实验的受体配体共晶结构&#xff1a;3.1.2 来自深度学习和语言模型推理预测的蛋白结构&a…

用电商API,轻松获取淘宝商品数据!

我们进入到淘宝商品数据页面&#xff0c;按F12打开开发者模式&#xff0c;对页面进行观察&#xff0c;我们发现淘宝页面是Ajax方式加载的&#xff0c;而且它的接口参数很复杂且没有固定的规律&#xff0c;但是Selenium又被淘宝反爬限制了&#xff0c;所以我们不能使用Ajax来获取…

vue2 设置keepAlive之后怎么刷新页面数据

场景&#xff1a;移动端有 A、B、C 三个页面&#xff0c;A、B 页面路由设置了keepAlive属性&#xff0c;有下面两个场景&#xff1a; 1、A 页面 --> B 页面&#xff0c;B 页面刷新。 2、C 页面 --> B页面&#xff0c;B 页面不刷新。 一、分为以下两个情况讨论&#xf…

cAdvisor+Prometheus+Grafana 搞定Docker容器监控平台

cAdvisorPrometheusGrafana cAdvisorPrometheusGrafana 搞定Docker容器监控平台1、先给虚拟机上传cadvisor2、What is Prometheus?2.1、架构图 3、利用docker安装普罗米修斯4、安装grafana cAdvisorPrometheusGrafana 搞定Docker容器监控平台 1、先给虚拟机上传cadvisor cAd…

fastjson反序列化漏洞

fastjson反序列化漏洞 文章目录 fastjson反序列化漏洞1.漏洞原理2.探测方式2.1 查看回显2.2 LDAP/RMI服务测试 3.LDAP/RMI服务搭建要求4.漏洞复现4.1 fastjson <1.2.47 反序列化导致任意命令执行漏洞4.1.1 环境准备4.1.2 复现过程 4.2 fastjson <1.2.24 反序列化导致任意…

OpenHarmony、HarmonyOS打开编辑 PDF 等操作的三方组件使用教程

项目场景: 随着数字化时代的发展,PDF 文档成为广泛应用于各行业的重要文件格式。为了提高OpenHarmony/HarmonyOS生态系统的功能性和用户体验,我们需要一款支持打开、编辑PDF文件的应用程序。 使用户能够轻松打开、浏览和编辑PDF文件。该应用将充分利用OpenHarmony/HarmonyO…

激光雷达原理

全球汽车行业正在进行自动化变革&#xff0c;这将彻底改变交通运输的安全和效率水平。 戴姆勒在S级豪华车型中引入L3级自动驾驶&#xff08;L3&#xff0c;在特定条件下自动驾驶&#xff0c;人类驾驶员一旦被请求就会随时接管&#xff09;是自动驾驶革命的一个重大突破。其他多…

Zoho Projects 8.0震撼来袭,项目管理再升级! 全方位功能变化解读

有效的项目管理方法保证项目按照进度、成本、质量要求进行交付&#xff0c;是针对单个项目或项目群的管理&#xff0c;从而确保项目符合企业的战略目标&#xff0c;实现企业收益最大化。 对于项目管理工作来说&#xff0c;我们通常会认为只有专业的经理才能胜任&#xff0c;软件…

k8s中容器的调度与创建:CRI,cgroup

container调度与创建 选自&#xff1a;K8s、CRI与container - packy的文章 - 知乎 https://zhuanlan.zhihu.com/p/102897620 Cgroup创建&#xff1a; cgexec -g cpu,memory:$UUID \ > unshare -uinpUrf --mount-proc \ > sh -c "/bin/hostname $UUID &…

力扣hot100题解(python版36-40题)

36、二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 *中序 遍历* 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&am…