IO多路转接 ——— select、poll、epoll

news2024/11/18 1:30:32

select初识
select是系统提供的一个多路转接接口。

select系统调用可以让我们的程序同时监视多个文件描述符的上的事件是否就绪。
select的核心工作就是等,当监视的多个文件描述符中有一个或多个事件就绪时,select才会成功返回并将对应文件描述符的就绪事件告知调用者。
 

select基本工作流程
如果我们要实现一个简单的select服务器,该服务器要做的就是读取客户端发来的数据并进行打印,那么这个select服务器的工作流程应该是这样的:

先初始化服务器,完成套接字的创建、绑定和监听。
定义一个fd_array数组用于保存监听套接字和已经与客户端建立连接的套接字,刚开始时就将监听套接字添加到fd_array数组当中。
然后服务器开始循环调用select函数,检测读事件是否就绪,如果就绪则执行对应的操作。
每次调用select函数之前,都需要定义一个读文件描述符集readfds,并将fd_array当中的文件描述符依次设置进readfds当中,表示让select帮我们监视这些文件描述符的读事件是否就绪。
当select检测到数据就绪时会将读事件就绪的文件描述符设置进readfds当中,此时我们就能够得知哪些文件描述符的读事件就绪了,并对这些文件描述符进行对应的操作。
如果读事件就绪的是监听套接字,则调用accept函数从底层全连接队列获取已经建立好的连接,并将该连接对应的套接字添加到fd_array数组当中。
如果读事件就绪的是与客户端建立连接的套接字,则调用read函数读取客户端发来的数据并进行打印输出。
当然,服务器与客户端建立连接的套接字读事件就绪,也可能是因为客户端将连接关闭了,此时服务器应该调用close关闭该套接字,并将该套接字从fd_array数组当中清除,因为下一次不需要再监视该文件描述符的读事件了。
 

log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./selectServer.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
    // va_list ap;
    // va_start(ap, format);
    // while()
    // int x = va_arg(ap, int);
    // va_end(ap); //ap=nullptr
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    // struct tm *localtime = localtime(&timestamp);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // vprintf(format, args);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    // FILE *fp = fopen(LOGFILE, "a");
    printf("%s%s\n", stdBuffer, logBuffer);
    // fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    // fclose(fp);
}

selectserver.hpp

#include <iostream>
#include <cstring>
#include <sys/select.h>
#include "log.hpp"
#include "sock.hpp"
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/time.h>
#include <algorithm>
#define NUM 1024
#define FD_NONE -1
using namespace std;
class SelectServer
{
public:
  //端口类型设置为16位是因为TCP报文中端口号为16位
  SelectServer(const uint16_t &port = 8080):_port(port)
  {
    _listensock = Sock::Socket();
    Sock::Bind(_listensock,_port);
    Sock::Listen(_listensock);
    logMessage(DEBUG,"%s","create base socket success");
    for(int i = 0;i < NUM;i++) _fd_array[i] = FD_NONE;
    _fd_array[0] = _listensock;//规定第一个为监听套接字
    std::cout<<"初始化完成...."<<std::endl;
  } 
  void start()
  {
    while(1)
    {
      struct timeval timeout = {0,0};
      fd_set rfds;
      FD_ZERO(&rfds);
      int maxfd = _listensock;
      for(int i = 0;i < NUM;i++)
      {
         if(_fd_array[i] == FD_NONE) continue;
         FD_SET(_fd_array[i],&rfds);
         maxfd = max(maxfd,_fd_array[i]);
      }
     int n = select(maxfd + 1,&rfds,nullptr, nullptr,&timeout);
     DebugPrint();
     switch (n)
     {
     case 0:
        sleep(1);
        logMessage(DEBUG,"%s","time out...");
        break;
     case -1:
        logMessage(DEBUG,"%s","select error");
        break;
     default:
        //成功
        logMessage(DEBUG,"%s","get a new link event........");
        //成功了的话,如果不去读取的话会一直提醒读取,也就是说链接好的
        //链接会被放在就绪队列中,也就是看到链接在排队,操作系统会一直提醒有连接成功
        //当你要取的时候会取队列的头部连接去执行
        //因为我们的select是检查_listensock有没有获取的连接已经到达Tcp层
        //如果到达的话,就说明可以读走这个链接了,所以我们检查的是IO
        //也就是读到连接的操作,而不是建立连接的操作
        HandlerEvent(rfds);
        sleep(1);
        break;
     }
    }
  }

private:
  uint16_t _port;
  int _listensock;
  int _fd_array[NUM];
  void HandlerEvent(const fd_set &rfds)
  {
    for(int i = 0;i < NUM;i++)
    {
      //1.去掉不合法的fd,也就是去掉没有建立连接的fd,也就是去掉数组里为FD_NONE
      if(_fd_array[i] == FD_NONE) continue;
      //2.合法的就一定就绪了?,不一定,所以需要FD_ISSET判断是否已经就绪
      if(FD_ISSET(_fd_array[i],&rfds))
      {
         //1.如果是监听套接字就绪了,那就是accept
         //2.如果不是的话,那就处理该链接,进行读取函数
         if(_fd_array[i] == _listensock) Accepter();
         else Recver(i);
      }
    }
  }

  void Accepter()
  {
    string clientip;
    uint16_t clientport;
    int sock = Sock::Accept(_listensock,&clientip,&clientport);
    if(sock < 0)
    {
      logMessage(WARNING,"%s %s:%d","accept error",strerror(errno),errno);
      return;
    }
    logMessage(DEBUG,"get a new link success");
    int pos = 0;
    for(;pos < NUM;pos++)
    {
     if(_fd_array[pos] == FD_NONE) break;
    }
     if(pos == NUM)
     {
        logMessage(WARNING, "%s:%d", "select server already full,close: %d", sock);
        close(sock);
     }else
     {
        _fd_array[pos] = sock;
     }
  }
  void Recver(int pos)
  {
    logMessage(DEBUG,"message in,get IO event:%d",_fd_array[pos]);
    // 暂时先不做封装, 此时select已经帮我们进行了事件检测,fd上的数据一定是就绪的,即 本次 不会被阻塞
    // 这样读取有bug吗?有的,你怎么保证以读到了一个完整包文呢?
    char buffer[1024];
    int n = recv(_fd_array[pos],buffer,sizeof(buffer) - 1,0);
    //不会堵塞,
    if(n > 0)
    {
      buffer[n] = 0;
      logMessage(DEBUG,"client[%d]# %s",_fd_array[pos],buffer);
    }
    else if(n == 0)
    {
       logMessage(DEBUG, "client[%d] quit, me too...", _fd_array[pos]);
      //对端关闭那么我也关闭
      close(_fd_array[pos]);
      _fd_array[pos] = FD_NONE;
    }
    else
    {
      logMessage(WARNING, "%d sock recv error, %d : %s", _fd_array[pos], errno, strerror(errno));
      close(_fd_array[pos]);
      _fd_array[pos] = FD_NONE;
    }
  }
   void DebugPrint()
    {
        cout << "_fd_array[]: ";
        for(int i = 0; i < NUM; i++)
        {
            if(_fd_array[i] == FD_NONE) continue;
            cout << _fd_array[i] << " ";
        }
        cout << endl;
    }
};

 sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

//全加静态成员让他变成一个方法
class Sock
{
private:
    // listen的第二个参数,意义:底层全连接队列的长度 = listen的第二个参数+1
    const static int gbacklog = 10;
public:
    Sock() {}
    static int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            exit(2);
        }
        int opt = 1;
        setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        return listensock;
    }
    static void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            exit(3);
        }
    }
    static void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            exit(4);
        }

    }
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    static int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }
    static bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

 main.cc

 

#include "selectserver.hpp"
using namespace std;
int main()
{
   SelectServer select;
   cout<<"runring......."<<endl;
   select.start();
}

I/O多路转接之poll

poll初识

poll也是系统提供的一个多路转接接口。

  • poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪,和select的定位是一样的,适用场景也是一样的。

poll的工作流程和select是基本类似的,这里我们也实现一个简单poll服务器,该服务器也只是读取客户端发来的数据并进行打印。

log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./selectServer.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
    // va_list ap;
    // va_start(ap, format);
    // while()
    // int x = va_arg(ap, int);
    // va_end(ap); //ap=nullptr
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    // struct tm *localtime = localtime(&timestamp);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // vprintf(format, args);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    // FILE *fp = fopen(LOGFILE, "a");
    printf("%s%s\n", stdBuffer, logBuffer);
    // fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    // fclose(fp);
}

pollserver.hpp

#include <iostream>
#include <cstring>
#include <sys/select.h>
#include "log.hpp"
#include "sock.hpp"
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/time.h>
#include <algorithm>
#include <poll.h>
#define NUM 1024
#define FD_NONE -1
using namespace std;
class PollServer
{
public:
  static const int nfds = 100;
public:
 // struct pollfd {
 //     int   fd;         // 文件描述符
 //     short events;     // 需要关注的事件
 //     short revents;    // 实际发生的事件
 // };
//  fd:待轮询的文件描述符。
// events:关注的事件,可以是以下值的组合:
// POLLIN:可读事件(数据可读取)
// POLLOUT:可写事件(数据可写入)
// POLLERR:错误事件(发生错误)
// POLLHUP:挂起事件(连接断开)
// POLLNVAL:无效事件(文件描述符未打开)
// revents:实际发生的事件,在调用 poll 后由系统设置。
// poll 函数将在等待期间阻塞,并返回发生事件的数量,如果超时则返回 0,如果出错则返回 -1。

// 您可以使用 poll 函数来同时监视多个文件描述符,并根据发生的事件采取相应的操作。
  //端口类型设置为16位是因为TCP报文中端口号为16位
  PollServer(const uint16_t &port = 8080):_port(port),_nfds(nfds)
  {
    _listensock = Sock::Socket();
    Sock::Bind(_listensock,_port);
    Sock::Listen(_listensock);
    logMessage(DEBUG,"%s","create base socket success");
    _fds = new struct pollfd[_nfds];
    for(int i = 0;i < NUM;i++) 
    {
      _fds[i].fd = FD_NONE;
      _fds[i].events = _fds[i].revents = 0;
    } 
    _fds[0].fd= _listensock;//规定第一个为监听套接字,需要关注的套接字是什么,这是对象方面
    _fds[0].events = POLLIN;//需要套接字中关注的事件是读事件,这个才是关系的动作
    _timeout = 1000;
    std::cout<<"初始化完成...."<<std::endl;
  } 
  void start()
  {
    while(1)
    {
     int n = poll(_fds,_nfds,_timeout);
     DebugPrint();
     switch (n)
     {
     case 0:
        sleep(1);
        logMessage(DEBUG,"%s","time out...");
        break;
     case -1:
        logMessage(DEBUG,"%s","select error");
        break;
     default:
        //成功
        logMessage(DEBUG,"%s","get a new link event........");
        //成功了的话,如果不去读取的话会一直提醒读取,也就是说链接好的
        //链接会被放在就绪队列中,也就是看到链接在排队,操作系统会一直提醒有连接成功
        //当你要取的时候会取队列的头部连接去执行
        //因为我们的select是检查_listensock有没有获取的连接已经到达Tcp层
        //如果到达的话,就说明可以读走这个链接了,所以我们检查的是IO
        //也就是读到连接的操作,而不是建立连接的操作
        HandlerEvent();
        sleep(1);
        break;
     }
    }
  }
  ~PollServer()
  {
    if(_listensock >= 0) close(_listensock);
    if(!_fds) delete [] _fds;
  }
private:
  uint16_t _port;
  int _listensock;
  int _timeout;
  struct pollfd* _fds;
  int _nfds;
  void HandlerEvent()
  {
    for(int i = 0;i < _nfds;i++)
    {
      //1.去掉不合法的fd,也就是去掉没有建立连接的fd,也就是去掉数组里为FD_NONE
      if(_fds[i].fd == FD_NONE) continue;
      //2.合法的就一定就绪了?,不一定,所以需要FD_ISSET判断是否已经就绪
      if(_fds[i].revents & POLLIN)//判断读事件是否就绪,就是就是一个数字
      {
         //1.如果是监听套接字就绪了,那就是accept
         //2.如果不是的话,那就处理该链接,进行读取函数
         if(_fds[i].fd == _listensock) Accepter();
         else Recver(i);
      }
    }
  }

  void Accepter()
  {
    string clientip;
    uint16_t clientport;
    int sock = Sock::Accept(_listensock,&clientip,&clientport);
    if(sock < 0)
    {
      logMessage(WARNING,"%s %s:%d","accept error",strerror(errno),errno);
      return;
    }
    logMessage(DEBUG,"get a new link success");
    int pos = 0;
    for(;pos < NUM;pos++)
    {
     if(_fds[pos].fd == FD_NONE) break;
    }
     if(pos == NUM)
     {
        logMessage(WARNING, "%s:%d", "select server already full,close: %d", sock);
        close(sock);
     }else
     {
        _fds[pos].fd = sock;
        _fds[pos].events = POLLIN;
     }
  }
  void Recver(int pos)
  {
    logMessage(DEBUG,"message in,get IO event:%d",_fds[pos].fd);
    // 暂时先不做封装, 此时select已经帮我们进行了事件检测,fd上的数据一定是就绪的,即 本次 不会被阻塞
    // 这样读取有bug吗?有的,你怎么保证以读到了一个完整包文呢?
    char buffer[1024];
    int n = recv(_fds[pos].fd,buffer,sizeof(buffer) - 1,0);
    //不会堵塞,
    if(n > 0)
    {
      buffer[n] = 0;
      logMessage(DEBUG,"client[%d]# %s",_fds[pos].fd,buffer);
    }
    else if(n == 0)
    {
       logMessage(DEBUG, "client[%d] quit, me too...", _fds[pos].fd);
      //对端关闭那么我也关闭
      close(_fds[pos].fd);
      _fds[pos].fd = FD_NONE;
      _fds[pos].events = 0;
    }
    else
    {
      logMessage(WARNING, "%d sock recv error, %d : %s", _fds[pos].fd, errno, strerror(errno));
      close(_fds[pos].fd);
      _fds[pos].fd = FD_NONE;
      _fds[pos].fd = 0;
    }
  }
   void DebugPrint()
    {
        cout << "_fd_array[]: ";
        for(int i = 0; i < NUM; i++)
        {
            if(_fds[i].fd  == FD_NONE) continue;
            cout << _fds[i].fd << " ";
        }
        cout << endl;
    }
};

sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

class Sock
{
private:
    // listen的第二个参数,意义:底层全连接队列的长度 = listen的第二个参数+1
    const static int gbacklog = 10;
public:
    Sock() {}
    static int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            exit(2);
        }
        int opt = 1;
        setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        return listensock;
    }
    static void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            exit(3);
        }
    }
    static void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {exit(4);}
    }
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    static int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }
    static bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

 main.cc

#include "pollserver.hpp"
using namespace std;
int main()
{
   PollServer pollserver;
   cout<<"runring......."<<endl;
   pollserver.start();
}

/O多路转接之epoll
epoll初识
epoll也是系统提供的一个多路转接接口。

epoll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪,与select和poll的定位是一样的,适用场景也相同。
epoll在命名上比poll多了一个e,这个e可以理解成是extend,epoll就是为了同时处理大量文件描述符而改进的poll。
epoll在2.5.44内核中被引进,它几乎具备了select和poll的所有优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll的相关系统调用
epoll有三个相关的系统调用,分别是epoll_create、epoll_ctl和epoll_wait。

epoll工作原理


 

 epoll.hpp

#pragma once
#include <iostream>
#include <sys/epoll.h>
#include <unistd.h>
using namespace std;
class Epoll
{
public:
   static const int gsize = 256;
public:
   static int CreateEpoll()
   {
       int epfd = epoll_create(gsize);
       if(epfd > 0) return epfd;
       exit(5);
   }

   static bool CtrlEpoll(int epfd,int oper,int sock,uint32_t events)
   {
      struct epoll_event ev;
      ev.events = events;
      ev.data.fd = sock;
      int n = epoll_ctl(epfd,oper,sock,&ev);
      return n == 0;
   }
   static int WaitEpoll(int epfd,struct epoll_event* revs,int num,int timeout)
   {
            // 细节1:如果底层就绪的sock非常多,revs承装不下,怎么办??不影响!一次拿不完,就下一次再拿
        // 细节2:关于epoll_wait的返回值问题:有几个fd上的事件就绪,就返回几,epoll返回的时候,会将所有
        //       就绪的event按照顺序放入到revs数组中!一共有返回值个!
      return epoll_wait(epfd,revs,num,timeout);
   }
};

 log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./selectServer.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
    // va_list ap;
    // va_start(ap, format);
    // while()
    // int x = va_arg(ap, int);
    // va_end(ap); //ap=nullptr
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    // struct tm *localtime = localtime(&timestamp);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // vprintf(format, args);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    // FILE *fp = fopen(LOGFILE, "a");
    printf("%s%s\n", stdBuffer, logBuffer);
    // fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    // fclose(fp);
}

sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

//全加静态成员让他变成一个方法
class Sock
{
private:
    // listen的第二个参数,意义:底层全连接队列的长度 = listen的第二个参数+1
    const static int gbacklog = 10;
public:
    Sock() {}
    static int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            exit(2);
        }
        int opt = 1;
        setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        return listensock;
    }
    static void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            exit(3);
        }
    }
    static void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            exit(4);
        }

    }
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    static int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }
    static bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

epollserver.hpp

#include "sock.hpp"
#include "log.hpp"
#include <memory>
#include <sys/types.h>
#include <cstring>
#include <functional>
#include <string>
#include "epoll.hpp"
using namespace std;
const static int default_port = 8080;
const static int gnum = 64;
using func_t = function<void(string)>;
class EpollServer
{
public:
     EpollServer(func_t HandlerRequese,const int &port = default_port)
     :_port(port),_HandlerRequest(HandlerRequese),_revs_num(gnum)
     {
        // 0. 申请对应的空间
       _revs = new struct epoll_event[_revs_num];
         // 1. 创建listensock
        _listensock = Sock::Socket();
        Sock::Bind(_listensock,_port);
        Sock::Listen(_listensock);
       //2.创建epoll模型
       _epfd = Epoll::CreateEpoll();
       logMessage(DEBUG,"init success,listensock: %d,epfd: %d",_listensock,_epfd);
       //3.将listensock加入到epoll模型中,以关系读的事务加入
       if(!Epoll::CtrlEpoll(_epfd,EPOLL_CTL_ADD,_listensock,EPOLLIN)) exit(6);//
       //EPOLLIN是读事务
      logMessage(DEBUG, "add listensock to epoll success."); // 3, 4
     }
     void Accepter(int listensock)
     {
       string clientip;
       uint16_t clientport;
       int sock = Sock::Accept(listensock,&clientip,&clientport);
       if(sock < 0) 
       {
          logMessage(WARNING, "accept error!");
                return;
       }
         // 能不能直接读取?不能,因为你并不清楚,底层是否有数据!
            // 将新的sock,添加给epoll
        if(!Epoll::CtrlEpoll(_epfd,EPOLL_CTL_ADD,sock,EPOLLIN)) exit(6);
       logMessage(DEBUG, "add new sock : %d to epoll success", sock);   
     }

     void Recver(int sock)
     {
       char buffer[10240];
       ssize_t n = recv(sock,buffer,sizeof(buffer) - 1,0);
       if(n > 0)
       {
        //假设这里就是读到了一个完整的报文 // 如何保证??
        buffer[n] = 0;
        _HandlerRequest(buffer); // 2. 处理数据
       }
       else if(n == 0)
       {
          // 1. 先在epoll中去掉对sock的关
         bool res = Epoll::CtrlEpoll(_epfd,EPOLL_CTL_DEL,sock,0);
         assert(res);
         (void)res;
         //2.在close文件
         close(sock);
         logMessage(NORMAL,"client %d quit,me too...",sock);
       }
       else
       {
        //1.先在epoll中去掉对sock的关心
        bool res = Epoll::CtrlEpoll(_epfd,EPOLL_CTL_DEL,sock,0);
        assert(res);
         (void)res;
         close(sock);
         logMessage(NORMAL, "client recv %d error, close error sock", sock);
       }
     }
     void HandlerEvents(int n)
     {
      assert(n > 0);
      for(int i = 0;i < n;i++)
      {
        uint32_t revents = _revs[i].events;
        int sock = _revs[i].data.fd;//看看是哪个fd就绪了
        if(revents & EPOLLIN)
        {
          if(sock == _listensock) Accepter(_listensock);
          else Recver(sock);
        }
        if(revents & EPOLLOUT)
        {
            //TODO
        }
      }

     }
     void looponce(int timeout)
     {
       int n = Epoll::WaitEpoll(_epfd,_revs,_revs_num,timeout);
        //if(n == _revs_num) //扩容
       switch (n)//返回值n,代表有一个关心的事务就绪
       {
         case 0:
              logMessage(DEBUG, "timeout..."); // 3, 4
              break;
         case -1:
              logMessage(WARNING, "epoll wait error: %s", strerror(errno));
              break;
         default:
         //等待成功,有至少一个关系的事务已经就绪
         //去执行
         logMessage(DEBUG, "get a event");
         HandlerEvents(n);
         break;
       }
     }
     void start()
     {
        int timeout = 1000;
        while(1)
        {
          looponce(timeout);
        }
     }
     ~EpollServer()
     {
        if (_listensock >= 0)
                close(_listensock);
            if (_epfd >= 0)
                close(_epfd);
            if (_revs)
                delete[] _revs;
     }
private:
     int _listensock;
     uint16_t _port;
     int _epfd;
     struct epoll_event* _revs;
     int _revs_num;
     func_t _HandlerRequest;
};

main.cc

#include "epollserver.hpp"
#include <memory>
#include <iostream>
#include <string>
using namespace std;
void change(string str)
{
    cout<<str<<endl;
}
int main()
{
   EpollServer epollserver(change);
   cout<<"runring......."<<endl;
   epollserver.start();
}

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

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

相关文章

kingbase(人大金仓)数据库的常用知识点与简单巡检

查看服务是否已设为开机自启 systemctl list-dependencies |grep kingbasehttps://blog.csdn.net/gyqailxj/article/details/127290687

Dynamic CRM开发 - 实体字段(二)字段安全性

在上一篇文章 Dynamic CRM开发 - 实体字段(一)中提到了创建实体字段时,有个“字段安全性”属性,主要用于字段的权限控制,本篇文章专门对此做详细说明。 需求:产品的折扣价格,只对有权限的用户可见。 首先创建一个“折扣价”字段,勾选“字段安全性”属性,如下图: 启…

网络安全—黑客技术(学习笔记)

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

基于QCC_BES 平台的LMS自适应滤波算法实现

+我V hezkz17进数字音频系统研究开发交流答疑群(课题组) LMS算法是最小均方(Least Mean Square)算法的缩写。它是一种自适应滤波算法,常用于信号处理、系统辨识和自适应滤波等领域。 LMS算法的目标是通过对输入信号和期望输出信号之间的误差进行最小化,来调整滤波器的权重…

协议的分层结构

1.1TCP/IP 协议 为了使各种不同的计算机之间可以互联&#xff0c;ARPANet指定了一套计算机通信协议&#xff0c;即TCP/IP 协议(族). 注意TCP /IP 协议族指的不只是这两个协议 而是很多协议&#xff0c; 只要联网的都使用TCP/IP协议族 为了减少 协议设计的复杂度 &#xff0c;大…

python中的matplotlib画散点图(数据分析与可视化)

python中的matplotlib画散点图&#xff08;数据分析与可视化&#xff09; import numpy as np import pandas as pd import matplotlib.pyplot as pltpd.set_option("max_columns",None) plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus]Fa…

LLMs参考资料第一周以及BloombergGPT特定领域的训练 Domain-specific training: BloombergGPT

1. 第1周资源 以下是本周视频中讨论的研究论文的链接。您不需要理解这些论文中讨论的所有技术细节 - 您已经看到了您需要回答讲座视频中的测验的最重要的要点。 然而&#xff0c;如果您想更仔细地查看原始研究&#xff0c;您可以通过以下链接阅读这些论文和文章。 1.1 Trans…

NoSuchModuleError: Can‘t load plugin: sqlalchemy.dialects:clickhouse解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

HDLBits-Verilog学习记录 | Verilog Language-Vectors

文章目录 11.vectors | vector012.vectors in more detail | vector113.Vector part select | Vector214.Bitwise operators | Vectorgates15.Four-input gates | Gates416.Vector concatenation operator | Vector317.Vector reversal 1 | Vectorr18. Replication operator | …

HTML常见标签详解

HTML 标签 一 . HTML 结构1. 认识 HTML 标签2. HTML 文件的基本结构3. 标签层次结构 二 . HTML常见标签注释标签标题标签: h1~h6段落标签: p换行标签 :br格式化标签图片标签: img超链接标签: a列表标签无语义标签: div & span 三 . 表格标签1. 基本使用2. 合并单元格 四 . …

轮转数组——C语言

题目&#xff1a; 解法1&#xff1a;把最后一位数字移动到数组的第一位&#xff0c;然后将第二位开始的每一位往前移动一位 这种方法的时间复杂度O&#xff08;N^2&#xff09; 空间复杂度O&#xff08;1&#xff09; rotate(int* arr, int n, int k) {k % n;int i 0;for (i …

实战项目 在线学院springcloud调用篇3

一 springcloud与springboot的关系 1.1 关系 1.2 版本关系 二 案例工程 2.1 工程结构 2.2 调用关系 2.3 注册的配置 1.nacos的搭建部署 2.vod&#xff0c;edu项目的注册nacos 3.查看

如何使用Wireshark进行网络流量分析?

如何使用Wireshark进行网络流量分析。Wireshark是一款强大的网络协议分析工具&#xff0c;可以帮助我们深入了解网络通信和数据流动。 1. 什么是Wireshark&#xff1f; Wireshark是一个开源的网络协议分析工具&#xff0c;它可以捕获并分析网络数据包&#xff0c;帮助用户深入…

11、vue3

一、为什么要学 Vue3 1.1 为什么要学 Vue3 1.2 Vue3的优势 1.3 Vue2 选项式 API vs Vue3 组合式API Vue3 组合式API vs Vue2 选项式 API 二、create-vue搭建Vue3项目 2.1 认识 create-vue 2.2 使用create-vue创建项目 前提环境条件 已安装 16.0 或更高版本的 Node.js node -…

iOS 如何对整张图分别局部磨砂,并完全贴合

官方磨砂方式 - (UIVisualEffectView *)effectView{if(!_effectView){UIBlurEffect *blur [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];_effectView [[UIVisualEffectView alloc] initWithEffect:blur];}return _effectView; }使用这种方式对一张图的上半部分和…

Andriod Studio不支持项目指定的Gradle插件版本

1&#xff0c;问题描述&#xff1a; 我之前的项目执行编译之类的是OK的&#xff0c;download下来最新的代码之后&#xff0c;发现很多编译错误&#xff0c;用gradle更新一下依赖包&#xff0c;发现出现下面的错误 The project is using an incompatible version (AGP 8.1.0) …

基于微信小程序的仓储进销存管理系统_r275i

随着科学研究的不断深入&#xff0c;有关仓储的各种信息量不断成倍增长。面对庞大的信息量&#xff0c;就需要有仓储管理系统来提高管理工作的效率。通过这样的系统&#xff0c;我们可以做到信息的规范管理和快速查询&#xff0c;从而减少了管理方面的工作量。 建立仓储管理系…

Spark大数据分析与实战笔记(第一章 Scala语言基础-1)

文章目录 章节概要1.1 初识Scala1.1.1 Scala的概述1.1.2 Scala的下载安装1.1.3 在IDEA开发工具中下载安装Scala插件1.1.4 开发第一个Scala程序 章节概要 Spark是专为大规模数据处理而设计的快速通用的计算引擎&#xff0c;它是由Scala语言开发实现的&#xff0c;关于大数据技术…

Java可视化物联网智慧工地SaaS平台源码:人脸识别考勤

基于微服务JavaSpring Cloud Vue UniApp MySql实现的智慧工地云平台源码 智慧工地是指利用云计算、大数据、物联网、移动互联网、人工智能等技术手段&#xff0c;为建筑施工现场提供智能硬件及物联网平台的解决方案&#xff0c;以实现建筑工地的实时化、可视化、多元化、智慧化…

oracle存储过程调试

oracle如果存储过程比较复杂&#xff0c;我们要定位到错误就比较困难&#xff0c;那么可以存储过程的调试功能 先按简单的存储过程做个例子&#xff0c;就是上次做的存储过程&#xff08;proc_test&#xff09; 1、先在数据库的procedures文件找到我们之前创建存储过程 2、选…