当前位置: 首页 > news >正文

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

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   4const 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=nullptrchar 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_NONEif(_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的第二个参数+1const 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   4const 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=nullptrchar 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_NONEif(_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的第二个参数+1const 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   4const 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=nullptrchar 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的第二个参数+1const 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,添加给epollif(!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, 4break;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();
}

相关文章:

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

select初识 select是系统提供的一个多路转接接口。 select系统调用可以让我们的程序同时监视多个文件描述符的上的事件是否就绪。 select的核心工作就是等&#xff0c;当监视的多个文件描述符中有一个或多个事件就绪时&#xff0c;select才会成功返回并将对应文件描述符的就绪…...

FPGA原理与结构——FIFO IP核原理学习

一、FIFO概述 1、FIFO的定义 FIFO是英文First-In-First-Out的缩写&#xff0c;是一种先入先出的数据缓冲器&#xff0c;与一般的存储器的区别在于没有地址线&#xff0c; 使用起来简单&#xff0c;缺点是只能顺序读写数据&#xff0c;其数据地址由内部读写指针自动加1完成&…...

【Linux操作系统】Linux中的信号回收:管理子进程的关键步骤

在Linux中&#xff0c;我们可以通过捕获SIGCHLD信号来实现对子进程的回收。当一个子进程终止时&#xff0c;内核会向其父进程发送SIGCHLD信号。父进程可以通过注册信号处理函数&#xff0c;并在处理函数中调用wait()或waitpid()函数来回收已终止的子进程。 文章目录 借助信号捕…...

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;关于大数据技术…...

R语言03-R语言中的矩阵

概念 在R语言中&#xff0c;矩阵&#xff08;Matrix&#xff09;是一个二维的数据结构&#xff0c;由行和列组成&#xff0c;其中所有元素必须具有相同的数据类型。矩阵可以用于存储数值型数据&#xff0c;常用于线性代数运算、统计计算以及数据处理等领域。 代码示例 # 创建…...

“深入理解JVM:探索Java虚拟机的工作原理与优化技巧“

标题&#xff1a;深入理解JVM&#xff1a;探索Java虚拟机的工作原理与优化技巧 摘要&#xff1a;本文将深入探索Java虚拟机&#xff08;JVM&#xff09;的工作原理及优化技巧。我们将介绍JVM的架构和组成部分&#xff0c;解释JVM是如何将Java字节码转换为可执行代码的。我们还…...

SQL注入原理

SQL、SQL注入是什么&#xff1f; 结构化查询语言(Structured Query Language&#xff0c;SQL)&#xff0c;是一种特殊的编程语言&#xff0c;用于数据库的标准数据查询。1986 年10 月美国国家标准协会对SQL 进行了规范后&#xff0c;以此作为关系型数据库系统的标准语言。1987 …...

PIL.Image和base64,格式互转

将PIL.Image转base64 ##PIL转base64 import base64 from io import BytesIOdef pil_base64(image):img_buffer BytesIO()image.save(img_buffer, formatJPEG)byte_data img_buffer.getvalue()base64_str base64.b64encode(byte_data)return base64_str将base64转PIL.Image …...

vue父子组件传值(v-model)

父组件使用v-model传值给子组件 <template><!-- 按钮 --> <el-button click"addMenu(new)">打开弹框</el-button><!-- 自定义组件,下面这两种写法都可以&#x1f447; --> <MediaDialog :name"name" v-model:visible&qu…...

Java接口详解

接口 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本上的USB口&#xff0c;电源插座等。 电脑的USB口上&#xff0c;可以插&#xff1a;U盘&#xff0c;鼠标&#xff0c;键盘等所有符合USB协议的设备 电源插座插孔上&#xff0c;…...

Windows共享文件夹,用户密码访问

Windows共享文件夹&#xff0c;用户密码访问 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.先创建一个用户 计算机右键----管理----本地用户和组----点击用户进去---右键新建用户 这里以kk为例 2.找到你想共享的文件夹 3.共享-想共享的文件夹---右键---属性---共…...

Mac更新node

查看本机node版本 node -v 删除node相关内存 sudo npm cache clean -f 安装n sudo npm install n -g 更新node版本 sudo n stable // 把当前系统的 Node 更新成最新的 “稳定版本” sudo n lts // 长期支持版 sudo n latest // 最新版 sudo n 18.17.1 // 指定安装版本 可以顺便…...

2023国赛数学建模思路 - 案例:粒子群算法

文章目录 1 什么是粒子群算法&#xff1f;2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Pa…...

Wireshark数据抓包分析之ARP协议

一、实验目的&#xff1a; 通过wireshark的数据抓包了解这个ARP协议的具体内容 二、预备知识: 1.Address Resolution Protocol协议&#xff0c;就是通过目标IP的值&#xff0c;获取到目标的mac地址的一个协议 2.ARP协议的详细工作过程&#xff0c;下面描述得非常清晰&#xff…...

6个比较火的AI绘画生成工具

随着人工智能技术的发展&#xff0c;市场上出现了越来越多的人工智能图像生成工具。这些人工智能图像生成工具可以自动创建惊人的图像、艺术作品和设计&#xff0c;以帮助设计师和创意人员更快地实现他们的创造性想法。在本文中&#xff0c;我们将推荐7种最近流行的人工智能图像…...

静力水准仪说明介绍

静力水准仪是测量两点间或多点间相对高程变化的仪器。由储液器、高精度芯体和特别定制电路模块、保护罩等部件组成。沉降系统由多个同型号传感器组成&#xff0c;储液罐之间由通气管和通液管相连通&#xff0c;基准点置于一个稳定的水平基点&#xff0c;当测点相对于基准点发生…...

HAProxy 高级功能与配置

HAProxy 高级功能与配置 配置和验证的环境看这篇文章:HAProxy 各种调度算法介绍 一.基于 cookie 的会话保持 使用cookie关键字来配置后端服务器基于 cookie 的会话持久连接。 配置格式 cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ][ post…...

cuda编程002—流

没有使用同步的情况&#xff1a; #include <stdio.h> #include <cuda_runtime.h>__global__ void test_kernel(){printf("Message from Device.\n"); } void test(){test_kernel<<<1, 1>>>(); } #include <cuda_runtime.h> #i…...

2023年国赛 高教社杯数学建模思路 - 案例:粒子群算法

文章目录 1 什么是粒子群算法&#xff1f;2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Pa…...

【C#学习笔记】数据类中常用委托及接口——以List<T>为例

文章目录 List\<T\>/LinkedList \<T\>为什么是神&#xff1f;&#xff08;泛型为什么是神&#xff09;一些常见&#xff0c;通用的委托和接口ComparisonEnumerator List<T>/LinkedList <T>为什么是神&#xff1f;&#xff08;泛型为什么是神&#xff0…...

idea的断点调试

1、行断点 首先在代码的最左侧点击会显示红色的圆圈 第二步在main方法中右键选中debug run进行运行 会出现下面图片的情况 出现上图之后&#xff0c;点击console 下一步 这个时候就可以看到调试的结果了 6、方法调用栈&#xff1a;这里显示了该线程调试所经过的所有方法&…...

vue和react学哪一个比较有助于以后发展?

前言 首先声明vue和react这两个框架都是很优秀的前端框架&#xff0c;使用的人群下载量上数量也是相当的庞大&#xff0c;这篇文章没有贬低或者攻击任何一个框架的意思&#xff0c;只在于根据答主的问题来对这两个框架做出对比&#xff0c;以方便大家更加清晰的了解到当下vue和…...

【SkyWalking】分布式服务追踪与调用链系统

1、基本介绍 SkyWalking是一个开源的观测平台&#xff0c;官网&#xff1a;Apache SkyWalking&#xff1b; 可监控&#xff1a;分布式追踪调用链 、jvm内存变化、监控报警、查看服务器基本配置信息。 2、SkyWalking架构原理 在整个skywalking的系统中&#xff0c;有三个角色&am…...

Python“牵手”速卖通商品详情API接口运用场景及功能介绍

速卖通电商API接口是针对速卖通提供的电商服务平台&#xff0c;为开发人员提供了简单、可靠的技术来与速卖通电商平台进行数据交互&#xff0c;实现一系列开发、管理和营销等操作。其中包括商品详情API接口&#xff0c;通过这个API接口商家可以获取商品的详细信息&#xff0c;包…...

java调用python脚本的示例

java调用python脚本的示例 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class JavaCallPythonScript {public static void main(String[] args) {// 调用Python脚本的命令String pythonScriptPath "path/to/y…...

【C语言】柔性数组(可边长数组)

一、介绍 柔性数组&#xff08;Flexible Array&#xff09;&#xff0c;又称可变长数组。一般数组的长度是在编译时确定&#xff0c;而柔性数组对象的长度在运行时确定。在定义结构体时允许创建一个空数组&#xff08;例如&#xff1a;arr [ 0 ] &#xff09;&#xff0c;该数…...

C++信息学奥赛1131:基因相关性

这段代码的功能是比较两个字符串的相似度&#xff0c;并根据给定的阈值判断是否相似。 解析注释后的代码如下&#xff1a; #include <iostream> #include <string> using namespace std;int main() {double bf; // 定义双精度浮点数变量bf&#xff0c;用于存储阈…...

如何保证分布式系统中服务的高可用性:应对 ZooKeeper Leader 节点故障的注册处理策略

推荐阅读 AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画、AI讲话、翻译,GPU点亮AI想象空间 资源分享 「java、python面试题」来自UC网盘app分享&#xff0c;打开手机app&#xff0c;额外获得1T空间 https://dr…...

SQL注入之延时注入

文章目录 延时注入是什么&#xff1f;延时注入获取数据库版本号 延时注入是什么&#xff1f; 延时注入就是利用sleep()函数通过if语句判断所写的语句真假&#xff0c;如果为真返回我们想要的东西&#xff08;例如&#xff1a;数据库的长度&#xff0c;数据库的名字等&#xff0…...

运维高级学习--Docker(二)

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 #拉取mysql5.6和owncloud镜像 [rootlocalhost ~]# docker pull mysql:5.6 [rootlocalhost ~]# docker pull owncloud [rootlocalhost ~]# docker images REPOSITORY TAG IMAGE ID CREATED …...

律所网站建设国队男子接力赛/电商培训班一般多少钱一个月

一、问答题 1、自动化代码中&#xff0c;用到了哪些设计模式&#xff1f; 2、什么是Selenium 3、TestNG中注解有哪些&#xff1f; 4、什么是断言&#xff1f; 5、TestNG有哪些有点&#xff1f; 6、什么是web自动化测试&#xff1f; 7、写出Selenium中你最熟悉的接口或类…...

wordpress3.3/网络营销与传统营销的区别

1)API申请2)API调用3)[b]效果展示[/b]4)精准查询--------------------------------------------------------------------------1)API申请爱帮提供查询接口API&#xff0c;需要申请Key http://www.aibang.com查询数量有限制&#xff0c;2013-12-4 最多 1000次/每天 10次/分钟返…...

介绍做燕窝的网站/百度搜索推广费用

UVM验证环境–force的一种用法...

甘肃 政府 网站建设/百度人工在线客服

一、 linux文件系统 linux使用标准的目录结构&#xff0c;在安装的时候&#xff0c;安装程序就已经为用户创建了文件系统和完整而固定的目录组成形式&#xff0c;并指定了每个目录的作用和其中的文件类型。 文件系统树状结构如下&#xff1a; / 根目录 ┏━━━┳━━━┳━━…...

西安学校网站建设哪家好/软文营销文章案例

1.函数基本语法及特性 什么是函数&#xff1f; 函数这个名词源于数学&#xff0c;是由莱布尼兹在1694年开始使用的。而我们这里讲的函数与数学中的函数区别还是很大的&#xff0c;这里我们就不多加赘述了。 定义&#xff1a; 函数是指一段在一起的、可以做某一件事儿的程序。也…...

看24小时b站直播/网站推广软件

FLL智能交通任务FLL是学校今年新上的项目&#xff0c;FLL比赛是一个激发孩子解决问题、激发创意的平台。FLL机器人比赛是由机器人全自动的执行的&#xff0c;也就是说&#xff0c;机器人在执行任务时你不要去干扰它。但是大多数的队伍都要在比赛时去中断机器人&#xff0c;这样…...