select模型:
说的通俗一点就是各个客户端连接的文件描述符也就是套接字,
都被放到了一个集合中,
调用select函数之后会一直监视这些文件描述符中有哪些可读,
如果有可读的描述符那么我们的工作进程就去读取资源。
select
刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路复用( IO multiplexing)
- 信号驱动 I/O( signal driven IO)
- 异步 I/O(asynchronous IO)
注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。
函数原型:
int socket_select (array &$read ,array &$write ,array &$except ,int $tv_sec [,int $tv_usec= 0 ])
作用说明:用于确定一个或多个套接字的状态,对每一个套接字,调用者可查询它的可读性、可写性及错误状态信息
参数说明:
read: 指向一组等待可读性检查的套接字
write: 指向一组等待可写性检查的套接字
except: 指向一组等待错误检查的套接字
tv_sec: 用来设置 select() 的等待时间,秒
tv_usec: 用来设置 select() 的等待时间,微妙
这里注意一下,如果 tv_sec 设置为0,则 socket_select 立即返回,也就是非阻塞的。如果 tv_sec 设置为 null ,则 socket_select 将一直阻塞到有套接字满足条件。
下面通过代码代码来简单举例:
poll模型:
poll 和 select 的实现非常类似,本质上的区别就是存放 fd 集合的数据结构不一样。select 在一个进程内可以维持最多 1024 个连接,poll 在此基础上做了加强,可以维持任意数量的连接。
但 select 和 poll 方式有一个很大的问题就是,我们不难看出来 select 是通过轮训的方式来查找是否可读或者可写,打个比方,如果同时有100万个连接都没有断开,而只有一个客户端发送了数据,所以这里它还是需要循环这么多次,造成资源浪费。
所以后来出现了 epoll系统调用。
epoll模型:
epoll 是 select 和 poll 的增强版,epoll 同 poll 一样,文件描述符数量无限制。
epoll是基于内核的反射机制,在有活跃的 socket 时,系统会调用我们提前设置的回调函数。而 poll 和 select 都是遍历。
但是也并不是所有情况下 epoll 都比 select/poll 好,比如在如下场景:
在大多数客户端都很活跃的情况下,系统会把所有的回调函数都唤醒,所以会导致负载较高。既然要处理这么多的连接,那倒不如 select 遍历简单有效。
在 PHP 中我们可以使用 libevet 拓展来实现 epoll。
libevent 是一个用C语言写的,基于事件驱动的高性能网络库。支持多种 I/O 多路复用技术,epoll、 poll、 dev/poll、 select 和 kqueue 等。 libevent 同时为文件描述符、信号、超时设定等事件提供了监听回调。所以这种编程方式也可以说是事件编程。