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(×tamp);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(×tamp);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(×tamp);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的核心工作就是等,当监视的多个文件描述符中有一个或多个事件就绪时,select才会成功返回并将对应文件描述符的就绪…...

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

【Linux操作系统】Linux中的信号回收:管理子进程的关键步骤
在Linux中,我们可以通过捕获SIGCHLD信号来实现对子进程的回收。当一个子进程终止时,内核会向其父进程发送SIGCHLD信号。父进程可以通过注册信号处理函数,并在处理函数中调用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是专为大规模数据处理而设计的快速通用的计算引擎,它是由Scala语言开发实现的,关于大数据技术…...
R语言03-R语言中的矩阵
概念 在R语言中,矩阵(Matrix)是一个二维的数据结构,由行和列组成,其中所有元素必须具有相同的数据类型。矩阵可以用于存储数值型数据,常用于线性代数运算、统计计算以及数据处理等领域。 代码示例 # 创建…...
“深入理解JVM:探索Java虚拟机的工作原理与优化技巧“
标题:深入理解JVM:探索Java虚拟机的工作原理与优化技巧 摘要:本文将深入探索Java虚拟机(JVM)的工作原理及优化技巧。我们将介绍JVM的架构和组成部分,解释JVM是如何将Java字节码转换为可执行代码的。我们还…...
SQL注入原理
SQL、SQL注入是什么? 结构化查询语言(Structured Query Language,SQL),是一种特殊的编程语言,用于数据库的标准数据查询。1986 年10 月美国国家标准协会对SQL 进行了规范后,以此作为关系型数据库系统的标准语言。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><!-- 自定义组件,下面这两种写法都可以👇 --> <MediaDialog :name"name" v-model:visible&qu…...

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

Windows共享文件夹,用户密码访问
Windows共享文件夹,用户密码访问 小白教程,一看就会,一做就成。 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 什么是粒子群算法?2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法? 粒子群算法(Pa…...

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

6个比较火的AI绘画生成工具
随着人工智能技术的发展,市场上出现了越来越多的人工智能图像生成工具。这些人工智能图像生成工具可以自动创建惊人的图像、艺术作品和设计,以帮助设计师和创意人员更快地实现他们的创造性想法。在本文中,我们将推荐7种最近流行的人工智能图像…...
静力水准仪说明介绍
静力水准仪是测量两点间或多点间相对高程变化的仪器。由储液器、高精度芯体和特别定制电路模块、保护罩等部件组成。沉降系统由多个同型号传感器组成,储液罐之间由通气管和通液管相连通,基准点置于一个稳定的水平基点,当测点相对于基准点发生…...
HAProxy 高级功能与配置
HAProxy 高级功能与配置 配置和验证的环境看这篇文章:HAProxy 各种调度算法介绍 一.基于 cookie 的会话保持 使用cookie关键字来配置后端服务器基于 cookie 的会话持久连接。 配置格式 cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ][ post…...

cuda编程002—流
没有使用同步的情况: #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 什么是粒子群算法?2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法? 粒子群算法(Pa…...

【C#学习笔记】数据类中常用委托及接口——以List<T>为例
文章目录 List\<T\>/LinkedList \<T\>为什么是神?(泛型为什么是神)一些常见,通用的委托和接口ComparisonEnumerator List<T>/LinkedList <T>为什么是神?(泛型为什么是神࿰…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...