南山做网站推广乐云seo公司要建个网站

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

南山做网站推广乐云seo,公司要建个网站,网站设计与管理方向,东莞专业网站推广怎么做select 1.初识select2.了解select基本概念和接口介绍3.select服务器4.select特点及优缺点总结 点赞#x1f44d;#x1f44d;收藏#x1f31f;#x1f31f;关注#x1f496;#x1f496; 你的支持是对我最大的鼓励#xff0c;我们一起努力吧!#x1f603;#x1f603;… select 1.初识select2.了解select基本概念和接口介绍3.select服务器4.select特点及优缺点总结 点赞收藏关注 你的支持是对我最大的鼓励我们一起努力吧! 1.初识select 我们曾经说过 IO 等 数据拷贝。 select是多路转接的一种它只负责等待可以一次等待多次fd更为重要的是select本身没有数据拷贝的能力拷贝要read、write来完成。 所以select在IO环节中只负责等一旦哪一个文件描述符就绪了那select要有方式来告知上层哪一个文件描述符好了。然后上层来读取。 系统提供select函数来实现多路复用输入/输出模型. select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;程序会停在select这里等待直到被监视的文件描述符有一个或多个发生了状态改变; 2.了解select基本概念和接口介绍 nfds因为select可以一次等待多个文件描述符而每一个文件描述符它的本质是数组下标所以多个文件描述符它的数字大小肯定不一样同时多个文件描述符也是不同整数构成的它一定有最大一定有最小而其中第一次参数表示select要监视的多个fd中值最大的fd1 如当前监视的是3、4、5、6那个这个nfd就是61。 除了第一个参数剩下的四个参数有一个共同特点全都是输入输出型参数也就是说未来是由我们传给select传过去之后OS也要对传入的值做修改然后输出给我们。 timeoutselect一次等待多个fd最后一个参数决定了当select在等多个fd时它具体的等待方式是什么。 timeout设置为nullptr阻塞式。也就是select一次等待多个df但没有任何一个fd就绪时select只能在底层阻塞这个调用就不返回直到有任何一个就绪了。 struct timeval结构内第一个变量表示的是秒第二个变量表示的是微秒。 当用户调用select的时候如果定义了一个struct timeval timeout{00}传给最后一个参数表示非阻塞。 也就是select一次等待多个df但没有任何一个fd就绪时select立马返回。 如果定义了一个struct timeval timeout{50}传给最后一个参数。表示的是select的调用5s以内阻塞式超过5s非阻塞返回一次。假设5s内有任何一个fd就绪了select都可以立马返回。 然后这个参数会被设置成剩下的秒数。 返回值 ret 0 表示有ret个fd就绪了。 ret 0 表示超时返回。假设设置时间是5s5s内阻塞式不返回超过5s没有一个就绪就是超时了。 ret 0 表示select调用失败了。比如你今天服务器打开3、4、5这三个描述符你现在只有这三个fd是合法的可是你非要把10或20也管理起来10和20在进程根本没有被打开你还要交给select那select当然就调用失败了。 失败返回-1erron被设置。 其实select中间三个参数是最重要的下面介绍一下 select在等什么呢它在等文件描述符上的事件就绪 那是文件描述符上的什么事件就绪呢 通常一般分三类 读事件就绪表示这个文件描述符缓冲区有数据了可以读了。 写事件就绪表示缓冲区内有空间了可以写了。 读写事件就绪我们统称为IO事件就绪 异常事件就绪在进行读写时可能会发生各种意外比如正在给对方写入对方把文件描述符关了此时我正在向一个已经关闭的客户端写入这个时候在写入时可能出现异常。 select未来关心的事情只有三类读写异常 — 对于任何一个fd都是这三种 所以这三个参数就分别对应就是让select关心的读写异常事件。
可是select不是可以同时管理多个fd的读、写、异常事件吗 可是现在select中除了第一个参数给我多个fd的感受我们好像没有见到有多个fd。 我们可以看到这三个参数的类型是fd_set。 它其实是一个位图结构用来表示文件描述符集合。 在信号的时候有三种表pending表block表还有handler表其中pending表block表也就是位图结构。每个比特位表示不同的信号。 文件描述符是0、1、2等这样的数组下标一决定了大家都不同 二大家会连续。所以我们采用位图结构表征各个文件描述符。位图结构一般实现都是采用结构体里面套数组完成。你想有多大位图自己设置就可以。 下面以读事件为例写和异常完全一模一样 如果想让select关心写在定义一个位图结构把文件描述符设置进写集合里。关心异常也是同样做法。 因为后面参数都是输入输出型参数所以操作系统直接在你传的位图中做修改 所以对同一个参数做修改本质就是让用户和内核之间互相沟通互相知晓对方要的或者关心关心的 因此读、写、异常这里操作都是一模一样的 如果你想让select既关心一个文件描述符的读又关心写那就定义两种位图把在这个文件描述符分别添加到读文件描述符集写文件描述符集。那OS就帮我同时关心该文件描述符的读和写了。 所以读、写、异常三个参数位置的不同表示用户和内核分别交互的不同事件。 参数细节现在就说完了。还有一个问题fd_set是一个位图能之间对fd_set这个位图做任何修改吗 不可以不建议 操作系统为了更好支持我们向位图里进行设置查看位图等。系统给我们配了对应的位图操作接口。 void FD_CLR(int fd, fd_set *set); //把一个fd从集合中清除int FD_ISSET(int fd, fd_set *set); //判断一个fd是否在集合里void FD_SET(int fd, fd_set *set); //把一个fd设置到集合里void FD_ZERO(fd_set set); //把证文件描述符集清空3.select服务器 接下来我们写一个select服务器这里我们先只处理读取只获取数据。写入等到epoll哪里在处理边写边介绍select 服务器的更多细节。 先准备一下要用东西下面有些代码是我们以前写tcp服务器已经写过了这里就不在重复说直接用了。 错误码封装 #pragma onceenum {USAGG_ERR 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR };日志函数封装 #pragma once#includeiostream #includestring #includestdio.h #include cstdarg #includectime #includesys/types.h #includeunistd.h #includefstream#define DUGNUM 0 #define NORMAL 1 #define WARNING 2 #define ERROR 3 #define FATAL 4#define LOG_NORMAL log.txt #define LOG_ERR log.errorconst char level_to_string(int level) {switch(level){case DUGNUM: return DUGNUM;case NORMAL: return NORMAL;case WARNING: return WARNING;case ERROR: return ERROR;case FATAL: return FATAL;} }//时间戳变成时间 char* timeChange() {time_t nowtime(nullptr);struct tm* local_time;local_timelocaltime(now);static char time_str[1024];snprintf(time_str,sizeof time_str,%d-%d-%d %d-%d-%d,local_time-tm_year 1900,\local_time-tm_mon 1, local_time-tm_mday,local_time-tm_hour, \local_time-tm_min, local_time-tm_sec);return time_str; }void logMessage(int level,const char* format,…) {//[日志等级] [时间戳/时间] [pid] [message]//[WARNING] [2024-3-21 10-46-03] [123] [创建sock失败] #define NUM 1024//获取时间char* nowtimetimeChange();char logprefix[NUM];snprintf(logprefix,sizeof logprefix,[%s][%s][pid: %d],level_to_string(level),nowtime,getpid());//char logconten[NUM];va_list arg;va_start(arg,format);vsnprintf(logconten,sizeof logconten,format,arg);std::coutlogprefixlogcontenstd::endl; };创建套接字封装 这里为了方便我们全部写成静态成员函数了。后面我们epoll这里在设计一下 #pragma once#include iostream #include string #include cstring #include unistd.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include log.hpp #include err.hppusing namespace std;class Sock {const static int backlog 32;public:static int sock(){// 1. 创建socket文件套接字对象int sock socket(AF_INET, SOCK_STREAM, 0);if (sock 0){logMessage(FATAL, create socket error);exit(SOCKET_ERR);}logMessage(NORMAL, create socket success: %d, sock);//当服务器挂了可以重启int opt 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, opt, sizeof(opt));return sock;}static void Bind(int sock,int port){// 2. bind绑定自己的网络信息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(sock, (struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, bind socket error);exit(BIND_ERR);}logMessage(NORMAL, bind socket success);}static void Listen(int sock){// 3. 设置socket 为监听状态if (listen(sock, backlog) 0) {logMessage(FATAL, listen socket error);exit(LISTEN_ERR);}logMessage(NORMAL, listen socket success);}static int Accept(int listensock, std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len sizeof(peer);int sock accept(listensock, (struct sockaddr *)peer, len);if (sock 0)logMessage(ERROR, accept error, next);else{logMessage(NORMAL, accept a new link success, get new sock: %d, sock); // ?*clientip inet_ntoa(peer.sin_addr);clientport ntohs(peer.sin_port);}return sock;} };调用 #include selectServer.hpp #include err.hpp #include memorystatic void usage(std::string proc) {std::cerr Usage:\n\t proc port \n\n; }int main(int argc,char argv[]) {if(argc ! 2){usage(argv[0]);exit(USAGG_ERR);}unique_ptrSelectServer usl(new SelectServer(atoi(argv[1])));usl-initServer();usl-start();return 0; }服务器 #include iostream #include sock.hppusing namespace std;class SelectServer {static const int defaultport 8080;public:SelectServer(int port defaultport) : _port(port), _listensock(-1){}void initServer(){// 1.创建套接字_listensock Sock::sock();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);}void start(){for (;;){string clientip;uint16_t clientport;int sock Sock::Accept(_listensock, clientip, clientport); // accept 等 获取if (sock 0)continue;// 开始进行服务器的处理逻辑}}~SelectServer(){if (_listensock 0)close(_listensock);}private:int _listensock;int _port; };上面服务器的代码都是我们曾经写过的今天写select服务器当然不是这么简单了。 下面我要在网络服务器中引入select接口。 select它是一个只做监听的只做IO中等待的系统调用接口一旦有事件就绪了它会通知我它一次可以等待多个文件描述符可是目前我们面临的第一个尴尬问题是你刚开始的服务器根本没有多个文件描述符刚开始只有一个啊而且还是_listensock套接字。可是我们也知道_listensock也是套接字而后序所有多出来的套接字本质上都是从_listensock上来的所以select要监管多个套接字的话首先要把_listensock监管起来 因此_listensock首先要交给select那我们要想清楚了select有读事件写事件异常事件并没有任何所谓的监听事件啊但是没问题能交给_listensock的连接就绪事件 读事件就绪因为本质就是对方发的连接请求触发的三次握手也属于客户端向服务器发信息所以认为是读事件就绪 这里也不应该_listensock套接字创建好了直接循环获取accept因为accept函数自己通过_listensock套接字获取连接时没有连接时accpet也在阻塞等。有连接了才能获取连接然后返回。所以accpet 等 获取。这种写法是阻塞式写法我们想用的是多路转接。 我们的想法是当底层连接就绪了你来通知我这个时候我在调用accept。 此时相当于让select帮我负责等而accpet只负责获取连接不会被阻塞。 现在要做的就是先调用select因为我们现在就一个_listensock因此select目前写法是不正确的。我们接下来慢慢改。 void start() {// 目前有些地方写的有问题fd_set rfds;FD_ZERO(rfds); // 对读文件描述符集初始化FD_SET(_listensock, rfds); // 将_listensock添加到读文件描述符集合中struct timeval timeout{3,0};for (;;){//我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我int n select(_listensock 1, rfds, nullptr, nullptr, timeout);switch (n){case 0:logMessage(NORMAL, timeout…);break;case -1:logMessage(WARNING, select error, code: %d, err string: %s, errno, strerror(errno));break;default://说明有事件就绪了,目前只有一个监听事件就绪了break;}// string clientip;// uint16_t clientport;// int sock Sock::Accept(_listensock, clientip, clientport); accept 等 获取// if (sock 0)// continue;// // 开始进行服务器的处理逻辑} }我们先验证这个timeout目前是3s内阻塞超过3s非阻塞返回一次。截图效果可能不明显。
这里简单描述一下刚开始等待3秒3秒后返回打印timeout… 然后就不阻塞了一直非阻塞打印timeout… 为什么 原因在于timeout传进来它是一个输入输出型参数。当你输入时是3s3内没有任何时间就绪那么这个timeout时间就在select内部慢慢见到了0s然后回过头在去select的时候这个timeout已经被改过了就变成全0了。所以此时只能是第一次阻塞等待了剩下都是非阻塞了。 所以正确写法是把timeout放入循环内部保证每一次都对它重新设定。 这样就是没有任何事件就绪时每隔3秒非阻塞返回一次。同样也可以把timeout改成0此时就是非阻塞了。并且可以不要timeout直接把最后一个参数设为nullptr此时就是阻塞了。 void start(){fd_set rfds;FD_ZERO(rfds); // 对读文件描述符集初始化FD_SET(_listensock, rfds); // 将_listensock添加到读文件描述符集合中for (;;){struct timeval timeout{3,0};//特定时间阻塞超过特定时间非阻塞返回一次//struct timeval timeout{0,0};非阻塞式//我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我//int n select(_listensock 1, rfds, nullptr, nullptr, timeout);//阻塞式int n select(_listensock 1, rfds, nullptr, nullptr, nullptr);switch (n){case 0:logMessage(NORMAL, timeout…);break;case -1:logMessage(WARNING, select error, code: %d, err string: %s, errno, strerror(errno));break;default://说明有事件就绪了,目前只有一个监听事件就绪了break;}}}下面改一点代码然后用telnet连接一下这个服务器看一下。 void start() {for (;;){fd_set rfds;FD_ZERO(rfds); // 对读文件描述符集初始化FD_SET(_listensock, rfds); // 将_listensock添加到读文件描述符集合中//struct timeval timeout {3, 0};// 我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我// int n select(_listensock 1, rfds, nullptr, nullptr, timeout);int n select(_listensock 1, rfds, nullptr, nullptr, nullptr);switch (n){case 0:logMessage(NORMAL, timeout…);break;case -1:logMessage(WARNING, select error, code: %d, err string: %s, errno, strerror(errno));break;default:// 说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL, get a new link…);break;}} }我们看到一直在打印get a new link… 日志在一直打说明我们当前循环的时候select一直在循环。不是只有一个连接吗怎么会给我这么多就绪消息。 原因在于我们并没有把底层的连接取走所以每一次调用select我们对应的_listensock套接字上面的事件都是就绪的所以每一次都添加关心的都是_listensock套接字所以select每一次都帮我们检测连接_listensock套接字有没有就绪。 为什么客户端关闭了服务器还在疯狂打印呢 因为连接建立成功后断开连接是双方的事。服务器连连接都没拿上去也就没有办法断开因为没有调用close。所以依旧告诉连接就绪。 所以我们发现select果然是有把连接就绪事件告诉我们的能力了然后我们也通过select监听到有连接事件到来了因此我们还要获取对应连接。连接就绪事件是被放在我们传给select的rfds里。输入时是用户告诉内核你要帮我关心该文件描述符集中那些fd的读事件输出时内核告诉用户该文件描述符集中那些fd读就绪了。 void HandlerEvent(fd_set rfds) {// 这里目前一定是_listensock,只有这一个// 但未来可能有很多fd就绪了,如何判断是_listensock就绪了呢?// 因此这里判断一下_listensock是否在这个集合里if (FD_ISSET(_listensock, rfds)){//走到这里就是, select 告送我,_listensock就绪了,然后才能执行下面代码string clientip;uint16_t clientport;int sock Sock::Accept(_listensock, clientip, clientport); accept 等 获取if (sock 0) return;logMessage(NORMAL,accept success [%s:%d],clientip.c_str(),clientport);} }void start() {for (;;){fd_set rfds;FD_ZERO(rfds); // 对读文件描述符集初始化FD_SET(_listensock, rfds); // 将_listensock添加到读文件描述符集合中// struct timeval timeout {3, 0};// 我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我// int n select(_listensock 1, rfds, nullptr, nullptr, timeout);int n select(_listensock 1, rfds, nullptr, nullptr, nullptr);switch (n){case 0:logMessage(NORMAL, timeout…);break;case -1:logMessage(WARNING, select error, code: %d, err string: %s, errno, strerror(errno));break;default:// 说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL, get a new link…);HandlerEvent(rfds);break;}} }这一次我们把连接拿上来所以在select就没有新连接了。也就不会疯狂打印get a new link… 不过还有问题走到这里accpet函数会不会被阻塞 不会因为走到这里_listensock已经是就绪的了。accpet直接读取绝对是有返回的。 得到一个sock套接字后然后我们可以直接进行read/recv吗 显然不能你直接调用read/recv你就能保证底层有数据吗不能建立好连接就是不给你发数据因为我们是单进程你直接调用read/recv就直接阻塞挂起了。根据原因就是你根本不清楚对应sock读事件是否就绪。 一调可能就阻塞你不清楚那谁清楚整个代码只有select有资格检测事件是否就绪 所以接下来并不是立马读取而是将新的sock 托管给select 让select帮我关心这个sock有没有事件就绪。 现在问题是你怎么把这个sock托管给select 一般而言编写select服务器要使用select需要程序员自己维护一个保存合法fd的数组! 首先套接字会越来越多你怎么知道这么多fd那些fd是合法的那些是非法的。其次rfds这个参数是输入输出型的你可能曾经设置过5个fd可能循环一次这些fd全都被清空了那你怎么知道历史上还有那些fd最后这么多fd你怎么保证更新出来的fd最大值是谁 所以我们得自己维护一个合法fd数组 因为增加一个类成员变量_fdarray 然后在初始服务时new一个数组但是这个数组给多大呢
我们未来保存所有文件描述符的类型是fd_set这是Linux内核给我们提供的自定义类型既然是一种类型它必有大小而且大小是固定的 所以我们能够添加的fd的个数一定是有上限的 大小是128 不过这里sizeof求得是字节但fd_set是一个位图结构因此还有乘8才是真实大小。 1024也就是说select服务器能够处理得文件描述符上限是1024个 所以new数组大小就是1024 然后对数组做一下初始化 然后在服务器启动之前未来所有合法fd都在这个数组里面未来要重新设置要关心的读文件描述符集更新最大值都在这个数组找这就决定了在刚开始的时候首先最开始只有一个_listensock套接字先设置进数组。 void initServer() {// 1.创建套接字_listensock Sock::sock();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_fdarray new int[fdnum];for (int i 0; i fdnum; i)_fdarray[i] defaultfd;_fdarray[0] _listensock;//这个位置后面就不变了 }然后启动服务器每一次select之前都要在数组内找到合法fd最大值是多少并且将数组中所有合法fd重新设置到读文件描述符集 void start() {for (;;){fd_set rfds;FD_ZERO(rfds); // 对读文件描述符集初始化int maxfd _fdarray[0];for (int i 0; i fdnum; i){if (_fdarray[i] defaultfd)//非法,没有被设置的continue;//因为rfds是输入输出型参数,因此每次都要将合法fd,重新添加到读文件描述符集FD_SET(_fdarray[i], rfds);if (maxfd _fdarray[i])maxfd _fdarray[i];//更新合法fd中最大fd}// FD_SET(_listensock, rfds); // 将_listensock添加到读文件描述符集合中// struct timeval timeout {3, 0};// 我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我// int n select(_listensock 1, rfds, nullptr, nullptr, timeout);int n select(maxfd 1, rfds, nullptr, nullptr, nullptr);switch (n){case 0:logMessage(NORMAL, timeout…);break;case -1:logMessage(WARNING, select error, code: %d, err string: %s, errno, strerror(errno));break;default:// 说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL, get a new link…);HandlerEvent(rfds);break;}} }接下来继续之前未完的事情accpet获取新的sock将新的sock托管给select 将新的sock托管给select的本质其实就是将sock添加到fdarray数组里 void HandlerEvent(fd_set rfds) {// 这里目前一定是_listensock,只有这一个// 但未来可能有很多fd就绪了,如何判断是_listensock就绪了呢?// 因此这里判断一下_listensock是否在这个集合里if (FD_ISSET(_listensock, rfds)){// 走到这里,accept 函数,会不会被阻塞?// 走到这里就是, select 告送我,_listensock就绪了,然后才能执行下面代码string clientip;uint16_t clientport;int sock Sock::Accept(_listensock, clientip, clientport); accept 等 获取if (sock 0)return;logMessage(NORMAL, accept success [%s:%d], clientip.c_str(), clientport);// 得到一个sock套接字后然后我们可以直接进行read/recv吗? 不能,整个代码只有select有资格检测事件是否就绪// 将新的sock 托管给select// 将新的sock托管给select的本质其实就是将sock添加到fdarray数组里int i 0;for (; i fdnum; i){//找放合法fd的位置if (_fdarray[i] ! defaultfd)continue;elsebreak;}if (i fdnum)//处理数组满的情况{logMessage(WARNING, server if full, please wait);close(sock);}else//添加到_fdarray数组{_fdarray[i] sock;}} }然后这里会从将sock从fdarray数组中交给select 为了看一看托管的文件描述符是不是越来越多了写一个打印函数当把sock添加fdarray数组之后调用一下这个函数看一下 void print() {for (int i 0; i fdnum; i){if (_fdarray[i] ! defaultfd)cout _fdarray[i] ;}cout endl; }所以随着连接增多对应的合法fd全部都会添加到_fdarray数组中然后处理完就绪事件之后每次在调用select之前都会把_fdarray中合法fd添加到rfds找到合法fd最大值一起交给select。 可是随着数组中合法fd越来越多select帮我们监管的fd也越来越多了那么事件的总类也变得越来越多了不过我们目前只处理读事件。并且我们处理读事件函数中也只写了一个_listensock套接字获取accpet获取连接。这是不够的我们还需要考虑处理正常的IO。 下面把处理_listensock套接字单独拿出来做一个封装 void Accepter(int listensock) {logMessage(DEBUG, Accepter in);// 走到这里,accept 函数,会不会被阻塞?// 走到这里就是, select 告送我,_listensock就绪了,然后才能执行下面代码string clientip;uint16_t clientport;int sock Sock::Accept(listensock, clientip, clientport); accept 等 获取if (sock 0)return;logMessage(NORMAL, accept success [%s:%d], clientip.c_str(), clientport);// 得到一个sock套接字后然后我们可以直接进行read/recv吗? 不能,整个代码只有select有资格检测事件是否就绪// 将新的sock 托管给select// 将新的sock托管给select的本质其实就是将sock添加到fdarray数组里int i 0;for (; i fdnum; i){if (_fdarray[i] ! defaultfd)continue;elsebreak;}if (i fdnum){logMessage(WARNING, server if full, please wait);close(sock);}else{_fdarray[i] sock;}print();logMessage(DEBUG, Accepter out); }// 1.handler event rfds 中,不仅仅是有一个fd是就绪的,可能存在多个 // 2.我们的select目前只处理了read事件 void HandlerEvent(fd_set rfds) {// 你怎么知道那些fd就绪了呢? 我不知道,我只能遍历for (int i 0; i fdnum; i){// 不合法fdif (_fdarray[i] defaultfd)continue;// 合法fd,但不一定就绪,要先判断if (_fdarray[i] _listensock FD_ISSET(_listensock, rfds))Accepter(_listensock);//处理_listensockelse if (FD_ISSET(_fdarray[i], rfds))Recver(_fdarray[i], i);//处理其他sock} }接下来处理正常IO void Recver(int sock, int pos) {logMessage(DEBUG, in Recver);// 1. 读取request// 这样读取是有问题的char buffer[1024];// 这里在进行读取的时候会不会被阻塞ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0); //… }不会被阻塞走到这里一定是sock读事件已经就绪了。其次这里不仅仅处理一个sock可能多个sock都会进来所以就一个栈上的缓存区去读取绝对是有问题的其次你怎么保证数据一次就读完了呢没有读完是不是要循环读取但是你怎么保证在读的时候不会被阻塞呢并且读完了就是一个完整的请求了吗然后反序列化等等这些问题我们都在epoll哪里处理 void Recver(int sock, int pos) {logMessage(DEBUG, in Recver);// 1. 读取request// 这样读取是有问题的char buffer[1024];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0); // 这里在进行读取的时候会不会被阻塞if (s 0)//读取成功{buffer[s] 0;logMessage(NORMAL, client# %s, buffer);}else if (s 0) //对方关闭了文件描述符{close(sock);//我也关_fdarray[pos] defaultfd;//不让select关心该sock了logMessage(NORMAL, client quit);return;}else//读取失败{close(sock);_fdarray[pos] defaultfd;logMessage(ERROR, client quit: %s, strerror(errno));return;}// 2.处理request }接下来处理request这里我们在类里在加入一个回调函数然后简单的直接返回就行了 void Recver(int sock, int pos){logMessage(DEBUG, in Recver);// 1. 读取request// 这样读取是有问题的char buffer[1024];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0); // 这里在进行读取的时候会不会被阻塞if (s 0)//读取成功{buffer[s] 0;logMessage(NORMAL, client# %s, buffer);}else if (s 0) //对方关闭了文件描述符{close(sock);//我也关_fdarray[pos] defaultfd;//不让select关心该sock了logMessage(NORMAL, client quit);return;}else//读取失败{close(sock);_fdarray[pos] defaultfd;logMessage(ERROR, client quit: %s, strerror(errno));return;}// 2. 处理requeststd::string response _cbs(buffer);// 3. 返回response}接下来把处理结果给给用户返回但是你怎么保证写事件就绪了呢所以这里还要在创建一个fd_set写事件然后把sock添加到这个写文件描述符集在添加到select写这里等到对应sock读事件就绪了才能给用户返回去。不过那样代码太复杂了所以今天不考虑这么多直接先写。 void Recver(int sock, int pos) {logMessage(DEBUG, in Recver);// 1. 读取request// 这样读取是有问题的char buffer[1024];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0); // 这里在进行读取的时候会不会被阻塞if (s 0)//读取成功{buffer[s] 0;logMessage(NORMAL, client# %s, buffer);}else if (s 0) //对方关闭了文件描述符{close(sock);//我也关_fdarray[pos] defaultfd;//不让select关心该sock了logMessage(NORMAL, client quit);return;}else//读取失败{close(sock);_fdarray[pos] defaultfd;logMessage(ERROR, client quit: %s, strerror(errno));return;}// 2. 处理requeststd::string response _cbs(buffer);// 3. 返回response// write bugwrite(sock, response.c_str(), response.size());logMessage(DEBUG, out Recver); }自此简单的select服务器写完了下面是服务器完整代码 #pragma once#include iostream #includefunctional #include sock.hppusing namespace std;class SelectServer {static const int defaultport 8080;static const int fdnum sizeof(fd_set) * 8;static const int defaultfd -1;using func_tfunctionstring(string);public:SelectServer(func_t f,int port defaultport) : _cbs(f),_port(port), _listensock(-1), _fdarray(nullptr){}void initServer(){// 1.创建套接字_listensock Sock::sock();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_fdarray new int[fdnum];for (int i 0; i fdnum; i)_fdarray[i] defaultfd;_fdarray[0] _listensock; // 这个位置后面就不变了}void print(){for (int i 0; i fdnum; i){if (_fdarray[i] ! defaultfd)cout _fdarray[i] ;}cout endl;}void Accepter(int listensock){logMessage(DEBUG, Accepter in);// 走到这里,accept 函数,会不会被阻塞?// 走到这里就是, select 告送我,_listensock就绪了,然后才能执行下面代码string clientip;uint16_t clientport;int sock Sock::Accept(listensock, clientip, clientport); accept 等 获取if (sock 0)return;logMessage(NORMAL, accept success [%s:%d], clientip.c_str(), clientport);// 得到一个sock套接字后然后我们可以直接进行read/recv吗? 不能,整个代码只有select有资格检测事件是否就绪// 将新的sock 托管给select// 将新的sock托管给select的本质其实就是将sock添加到fdarray数组里int i 0;for (; i fdnum; i){if (_fdarray[i] ! defaultfd)continue;elsebreak;}if (i fdnum){logMessage(WARNING, server if full, please wait);close(sock);}else{_fdarray[i] sock;}print();logMessage(DEBUG, Accepter out);}void Recver(int sock, int pos){logMessage(DEBUG, in Recver);// 1. 读取request// 这样读取是有问题的char buffer[1024];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0); // 这里在进行读取的时候会不会被阻塞if (s 0)//读取成功{buffer[s] 0;logMessage(NORMAL, client# %s, buffer);}else if (s 0) //对方关闭了文件描述符{close(sock);//我也关_fdarray[pos] defaultfd;//不让select关心该sock了logMessage(NORMAL, client quit);return;}else//读取失败{close(sock);_fdarray[pos] defaultfd;logMessage(ERROR, client quit: %s, strerror(errno));return;}// 2. 处理requeststd::string response _cbs(buffer);// 3. 返回response// write bugwrite(sock, response.c_str(), response.size());logMessage(DEBUG, out Recver);}// 1.handler event rfds 中,不仅仅是有一个fd是就绪的,可能存在多个// 2.我们的select目前只处理了read事件void HandlerEvent(fd_set rfds){// 你怎么知道那些fd就绪了呢? 我不知道,我只能遍历for (int i 0; i fdnum; i){// 不合法fdif (_fdarray[i] defaultfd)continue;// 合法fd,但不一定就绪,要先判断if (_fdarray[i] _listensock FD_ISSET(_listensock, rfds))Accepter(_listensock);else if (FD_ISSET(_fdarray[i], rfds))Recver(_fdarray[i], i);}}void start(){for (;;){fd_set rfds;FD_ZERO(rfds); // 对读文件描述符集初始化int maxfd _fdarray[0];for (int i 0; i fdnum; i){if (_fdarray[i] defaultfd) // 非法,没有被设置的continue;// 因为rfds是输入输出型参数,因此每次都要将合法fd,重新添加到读文件描述符集FD_SET(_fdarray[i], rfds);if (maxfd _fdarray[i])maxfd _fdarray[i];}// FD_SET(_listensock, rfds); // 将_listensock添加到读文件描述符集合中// struct timeval timeout {3, 0};// 我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我// int n select(_listensock 1, rfds, nullptr, nullptr, timeout);int n select(maxfd 1, rfds, nullptr, nullptr, nullptr);switch (n){case 0:logMessage(NORMAL, timeout…);break;case -1:logMessage(WARNING, select error, code: %d, err string: %s, errno, strerror(errno));break;default:// 说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL, have event ready!);HandlerEvent(rfds);break;}}}~SelectServer(){if (_listensock 0)close(_listensock);if (_fdarray)delete[] _fdarray;}private:int _listensock;int _port;int *_fdarray;func_t _cbs;};4.select特点及优缺点总结 select能同时等待的文件fd是有上限的除非重新改内核否则无法解决必须借助第三方数组来维护合法的fdselect的大部分参数都是输入输出型的调用select前要重新设置所有的fd调用之后我们还要更新所有的fd这带来的就是遍历的成本 — (用户层面)select为什么第一个参数是最大fd1呢 因为select要等待多个文件描述符 它怎么知道要等那些文件描述符那些事件呢怎么知道给你返回那个文件描述符的事件就绪了所以它要去查它既然要查就要去限定它去查的范围因为文件描述符就是一个个数组下标我有多个文件描述符表要遍历到哪里呢所以就有了最大值fd1 确定遍历范围 — (内核层面)select 采用位图用户-内核内核-用户来回的进行数据拷贝有拷贝成本的问题 因为select有如此之多的问题select接口使用不方便每次还要重新手动设置等等。。所以我们要有一种新的解决方案这种方案就是多种转接之epoll。不过在此之前先了解多种转接之poll。