96SEO 2026-02-20 00:02 16
念和接口介绍3.select服务器4.select特点及优缺点总结

select是多路转接的一种它只负责等待可以一次等待多次fd更为重要的是select本身没有数据拷贝的能力拷贝要read、write来完成。
所以select在IO环节中只负责等一旦哪一个文件描述符就绪了那select要有方式来告知上层哪一个文件描述符好了。
然后上层来读取。
select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;程序会停在select这里等待直到被监视的文件描述符有一个或多个发生了状态改变;
nfds因为select可以一次等待多个文件描述符而每一个文件描述符它的本质是数组下标所以多个文件描述符它的数字大小肯定不一样同时多个文件描述符也是不同整数构成的它一定有最大一定有最小而其中第一次参数表示select要监视的多个fd中值最大的fd1
除了第一个参数剩下的四个参数有一个共同特点全都是输入输出型参数也就是说未来是由我们传给select传过去之后OS也要对传入的值做修改然后输出给我们。
timeoutselect一次等待多个fd最后一个参数决定了当select在等多个fd时它具体的等待方式是什么。
timeout设置为nullptr阻塞式。
也就是select一次等待多个df但没有任何一个fd就绪时select只能在底层阻塞这个调用就不返回直到有任何一个就绪了。
timeval结构内第一个变量表示的是秒第二个变量表示的是微秒。
也就是select一次等待多个df但没有任何一个fd就绪时select立马返回。
timeout{50}传给最后一个参数。
表示的是select的调用5s以内阻塞式超过5s非阻塞返回一次。
假设5s内有任何一个fd就绪了select都可以立马返回。
表示超时返回。
假设设置时间是5s5s内阻塞式不返回超过5s没有一个就绪就是超时了。
表示select调用失败了。
比如你今天服务器打开3、4、5这三个描述符你现在只有这三个fd是合法的可是你非要把10或20也管理起来10和20在进程根本没有被打开你还要交给select那select当然就调用失败了。
异常事件就绪在进行读写时可能会发生各种意外比如正在给对方写入对方把文件描述符关了此时我正在向一个已经关闭的客户端写入这个时候在写入时可能出现异常。
所以这三个参数就分别对应就是让select关心的读写异常事件。
可是现在select中除了第一个参数给我多个fd的感受我们好像没有见到有多个fd。
在信号的时候有三种表pending表block表还有handler表其中pending表block表也就是位图结构。
每个比特位表示不同的信号。
二大家会连续。
所以我们采用位图结构表征各个文件描述符。
位图结构一般实现都是采用结构体里面套数组完成。
你想有多大位图自己设置就可以。
如果想让select关心写在定义一个位图结构把文件描述符设置进写集合里。
关心异常也是同样做法。
因为后面参数都是输入输出型参数所以操作系统直接在你传的位图中做修改
所以对同一个参数做修改本质就是让用户和内核之间互相沟通互相知晓对方要的或者关心关心的
如果你想让select既关心一个文件描述符的读又关心写那就定义两种位图把在这个文件描述符分别添加到读文件描述符集写文件描述符集。
那OS就帮我同时关心该文件描述符的读和写了。
所以读、写、异常三个参数位置的不同表示用户和内核分别交互的不同事件。
参数细节现在就说完了。
还有一个问题fd_set是一个位图能之间对fd_set这个位图做任何修改吗
操作系统为了更好支持我们向位图里进行设置查看位图等。
系统给我们配了对应的位图操作接口。
接下来我们写一个select服务器这里我们先只处理读取只获取数据。
写入等到epoll哪里在处理边写边介绍select
先准备一下要用东西下面有些代码是我们以前写tcp服务器已经写过了这里就不在重复说直接用了。
1,SOCKET_ERR,BIND_ERR,LISTEN_ERR
local_time;local_timelocaltime(now);static
time_str[1024];snprintf(time_str,sizeof
local_time-tm_mday,local_time-tm_hour,
logprefix[NUM];snprintf(logprefix,sizeof
%d],level_to_string(level),nowtime,getpid());//char
arg;va_start(arg,format);vsnprintf(logconten,sizeof
logconten,format,arg);std::coutlogprefixlogcontenstd::endl;
这里为了方便我们全部写成静态成员函数了。
后面我们epoll这里在设计一下
error);exit(SOCKET_ERR);}logMessage(NORMAL,
sizeof(local));local.sin_family
htons(port);local.sin_addr.s_addr
error);exit(BIND_ERR);}logMessage(NORMAL,
error);exit(LISTEN_ERR);}logMessage(NORMAL,
inet_ntoa(peer.sin_addr);*clientport
2){usage(argv[0]);exit(USAGG_ERR);}unique_ptrSelectServer
SelectServer(atoi(argv[1])));usl-initServer();usl-start();return
Sock::sock();Sock::Bind(_listensock,
_port);Sock::Listen(_listensock);}void
开始进行服务器的处理逻辑}}~SelectServer(){if
0)close(_listensock);}private:int
};上面服务器的代码都是我们曾经写过的今天写select服务器当然不是这么简单了。
select它是一个只做监听的只做IO中等待的系统调用接口一旦有事件就绪了它会通知我它一次可以等待多个文件描述符可是目前我们面临的第一个尴尬问题是你刚开始的服务器根本没有多个文件描述符刚开始只有一个啊而且还是_listensock套接字。
可是我们也知道_listensock也是套接字而后序所有多出来的套接字本质上都是从_listensock上来的所以select要监管多个套接字的话首先要把_listensock监管起来
因此_listensock首先要交给select那我们要想清楚了select有读事件写事件异常事件并没有任何所谓的监听事件啊但是没问题能交给_listensock的连接就绪事件
读事件就绪因为本质就是对方发的连接请求触发的三次握手也属于客户端向服务器发信息所以认为是读事件就绪
这里也不应该_listensock套接字创建好了直接循环获取accept因为accept函数自己通过_listensock套接字获取连接时没有连接时accpet也在阻塞等。
有连接了才能获取连接然后返回。
所以accpet
我们的想法是当底层连接就绪了你来通知我这个时候我在调用accept。
此时相当于让select帮我负责等而accpet只负责获取连接不会被阻塞。
现在要做的就是先调用select因为我们现在就一个_listensock因此select目前写法是不正确的。
我们接下来慢慢改。
(;;){//我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我int
strerror(errno));break;default://说明有事件就绪了,目前只有一个监听事件就绪了break;}//
}我们先验证这个timeout目前是3s内阻塞超过3s非阻塞返回一次。
截图效果可能不明显。
原因在于timeout传进来它是一个输入输出型参数。
当你输入时是3s3内没有任何时间就绪那么这个timeout时间就在select内部慢慢见到了0s然后回过头在去select的时候这个timeout已经被改过了就变成全0了。
所以此时只能是第一次阻塞等待了剩下都是非阻塞了。
所以正确写法是把timeout放入循环内部保证每一次都对它重新设定。
这样就是没有任何事件就绪时每隔3秒非阻塞返回一次。
同样也可以把timeout改成0此时就是非阻塞了。
并且可以不要timeout直接把最后一个参数设为nullptr此时就是阻塞了。
timeout{3,0};//特定时间阻塞超过特定时间非阻塞返回一次//struct
timeout{0,0};非阻塞式//我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我//int
strerror(errno));break;default://说明有事件就绪了,目前只有一个监听事件就绪了break;}}}下面改一点代码然后用telnet连接一下这个服务器看一下。
将_listensock添加到读文件描述符集合中//struct
我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我//
strerror(errno));break;default://
说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL,
日志在一直打说明我们当前循环的时候select一直在循环。
不是只有一个连接吗怎么会给我这么多就绪消息。
原因在于我们并没有把底层的连接取走所以每一次调用select我们对应的_listensock套接字上面的事件都是就绪的所以每一次都添加关心的都是_listensock套接字所以select每一次都帮我们检测连接_listensock套接字有没有就绪。
因为连接建立成功后断开连接是双方的事。
服务器连连接都没拿上去也就没有办法断开因为没有调用close。
所以依旧告诉连接就绪。
所以我们发现select果然是有把连接就绪事件告诉我们的能力了然后我们也通过select监听到有连接事件到来了因此我们还要获取对应连接。
连接就绪事件是被放在我们传给select的rfds里。
输入时是用户告诉内核你要帮我关心该文件描述符集中那些fd的读事件输出时内核告诉用户该文件描述符集中那些fd读就绪了。
但未来可能有很多fd就绪了,如何判断是_listensock就绪了呢?//
告送我,_listensock就绪了,然后才能执行下面代码string
return;logMessage(NORMAL,accept
[%s:%d],clientip.c_str(),clientport);}
我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我//
strerror(errno));break;default://
说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL,
link...);HandlerEvent(rfds);break;}}
}这一次我们把连接拿上来所以在select就没有新连接了。
也就不会疯狂打印get
不会因为走到这里_listensock已经是就绪的了。
accpet直接读取绝对是有返回的。
得到一个sock套接字后然后我们可以直接进行read/recv吗
显然不能你直接调用read/recv你就能保证底层有数据吗不能建立好连接就是不给你发数据因为我们是单进程你直接调用read/recv就直接阻塞挂起了。
根据原因就是你根本不清楚对应sock读事件是否就绪。
一调可能就阻塞你不清楚那谁清楚整个代码只有select有资格检测事件是否就绪
一般而言编写select服务器要使用select需要程序员自己维护一个保存合法fd的数组!
首先套接字会越来越多你怎么知道这么多fd那些fd是合法的那些是非法的。
其次rfds这个参数是输入输出型的你可能曾经设置过5个fd可能循环一次这些fd全都被清空了那你怎么知道历史上还有那些fd最后这么多fd你怎么保证更新出来的fd最大值是谁
我们未来保存所有文件描述符的类型是fd_set这是Linux内核给我们提供的自定义类型既然是一种类型它必有大小而且大小是固定的
不过这里sizeof求得是字节但fd_set是一个位图结构因此还有乘8才是真实大小。
1024也就是说select服务器能够处理得文件描述符上限是1024个
然后在服务器启动之前未来所有合法fd都在这个数组里面未来要重新设置要关心的读文件描述符集更新最大值都在这个数组找这就决定了在刚开始的时候首先最开始只有一个_listensock套接字先设置进数组。
Sock::sock();Sock::Bind(_listensock,
_port);Sock::Listen(_listensock);_fdarray
}然后启动服务器每一次select之前都要在数组内找到合法fd最大值是多少并且将数组中所有合法fd重新设置到读文件描述符集
defaultfd)//非法,没有被设置的continue;//因为rfds是输入输出型参数,因此每次都要将合法fd,重新添加到读文件描述符集FD_SET(_fdarray[i],
我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我//
strerror(errno));break;default://
说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL,
link...);HandlerEvent(rfds);break;}}
}接下来继续之前未完的事情accpet获取新的sock将新的sock托管给select
将新的sock托管给select的本质其实就是将sock添加到fdarray数组里
但未来可能有很多fd就绪了,如何判断是_listensock就绪了呢?//
告送我,_listensock就绪了,然后才能执行下面代码string
得到一个sock套接字后然后我们可以直接进行read/recv吗?
将新的sock托管给select的本质其实就是将sock添加到fdarray数组里int
defaultfd)continue;elsebreak;}if
fdnum)//处理数组满的情况{logMessage(WARNING,
wait);close(sock);}else//添加到_fdarray数组{_fdarray[i]
}然后这里会从将sock从fdarray数组中交给select
为了看一看托管的文件描述符是不是越来越多了写一个打印函数当把sock添加fdarray数组之后调用一下这个函数看一下
}所以随着连接增多对应的合法fd全部都会添加到_fdarray数组中然后处理完就绪事件之后每次在调用select之前都会把_fdarray中合法fd添加到rfds找到合法fd最大值一起交给select。
可是随着数组中合法fd越来越多select帮我们监管的fd也越来越多了那么事件的总类也变得越来越多了不过我们目前只处理读事件。
并且我们处理读事件函数中也只写了一个_listensock套接字获取accpet获取连接。
这是不够的我们还需要考虑处理正常的IO。
告送我,_listensock就绪了,然后才能执行下面代码string
得到一个sock套接字后然后我们可以直接进行read/recv吗?
将新的sock托管给select的本质其实就是将sock添加到fdarray数组里int
defaultfd)continue;elsebreak;}if
wait);close(sock);}else{_fdarray[i]
sock;}print();logMessage(DEBUG,
rfds))Accepter(_listensock);//处理_listensockelse
}不会被阻塞走到这里一定是sock读事件已经就绪了。
其次这里不仅仅处理一个sock可能多个sock都会进来所以就一个栈上的缓存区去读取绝对是有问题的其次你怎么保证数据一次就读完了呢没有读完是不是要循环读取但是你怎么保证在读的时候不会被阻塞呢并且读完了就是一个完整的请求了吗然后反序列化等等这些问题我们都在epoll哪里处理
//对方关闭了文件描述符{close(sock);//我也关_fdarray[pos]
defaultfd;//不让select关心该sock了logMessage(NORMAL,
quit);return;}else//读取失败{close(sock);_fdarray[pos]
}接下来处理request这里我们在类里在加入一个回调函数然后简单的直接返回就行了
//对方关闭了文件描述符{close(sock);//我也关_fdarray[pos]
defaultfd;//不让select关心该sock了logMessage(NORMAL,
quit);return;}else//读取失败{close(sock);_fdarray[pos]
返回response}接下来把处理结果给给用户返回但是你怎么保证写事件就绪了呢所以这里还要在创建一个fd_set写事件然后把sock添加到这个写文件描述符集在添加到select写这里等到对应sock读事件就绪了才能给用户返回去。
不过那样代码太复杂了所以今天不考虑这么多直接先写。
//对方关闭了文件描述符{close(sock);//我也关_fdarray[pos]
defaultfd;//不让select关心该sock了logMessage(NORMAL,
quit);return;}else//读取失败{close(sock);_fdarray[pos]
response.size());logMessage(DEBUG,
func_tfunctionstring(string);public:SelectServer(func_t
Sock::sock();Sock::Bind(_listensock,
_port);Sock::Listen(_listensock);_fdarray
告送我,_listensock就绪了,然后才能执行下面代码string
得到一个sock套接字后然后我们可以直接进行read/recv吗?
将新的sock托管给select的本质其实就是将sock添加到fdarray数组里int
defaultfd)continue;elsebreak;}if
wait);close(sock);}else{_fdarray[i]
sock;}print();logMessage(DEBUG,
//对方关闭了文件描述符{close(sock);//我也关_fdarray[pos]
defaultfd;//不让select关心该sock了logMessage(NORMAL,
quit);return;}else//读取失败{close(sock);_fdarray[pos]
response.size());logMessage(DEBUG,
rfds))Accepter(_listensock);else
因为rfds是输入输出型参数,因此每次都要将合法fd,重新添加到读文件描述符集FD_SET(_fdarray[i],
我告诉select关心读文件描述符集中的_listensock事件,就绪了之后告诉我//
strerror(errno));break;default://
说明有事件就绪了,目前只有一个监听事件就绪了logMessage(NORMAL,
ready!);HandlerEvent(rfds);break;}}}~SelectServer(){if
select能同时等待的文件fd是有上限的除非重新改内核否则无法解决必须借助第三方数组来维护合法的fdselect的大部分参数都是输入输出型的调用select前要重新设置所有的fd调用之后我们还要更新所有的fd这带来的就是遍历的成本
它怎么知道要等那些文件描述符那些事件呢怎么知道给你返回那个文件描述符的事件就绪了所以它要去查它既然要查就要去限定它去查的范围因为文件描述符就是一个个数组下标我有多个文件描述符表要遍历到哪里呢所以就有了最大值fd1
采用位图用户-内核内核-用户来回的进行数据拷贝有拷贝成本的问题
因为select有如此之多的问题select接口使用不方便每次还要重新手动设置等等。
。
所以我们要有一种新的解决方案这种方案就是多种转接之epoll。
不过在此之前先了解多种转接之poll。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback