多路转接之PollEpoll

news2024/10/9 18:04:22

文章目录

  • Poll
    • poll函数接口
    • poll的优缺点
    • poll示例
      • Util.hpp(所用到的函数方法)
      • Server.hpp
      • Server.cc
      • log.hpp(日志)
  • Epoll
    • epoll的相关系统调用
      • epoll_create
      • epoll_ctl
      • epoll_wait
    • epoll工作原理
    • epoll的优点
    • epoll工作方式
    • 对比LT和ET
    • epoll服务器(LT模式)示例
      • Util.hpp(需要调用的函数)
      • Server.hpp
      • Server.cc
      • log.hpp
      • 演示效果

Poll

poll函数接口

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数一是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合

对于pollfd结构体:

// pollfd结构
struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events */
    short revents; /* returned events */
};

fd表示需要关心的文件描述符

events表示需要关心该fd的哪些事件

revents表示该fd的哪些事件已经就绪

因此fd + events 用来输入的时候看,fd + revents 用来输出的时候看

对于events和revents的取值:

image-20230909163529142

参数二表示fds数组的长度

参数三表示poll函数的超时时间, 单位是毫秒(ms)

timeout > 0 : 表示在timeout时间内阻塞,否则非阻塞返回一次

timeout == 0:表示非阻塞等待

timeout < 0:表示阻塞等待

返回值:

返回值小于0, 表示出错;
返回值等于0, 表示poll函数等待超时;
返回值大于0, 表示poll由于监听的文件描述符就绪而返回

poll的优缺点

  1. 不同与select使用三个位图来表示三个fdset的方式, poll使用一个pollfd的指针实现
  2. pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比select更方便.
  3. poll并没有最大数量限制 (但是数量过大后性能也是会下降)

poll中监听的文件描述符数目增多时的缺点:

  1. 和select函数一样, poll返回后,需要轮询pollfd来获取就绪的描述符.
  2. 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
  3. 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降

poll示例

Util.hpp(所用到的函数方法)

#pragma once

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include <sys/socket.h>
#include <functional>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <cstring>
#include <vector>
#include <poll.h>
#include <sys/time.h>

using namespace std;

#define INITPORT 8000
#define DEFAULTFD -1

// 打印函数调试
void Print(const struct pollfd *fdv)
{
    cout << "fd list: ";
    for (int i = 0; i < 1024; i++)
    {
        if (fdv[i].fd != DEFAULTFD)
            cout << fdv[i].fd << " ";
    }
    cout << endl;
}

class Util
{
public:
    static void Recv(struct pollfd *fdv, int i)
    {
        cout << "recv in" << endl;
        // 读取
        // 读取失败就关闭sock并且修改集合组里的数据
        char buff[1024];
        ssize_t s = recv(fdv[i].fd, buff, sizeof(buff) - 1, 0);
        if (s > 0)
        {
            buff[s] = 0;
            cout << "client: " << buff << endl;
            LogMessage(NORMAL, "client: %s", buff);
        }
        else if (s == 0)
        {
            close(fdv[i].fd);
            fdv[i].fd = DEFAULTFD;
            fdv[i].events = 0;
            fdv[i].revents = 0;
            LogMessage(NORMAL, "client quit");
            return;
        }
        else
        {
            close(fdv[i].fd);
            fdv[i].fd = DEFAULTFD;
            fdv[i].events = 0;
            fdv[i].revents = 0;
            LogMessage(ERROR, "client quit: %s", strerror(errno));
            return;
        }

        // 写回数据
        // 这里不考虑写事件
        string response = buff;

        write(fdv[i].fd, response.c_str(), response.size());
        LogMessage(DEBUG, "Recver end");
    }

    // 将通信sock添加进集合组
    static void AddSock(struct pollfd *fdv, int listensock)
    {
        // listensock读事件就绪
        string clientip;
        uint16_t clientport;
        int sock = Util::GetSock(listensock, &clientip, &clientport);
        if (sock < 0)
            return;
        LogMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);
        int i = 0;
        for (; i < 1024; i++)
        {
            if (fdv[i].fd != DEFAULTFD)
                continue;
            else
                break;
        }
        if (i == 1024)
        {
            LogMessage(WARNING, "server if full, please wait");
            close(sock);
        }
        else
        {
            fdv[i].fd = sock;
            fdv[i].events = POLLIN;
            fdv[i].revents = 0;
        }

        Print(fdv);
        LogMessage(DEBUG, "Accepter out");
    }

    // 获取新连接创建通信sock
    static int GetSock(int listensock, string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int sock = accept(listensock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
            LogMessage(ERROR, "accept socket error, next");
        else
        {
            LogMessage(NORMAL, "accept socket %d success", sock);
            cout << "sock: " << sock << endl;
            *clientip = inet_ntoa(peer.sin_addr);
            *clientport = ntohs(peer.sin_port);
        }

        return sock;
    }

    // 设置监听套接字为监听状态
    static void setListen(int listensock)
    {
        if (listen(listensock, 5) < 0)
        {
            LogMessage(FATAL, "listen socket error!");
            exit(3);
        }
        LogMessage(NORMAL, "listen socket success");
    }

    // 绑定网络信息
    static void bindSock(int port, int listensock)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            LogMessage(FATAL, "bind socket error!");
            exit(2);
        }
        LogMessage(NORMAL, "bind sock success");
    }

    // 创建监听套接字
    static void createSock(int *listensock)
    {
        *listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            LogMessage(FATAL, "create socket error!");
            exit(1);
        }
        LogMessage(NORMAL, "create socket success");

        // 设置进程可以立即重启
        int opt = 1;
        setsockopt(*listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
    }

    // 设置非阻塞
    static void SetNonBlock(int fd)
    {
        int f = fcntl(fd, F_GETFL);
        if (f < 0)
        {
            cerr << "fcntl" << endl;
            return;
        }
        fcntl(fd, F_SETFL, f | O_NONBLOCK);
    }
};

Server.hpp

#pragma once

#include "Util.hpp"

class Server
{
public:
    Server(const uint16_t port = INITPORT)
        : _port(port), _listensock(-1)
    {
    }

    void HandlerEvent()
    {
        int i = 0;
        for (int i = 0; i < 1024; ++i)
        {
            // 过滤掉非法的fd
            if (fdv[i].fd == DEFAULTFD)
                continue;
            if(!(fdv[i].events & POLLIN))
                continue;

            if (fdv[i].fd == _listensock && (fdv[i].revents & POLLIN)) // 判断listensock在不在就绪的集合中
                Util::AddSock(fdv, _listensock);
            else if (fdv[i].revents & POLLIN)
                Util::Recv(fdv, i);
            else
            {
            }
        }
    }

    void Init()
    {
        // 创建监听套接字
        Util::createSock(&_listensock);

        // 绑定网络信息
        Util::bindSock(_port, _listensock);

        // 设置监听套接字为监听状态
        Util::setListen(_listensock);

        fdv = new struct pollfd[1024];
        for (int i = 0; i < 1024; ++i)
        {
            fdv[i].fd = DEFAULTFD;
            fdv[i].events = 0;
            fdv[i].revents = 0;
        }
        fdv[0].fd = _listensock;
        fdv[0].events = POLLIN;
    }

    void start()
    {
        int timeout = 1000;
        while (1)
        {         
            int n = poll(fdv, 1024, timeout);
            switch (n)
            {
            case 0:
                cout << "timeout...." << endl;
                LogMessage(NORMAL, "timeout....");
                break;
            case -1:
                printf("select error, code: %d, err string: %s", errno, strerror(errno));
                LogMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
                break;
            default:
                // 有事件就绪
                cout << "event readly" << endl;
                LogMessage(NORMAL, "event readly");
                // 处理事件
                HandlerEvent();
                break;
            }
        }
    }

    ~Server()
    {
        if (_listensock < 0)
            close(_listensock);
    }

private:
    int _listensock;
    uint16_t _port;
    struct pollfd *fdv;
};

Server.cc

#include "Server.hpp"
#include <memory>

// 输出命令错误函数
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_port\n\n";
}

int main(int argc, char *argv[])
{
    // 启动服务端不需要指定IP
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = atoi(argv[1]);
    unique_ptr<Server> sptr(new Server(port));

    sptr->Init();
    sptr->start();

    return 0;
}

log.hpp(日志)

#pragma once

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

using namespace std;

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *to_levelstr(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 nullptr;
    }
}

void LogMessage(int level, const char *format, ...)
{
#define NUM 1024
    char logpre[NUM];
    snprintf(logpre, sizeof(logpre), "[%s][%ld][%d]", to_levelstr(level), (long int)time(nullptr), getpid());

    char line[NUM];
    // 可变参数
    va_list arg;
    va_start(arg, format);

    vsnprintf(line, sizeof(line), format, arg);

    // 保存至文件
    FILE* log = fopen("log.txt", "a");
    FILE* err = fopen("log.error", "a");

    if(log && err)
    {
        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", logpre, line);

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

Epoll

是为处理大批量句柄而作了改进的poll

它几乎具备了select 和poll 的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法

epoll的相关系统调用

epoll 有3个相关的系统调用

epoll_create

#include <sys/epoll.h>

int epoll_create(int size);

创建一个epoll的句柄,size就是epoll的容量大小。返回epoll的文件描述符

注意用完之后, 必须调用close()关闭

epoll_ctl

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数

  1. 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
  2. 第一个参数是epoll_create()的返回值(epoll的句柄).
  3. 第二个参数表示动作,用三个宏来表示.
  4. 第三个参数是需要监听的fd.
  5. 第四个参数是告诉内核需要监听什么事

其中参数二的三个动作分别为:

  1. EPOLL_CTL_ADD :注册新的fd到epfd中;
  2. EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
  3. EPOLL_CTL_DEL :从epfd中删除一个fd;

参数四的结构为:

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;

events可以是以下几个宏的集合:

  1. EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  2. EPOLLOUT : 表示对应的文件描述符可以写;
  3. EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  4. EPOLLERR : 表示对应的文件描述符发生错误;
  5. EPOLLHUP : 表示对应的文件描述符被挂断;
  6. EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  7. EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里

epoll_wait

收集在epoll监控的事件中已经发送的事件

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
              int maxevents, int timeout);
  1. 参数events是分配好的epoll_event结构体数组.
  2. epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
  3. maxevents告诉内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
  4. 参数timeout是超时时间 (毫秒, 0会立即返回, -1是永久阻塞).
  5. 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败

epoll工作原理

当某一进程调用epoll_create方法时, Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关

  1. 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件.
  2. 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度).
  3. 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.
  4. 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
  5. 在epoll中,对于每一个事件,都会建立一个epitem结构体
  6. 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.
  7. 如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1)

总的来说,epoll的使用过程就是三部曲

  1. 调用epoll_create创建一个epoll句柄;
  2. 调用epoll_ctl, 将要监控的文件描述符进行注册;
  3. 调用epoll_wait, 等待文件描述符就绪

epoll的优点

  1. 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  2. 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
  3. 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
  4. 没有数量限制: 文件描述符数目无上限

epoll工作方式

epoll有2种工作方式-水平触发(LT)和边缘触发(ET)

对比LT和ET

LT:就好比快递员给你送快递,他打电话叫你下楼拿,可是你告诉快递员还没有空,于是快递员就一直在打电话给你,直到你下楼将快递取回。

ET:就好比快递员给你送快递,他打电话叫你下楼拿,可是你告诉快递员还没有空,这时这位快递员就不等你也不会再打电话给你,把快递放在楼下就走了,至于你有没有下楼取快递他不关心了

这两种模式的区别就在于其通知机制不一样,在LT模式下只要还有数据没有取走就会一直通知而对于ET从始至终只会通知一次,除非数据从无到有,从有到多发生变化的时候才会再次通知

LT是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把所有的数据都处理完,相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的。另一方面, ET 的代码复杂程度更高了

LT的文件描述符可以是阻塞的也可以是非阻塞的,但是ET模式下必须为非阻塞的

epoll服务器(LT模式)示例

Util.hpp(需要调用的函数)

#pragma once

#include "log.hpp"
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>

using namespace std;

#define INITPORT 8000
#define DEFAULTFD -1
#define EPOLLSIZE 128

class Util
{
public:
    // 获取新连接创建通信sock
    static int GetSock(int listensock, string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int sock = accept(listensock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
            LogMessage(ERROR, "accept socket error, next");
        else
        {
            LogMessage(NORMAL, "accept socket %d success", sock);
            cout << "sock: " << sock << endl;
            *clientip = inet_ntoa(peer.sin_addr);
            *clientport = ntohs(peer.sin_port);
        }

        return sock;
    }

    // 创建epoll模型
    static int CreateEpollFD()
    {
        int epfd = epoll_create(EPOLLSIZE);
        if(epfd < 0)
        {
            LogMessage(FATAL, "Create epoll_fd error!");
            exit(4);
        }
        LogMessage(NORMAL, "Create epoll_fd success!");

        return epfd;
    }

    // 设置监听套接字为监听状态
    static void SetListen(int sock)
    {
        int n = listen(sock, 5);
        if(n < 0)
        {
            LogMessage(FATAL, "Set listen error!");
            exit(3);
        }
        LogMessage(NORMAL, "Set listen success!");
    }

    // 绑定网络信息
    static void BindSock(int sock, uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        int n = bind(sock, (struct sockaddr *)&local, sizeof(local));
        if(n < 0)
        {
            LogMessage(FATAL, "Bind error!");
            exit(2);
        }
        LogMessage(NORMAL, "Bind success!");
    }

    // 创建监听套接字
    static int CreateSock()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0)
        {
            LogMessage(FATAL, "Create sock error!");
            exit(1);
        }
        LogMessage(NORMAL, "Create sock success!");

        return sock;
    }
};

Server.hpp

#pragma once

#include "Util.hpp"

class Server
{
public:
    Server(const uint16_t port = INITPORT) : _port(port), _listensock(-1), _epfd(-1), _revs(nullptr), _num(64)
    {
    }

    void Init()
    {
        // 创建监听套接字
        _listensock = Util::CreateSock();
        // 绑定网络信息
        Util::BindSock(_listensock, _port);
        // 设置监听套接字为监听状态
        Util::SetListen(_listensock);

        // 创建epoll模型
        _epfd = Util::CreateEpollFD();
        // 添加listensock到epoll中
        struct epoll_event ev;
        ev.events = EPOLLIN;
        // 记录fd,当事件就绪时可以知道是哪一个就绪
        ev.data.fd = _listensock;
        epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev);

        // 申请就绪事件的空间
        _revs = new struct epoll_event[_num];
    }

    void start()
    {
        int timeout = 1000;
        while (1)
        {
            int n = epoll_wait(_epfd, _revs, _num, timeout);
            switch (n)
            {
            case 0:
                LogMessage(NORMAL, "....timeout....");
                break;
            case -1:
                LogMessage(WARNING, "epoll_wait error: %s", strerror(errno));
                break;
            default:
                HandlerEvent(n);
                break;
            }
        }
    }

    // 获取事件
    void HandlerEvent(int n)
    {
        LogMessage(NORMAL, "HandlerEvent in");
        for (int i = 0; i < n; ++i)
        {
            // 保留事件就绪的类型
            uint32_t events = _revs[i].events;
            // 获取当前就绪事件的fd
            int sock = _revs[i].data.fd;

            if (sock == _listensock && (events & EPOLLIN))
            {
                string Clienip;
                uint16_t ClientPort;
                // 获取通信的套接字
                int fd = Util::GetSock(_listensock, &Clienip, &ClientPort);
                if (fd < 0)
                    continue;

                // 将sock添加进epoll
                struct epoll_event ev;
                ev.events = EPOLLIN;
                ev.data.fd = fd;
                epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
            }
            else if (events & EPOLLIN)
            {
                // 读取数据
                char buff[1024];
                int s = recv(sock, buff, sizeof(buff), 0);
                if (s > 0)
                {
                    buff[s] = 0;
                    cout << "client: " << buff << endl;
                    LogMessage(NORMAL, "client: %s", buff);
                }
                else if (s == 0)
                {
                    // 将sock从epoll中移除并关闭sock
                    epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
                    close(sock);
                    LogMessage(NORMAL, "client quit!");
                }
                else
                {
                    // 将sock从epoll中移除并关闭sock
                    epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
                    close(sock);
                    LogMessage(ERROR, "Recv error: %s", strerror(errno));
                }

                string response = buff;
                write(sock, response.c_str(), response.size());
            }
            else
            {
            }
        }
        LogMessage(NORMAL, "HandlerEvent end");
    }

    ~Server()
    {
        if (_listensock != -1)
            close(_listensock);
        if (_epfd != -1)
            close(_epfd);
    }

private:
    int _listensock;
    uint16_t _port;
    int _epfd;                 // epoll
    struct epoll_event *_revs; // 保存就绪事件
    int _num;                  // 就绪事件的空间大小
};

Server.cc

#include "Server.hpp"
#include <memory>

// 输出命令错误函数
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_port\n\n";
}

int main(int argc, char *argv[])
{
    // 启动服务端不需要指定IP
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = atoi(argv[1]);
    unique_ptr<Server> sptr(new Server(port));

    sptr->Init();
    sptr->start();

    return 0;
}

log.hpp

#pragma once

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

using namespace std;

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *to_levelstr(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 nullptr;
    }
}

void LogMessage(int level, const char *format, ...)
{
#define NUM 1024
    char logpre[NUM];
    snprintf(logpre, sizeof(logpre), "[%s][%ld][%d]", to_levelstr(level), (long int)time(nullptr), getpid());

    char line[NUM];
    // 可变参数
    va_list arg;
    va_start(arg, format);

    vsnprintf(line, sizeof(line), format, arg);

    // 保存至文件
    FILE* log = fopen("log.txt", "a");
    FILE* err = fopen("log.error", "a");

    if(log && err)
    {
        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", logpre, line);

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

演示效果

image-20230909215944131

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DeepinV20/Ubuntu安装postgresql方法

首先&#xff0c;建议看一下官方的安装文档PostgreSQL: Linux downloads (Ubuntu) PostgreSQL Apt Repository 简单的说&#xff0c;就是Ubuntu下的Apt仓库&#xff0c;可以用来安装任何支持版本的PgSQL。 If the version included in your version of Ubuntu is not the one…

一笑的大型连续剧之第二集

开场白 各位小伙伴们大家晚上好&#xff0c;今天来和大家一起更新一下我的开发之旅的第二集。上周时间也已经匆匆过去了。今天也是周六晚上了&#xff0c;这个周末很充实但是又很空虚。 本周小结 本周完成了我开发旅途中的第一个模块&#xff0c;关于绩效面谈的一个模块的一…

树莓派入门

目录 前言系统烧录使用官方烧录工具选择操作系统选择存储卡配置 Win32DiskImager 有屏幕树莓派开机树莓派关机无屏幕树莓派开机获取树莓派IP地址通过路由器获取共享网络方式获取给树莓派配置静态IP地址查找默认网关分盘给树莓派的IP地址修改树莓派DHCP配置文件 ssh登录 让树莓派…

排序(408)

一、插入排序&#xff08;直接、折半、希尔&#xff09; 【2009统考】若数据元素序列{11,12,13,7,8,9,23,4,5}是采用下列排序方法之一得到的第二趟排序后的结果&#xff0c;则该排序算法只能是&#xff08;B&#xff09; A、冒泡排序 B、插入排序 C、选择排序 …

freemarker模板引擎详解以及使用方法

哈喽&#xff01;大家好&#xff0c;我是旷世奇才李先生 文章持续更新&#xff0c;可以微信搜索【小奇JAVA面试】第一时间阅读&#xff0c;回复【资料】更有我为大家准备的福利哟&#xff0c;回复【项目】获取我为大家准备的项目 文章目录 一、freemarker 介绍1、简介 二、free…

Java 基于 SpringBoot 的酒店管理系统,附源码和数据库

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 一、前言介绍二、系统结构三、系统详细实现3.1用户信息管理3.2会员信息管理3.3客房信息管理3.4收藏…

浅析linux异步io框架 io_uring

前言 Linux内核5.1支持了新的异步IO框架iouring&#xff0c;由Block IO大神也即Fio作者Jens Axboe开发&#xff0c;意在提供一套公用的网络和磁盘异步IO&#xff0c;不过io_uring目前在磁盘方面要比网络方面更加成熟。 目录 背景简介 io_uring 系统API liburing 高级特性…

SpringBoot实例类-@Data

1.配置pom.xml 说明&#xff1a;添加lombok依赖 <!-- lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency> 2.刷新maven 说明&#xff1a;一般修改xml文件就需要刷…

数据库相关基础知识

第一章 概念 1、数据&#xff1a;描述事物的符号记录称为数据。特点&#xff1a;数据和关于数据的解释不可分。 2、数据库&#xff1a;长期存储在计算机内、有组织、可共享的大量的数据的集合。数据库中的数据按照一定的数据模型组织、描述和存储&#xff0c;具有较小的冗余度、…

Linux —— 信号阻塞

目录 一&#xff0c;信号内核表示 sigset_t sigprocmask sigpending 二&#xff0c;捕捉信号 sigaction 三&#xff0c;可重入函数 四&#xff0c;volatile 五&#xff0c;SIGCHLD 信号常见概念 实际执行信号的处理动作&#xff0c;称为信号递达Delivery&#xff1b;信…

广东智科与涂鸦智能达成合作,引领热泵市场智能转型新风向

全球能源危机正推动热泵市场的增长&#xff0c;据国际能源署报道&#xff0c;2022年全球热泵的销售额增长达11%&#xff0c;欧洲的销售额增长更是达到了40%。中国作为热泵市场的最大出口国&#xff0c;全球热泵市场需求的激增对于中国企业而言无疑是一剂“振奋剂”。 广东智科电…

QT/QTCreator开发/使用技巧

调试模式完整的展示过长的字符串 如图&#xff0c;当字符串过长时在调试模式下&#xff0c;无法非常清晰的看到全部的字符串&#xff0c;此时可以通过 右键菜单→ change value display format→spearate Window。此时字符串将单独显示在一个独立的窗口里。如果你想回到原状勾选…

关于“找不到mfc140u.dll,无法继续执行代码”问题的分析处理方法

我想和大家分享一个在编程过程中经常会遇到的问题——找不到mfc140u.dll,无法继续执行代码。找不到 mfc140u.dll&#xff0c;这个问题可能会让我们感到困扰。mfc140u.dll 是 Microsoft Foundation Classes&#xff08;MFC&#xff09;库的一部分&#xff0c;它是一个 Windows 系…

MySQL——读写分离

简介 读写分离&#xff0c;基本的原理是让主数据库处理事务性增、改、删操作&#xff08;INSERT、UPDATE、DELETE&#xff09;&#xff0c;而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。一般来说都是通过 主从复制&#xff…

领域驱动设计:领域事件

文章目录 领域事件识别领域事件领域事件相关案例领域事件总体架构 领域事件 领域事件是领域模型中非常重要的一部分&#xff0c;用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作&#xff0c;在实现业务解耦的同时&#xff0c;还有助于形成完整的业务闭环。 举例…

脚本:python实现动态爱心

文章目录 效果代码Reference python实现dynamic heart 效果 代码 import turtle as tu import random as ratu.setup(0.5, 0.5) # 设置画板大小&#xff08;小数表示比例&#xff0c;整数表示大小&#xff09; tu.screensize(1.0, 1.0) # 设置屏幕大小 tu.bgcolor(black) #…

Linux安装logstash

相关链接 项⽬主⻚&#xff1a; https://www.elastic.co/cn/downloads/logstash 下载地址&#xff1a; wget https://artifacts.elastic.co/downloads/logstash/logstash-7.5.1.tar.gz 官网下载可能比较慢&#xff0c;下面提供下载地址 百度云链接&#xff1a;https://pan.…

C# wpf 实现桌面放大镜

文章目录 前言一、如何实现&#xff1f;1、制作无边框窗口2、Viewbox放大3、截屏显示&#xff08;1&#xff09;、截屏&#xff08;2&#xff09;、转BitmapSource&#xff08;3&#xff09;、显示 4、定时截屏 二、完整代码三、效果预览总结 前言 做桌面截屏功能时需要放大镜…

【关于Java:认识异常】

文章目录 一、1. 异常概念与体系结构1.1 异常的概念1.2 常见的异常1.算数异常2.数组越界异常3.空指针异常 1.3 异常的体系结构1.4 异常的分类1. 编译时异常2. 运行时异常&#xff08;RuntimeException&#xff09; 二、 异常的处理方式2.1 防御式编程2.2 EAFP:&#xff08;异常…

API 架构学习

MQTT架构 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布/订阅&#xff08;publish/subscribe&#xff09;模式的“轻量级”通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff0c;由IBM在…