作者:小小号号-- | 来源:互联网 | 2023-09-24 14:58
前段时间在忙着学校的期中考试导致博客跟新暂停了。
根据前面所说的我们能够实现一个服务器端服务多个客户端的请求(每来一个客户端,我的服务器端就开一个进程去作相应的处理。)这种实现并发服务器的方法恢复出很大的代价的(需要大量的运算和内存空间,这是因为没每个进程都具有独立的内存空间),所以我们不会推荐使用。
所以我们接下来要了解的就是,当客户端发送请求时,不在创建进程去一对一的服务。这也就是我们后面(很长一段时间要介绍的)的博客要了解的I/O复用技术。
说通俗一点就是,现在我的服务器端不在一直关注是否有客户端请求连接了,而是把请求连接(还有读写等操作请求) 交给内核来监听,只有相对应的事件发生时,内核才会通知服务器端,然后服务器端来做相应的处理。大家也可以看下面的解释来理解
所以我们就开始我们的 I/O复用之路吧:
select实现并发服务器端
因为 select函数具有很好的移植性,而且也是最具代表性的实现复用服务器端的方法,锁以我们就先介绍它。
select函数的功能和调用顺序:
使用 select 函数时,可以将多个文件描述符集中到一起监听.
- 是否存在套接字接收数据?
- 无需阻塞传输数据的套接字有哪些?
- 那些套接字发生了异常?
select 函数使用方法与我们平时使用的函数有很大的区别,他比较难使用,所以博主现在这里给出 select 函数
#include
#includeint select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset,const struct timeval *timeout);参数:第一个参数: int maxfd ----> 监控对象文件描述符的最大数量,也就是监控的文件描述符集里最大文件描述符加1书上写的是监视对象文件描述符数量(我觉得描述的不是很准确)第二个参数: fd_set *readset ----> 监控是否有读数据到达文件描述符集合(fd_set 定义的集合),传入传出参数书上: 将所有关注“是否存在待读取数据”的文件描述符注册到 fd_set型变量(这里理解为集合会好一点),并传递其地址值。第三个参数:fd_set *writeset----> 监控写数据到达文件描述符集合,传入传出参数第四个参数:fd_set *exceptset----> 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数第五个参数:const struct timeval *timeout---->定时阻塞的监控时间,分成3种情况1.NULL,永远阻塞2.设置timeval,等待固定时间3.设置timeval里时间均为0,检查描述字后立即返回,轮询我们来看一看第五个参数的 结构体长什么样子struct timeval {long tv_sec; long tv_usec; };
返回值:(返回值也比较复杂,会引出另外四个函数,博主会解释的)发生错误:-1超时:0因发生时间而返回时,返回大于 0 的值,这个值就是满足我们监听的事件个数!!!(虽然返回了有几个事件满足监听,但是我们却不知道是哪几个事件满足,所以就会引出后面的四个函数来判断是那些事件满足)。
如果只看文字那么一定是非常枯燥的,所以博主再讲解后面更枯燥的内容之前先对比下我们这篇博客到底要做的是什么吧,也正好缓解下刚刚看select函数复杂的心情。
使用 select 函数之前我们是这样来处里多个客户端发送请求的:
相信应该明白了吧,有了 select函数之后我们就能让内核去监听是否有请求,如果有的话就通知我们的服务器去做相关处理即可。
我们假设客户端有三个请求发送过来,根据我们上面所讲到的,select会返回3,但是你知道是哪三个发送的请求吗?这就感到疑惑了对吧?也就是为了解决我们的疑问,所以我们接下来来介绍: 调用select函数之后如何产看结果的技术:
void FD_ZERO(fd_set *set); void FD_SET(int fd, fd_set *set); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set);
大家可以根据下面的图片来理解,因为解释的很清楚了,博主就不在自己画图
查阅书中的代码:
select函数的用法其实就差不多是这种用用法了。
- 设置 集合
- 初始化
- 添加监听事件
- 判断select的返回值
- 判断我们监听的事件是否存在