进程阻塞多路复用的web服务器。IO多路复用是指一旦内核发现某个进程指定的一个或多个IO条件准备好被读取,就会通知该进程。目前支持I/O多路复用的有select、poll、epoll,I/O多路复用Multiplexing是一种机制,通过它一个进程可以监视多个描述符。一旦一个描述符就绪(通常是读或写就绪),它就可以通知程序执行相应的读写操作。IO多路复用适用场合:当客户端处理多个描述符(通常是交互式输入和网络套接字)时,必须使用I/O多路复用。虽然有可能,但很少见,客户端同时处理多个套接字。如果TCP服务器同时处理侦听套接字和连接套接字,则通常使用I/O多路复用。如果一个服务器需要同时处理TCP和UDP,一般会使用I/O多路复用。如果一个服务器处理多种服务或多种协议,一般会使用I/O多路复用。与多进程、多线程技术相比,I/O多路复用技术最大的优点是系统开销小,系统不需要创建进程/线程,也不需要维护这些进程/线程,从而大大降低了系统开销。选择描述监视并等待多个文件描述符上的属性更改(可读、可写或错误异常)。select函数监听的文件描述符分为三类,分别是writefds、readfds和exceptfds。调用后select会阻塞,直到某个描述符就绪(数据可读、可写或发生错误异常)或超时(timeout指定等待时间)函数才会返回。select()函数返回时,可以通过遍历fdset找到就绪描述符,最大描述符不能超过1024poll描述poll的机制和select类似,本质上和select没有太大区别,多个描述符的管理也是根据描述符的状态进行轮询处理,但是poll对文件描述符的最大个数没有限制。poll和select同样的缺点是,包含大量文件描述符的数组是作为一个整体在用户态和内核地址空间之间复制的,不管这些文件描述符是否就绪,其开销随着数量的增加而增加文件描述符同时线性增加。select和pollselect/poll的问题很明显,他们需要循环检测连接上是否有事件。如果服务器有百万个连接,某一时刻只有一个连接向服务器发送数据,那么select/poll需要循环100万次,只有一次命中,剩下的999999次都是无效的,CPU资源白白浪费了。epoll说明epoll是在2.6内核中提出的,是之前select和poll的增强版。相比select和poll,epoll更灵活,没有描述符限制,不需要轮询。epoll使用一个文件描述符来管理多个描述符,并将用户相关文件描述符的事件存储到内核的一个事件表中。简单的说,当一个连接有I/O流事件时,epoll会告诉进程哪个连接有I/O流事件,然后进程去处理这个事件。网络服务器单进程阻塞多路复用网络服务器,如下图,描述服务监听流程如上1.保存所有socket,通过select系统调用监听socket描述符的可读事件2.selectwill在内核空间进行监听,一旦发现socket是可读的,就会从内核空间传递给用户空间。在用户空间,从逻辑上判断是serversocket可读还是clientsocket可读。3.如果serversocket是可读的,说明建立了一个新的client。将套接字保留在侦听数组中。4、如果客户端的socket是可读的,说明客户端发送的内容当前是可以读取的,读取内容,然后响应给客户端。缺点:1、select模式本身的缺点(1、循环遍历处理事件,2、内核空间传输数据的消耗)2、单个进程对于大量任务的弱点。代码实现classWorker{//监听socketprotected$socket=NULL;//连接事件回调public$onConnect=NULL;//接收消息事件回调public$onMessage=NULL;公共$workerNum=4;//子进程数public$allSocket;//存储所有socketspublicfunction__construct($socket_address){//监听地址+端口$this->socket=stream_socket_server($socket_address);stream_set_blocking($this->socket,0);//设置非阻塞$this->allSocket[(int)$this->socket]=$this->socket;}publicfunctionstart(){//获取配置文件$this->fork();}publicfunctionfork(){$this->accept();//子进程负责接收客户端请求}publicfunctionaccept(){//创建多个子进程阻塞接收服务器socketwhile(true){$write=$except=[];//需要监听socket$read=$this->allSocket;//谁改变了状态stream_select($read,$write,$except,60);//如何区分服务端和客户端foreach($readas$index=>$val){//当前变化的是服务端,有连接进入if($val===$this->socket){$clientSocket=stream_socket_accept($this->套接字);//阻塞监听器//触发事件的连接回调}$this->allSocket[(int)$clientSocket]=$clientSocket;}else{//从连接中读取客户端的内容$buffer=fread($val,1024);//如果数据为空,或者为false,不是资源类型if(empty($buffer)){if(feof($val)||!is_resource($val)){//触发关闭事件fclose($val);取消设置($this->allSocket[(int)$val]);继续;}}//正常读取数据,触发消息接收事件,响应内容}}}}}}}$worker=newWorker('tcp://0.0.0.0:9805');//连接事件$worker->onConnect=function($fd){//echo'连接事件触发器',(int)$fd,PHP_EOL;};//接收消息$worker->onMessage=function($conn,$message){//在事件回调中写业务逻辑$content="replymessage";$http_resonse="HTTP/1.1200OK\r\n";$http_resonse.="内容类型:text/html;charset=UTF-8\r\n";$http_resonse.="连接:保持活动\r\n";//保持连接$http_resonse.="Server:phpsocketserver\r\n";$http_resonse.="内容长度:".strlen($content)."\r\n\r\n";$http_resonse.=$内容;fwrite($conn,$http_resonse);};$工人->st艺术();//启动函数stream_socket_server在PHP中提供了一个非常方便的一次性创建、绑定端口、监听端口的功能0非阻塞,1阻塞stream_socket_accept(resource$server_socket\[,float$timeout=ini_get("default_socket_timeout")[,string&$peername]]):资源接受由stream_socket_server()创建的套接字连接
