1.select函数解析
上图是manpage中对select函数的声明,对各个参数已经返回值逐一分析:
在分析之前需明确一个概念:传入传出参数:即具有该性质的参数,在一定时刻作为传入。一定时刻接收函数的返回进行传出。
- int nfds: 监听的所有文件描述符中,文件描述符的最大值+1.
- fd_set *readfds;
- fd_set *writefds;
- fd_set *exceptfds 上述三个参数都是传入传出参数,类型的fd_set的指针,fd_set代表一个文件描述符集合。而三个参数根据名称表示意义则是对应要监听的类型(发生对应操作将做出反应),即监听读操作,写操作,其他操作。
- 上面三个参数作为传入时:传入的是要监听对应操作的文件描述符集合,传出时是有对应操作发生的文件描述符集合(重要)这里需注意是select的缺陷之一(编程时需小心)
- struct timeval *timeout指定监听的超时时间
- 返回值:select里所有集合中有信号发生的文件描述符的总个数,0表示没有,-1表示出错
2.select函数使用方法及场景
由于select的参数中含有文件描述符集,所以先介绍一些对文件描述符集的基本操作函数
函数1:将fd对应的文件描述符从监听的文件描述符集set中剔除出去,用于监听客户端连接断开。
函数2:判断对应的fd是否还在文件描述符集set中。
函数3:将对应的fd置入到文件描述符集set中去。
函数4:将文件描述符集set给清空。
使用场景:
当我们的服务器端需要对多个客户端的请求做出响应时,通常会有多线程(thread),多进程(fork)等方式对多个客户端的请求进行响应,但这些操作对资源的消耗以及响应的时间是较高的。
我们可以通过使用select对相应的文件描述符进行相应事件监听,当监听事件发生时通过我们boss(就处理程序做出相应处理),这里的select类似于实现一个小秘书的职责。
通过上面思路可做如下的思路图:
下面我将通过伪代码将整体处理思路进行程序,具体情况下的具体处理方式具体分析
以下代码需了解服务器端tcp通信的初始化代码结构。
linkfd = socket()
bind()
listen()
fd_set rset,allset;
FD_ZERO(&allset);
FD_SET(linkfd,&allset);
/*
这里定义一个allset一个rset的原因是为了弥补select函数的缺陷
其文件描述符集做为传入传出参数,只放在一片空间,响应文件描述符会将原文件描述符集覆盖,可能导
致select的集合不完全,所以这里定义了一个rset作为传入参数使用,定义allset作为接收传出参数使
用,保证文件描述符集的完整性
*/
while(1)
{
//每次循环回来需要找到allset中文件描述符最大值
rset = allset;
ret = select(最大的文件描述符+1,&rset,null,null,null);
if(ret>0)
{
if(FD_ISSET(linkfd,&rset))//连接事件发生
{
sendfd = accept();
FD_SET(sendfd,&allset); //allset中一直存放着最完整的文件描述符集合
}
for(i=linkfd+1;i<=最大文件描述符;i++)
{
if(FD_ISSET(I,&allset))//传输数据的事件发生
{
//事件的处理代码
}
}
}
}
3.select函数使用注意事项(优缺点)
优点:select是一个非常古老的函数,其设计也有相应的缺陷,但是其能沿用至今的关键因素就是在于其可移植性高,可跨平台使用,不像后面的epoll虽然设计精妙但是只能在linux下使用。
缺点:
1.采用的是轮询机制,对所有监听文件符都进行查询,不同于epoll的通过回调函数将相应的文件描述符放入相应集合的方式,所以随着监听数量增加,select的效率将会线性下降。
2.因为单个进程打开的文件描述符fd受限为1024,所以其转接的客户端数量有限。
易错点:重点
问题提出:由于函数的2-4个参数(也就是监听的文件描述符集)的传入传出特点,这表明我们监听的文件描述符集不能一直保存,进行一轮监听后,该参数就会被响应的文件描述符集给覆盖,此时如果不对文件描述符集进行操作再进行下一轮监听的话,会导致监听的文件描述符集不完整。
举例:当有一个客户端连接上服务器时,此时服务器端需要监听的文件描述符就包括一个连接文件描述符和一个与客户端通信的文件描述符,若此次监听无客户端连接但有已经连接的客户端通信,那么连接文件描述符就会在这一次监听中由于参数的传入传出性质被剔除掉,从此无法再接收新的客户端连接。
解决办法:使用两个文件描述符集,a文件描述符集用来保存监听(可能会在监听过程中增加)的所有文件描述符集,另一个b文件描述符集在每次监听前获得a监听文件描述符集传入,并在监听后将新响应的文件描述符加入到第一个完整的文件描述符集a中去,下一轮再次获得a的集合进行监听,此时就不会出现监听文件描述符丢失的情况。
4.总结
使用select编程时一定注意chapter3中的易错点问题,本文可以提高对网络编程的理解以及应付面试,如果有朋友对想要一个具体实现的demo(比较简单)可以私信我(无偿)。最后,如果博客中有错误欢迎大家指正一起进步。