[原文地址:https://blog.ti-node.com/blog...]在中依次讲解了三个递进的服务器:只有A服务器可以为一个客户端提供服务的服务器可以使用fork为多个客户端提供服务可以使用类似的预分叉派生过程为多个客户端提供服务的服务器。其实这个模型最大的问题是需要根据实际业务来估算进程数。仍然需要大量的过程来解决这个问题。CPU可能浪费在进程间的切换上,可能会出现惊群现象(简单理解就是100个进程在等待一个client连接,一个client来了但是所有进程都被唤醒了,但是最后只有一个进程为这个client服务,剩下的99个就白做了),那么,有没有一种方案可以让少量的进程为很多client服务呢?答案就是中提到的“IO多路复用”。多路复用是指多个客户端连接到socket,多路复用是指几个进程的多路复用。多路复用本身仍然属于同步通信方式,但显示的结果看起来是异步的,这一点值得注意。目前常用的多路复用方案有3种,分别是:select,最早的方案poll,是select,epoll的升级版,目前的最终方案版本。解决c10k问题的大侠今天说的是select。这个东西本身就是一个linux系统调用。在Linux中,万物皆文件,socket也不例外。每当Linux打开一个文件系统时,它会返回一个与该文件对应的标记,称为文件描述符。文件描述符是一个非负整数。当文件描述数达到最大时,会回到十进制重新开始(题外话:按照传统,标准输入一般为0,标准输出为1,标准错误为2).对文件的读写操作就是使用对文件描述符的读写操作。一个进程可以操作的文件描述符的数量是有限的,不同的系统有不同的数量。在Linux中,可以通过调整ulimit来调整控制。首先用一个简单的例子来说明select的功能和作用。双11来了,你给少林足球队买了很多球鞋,有10个快递员给你送,然后你不停地给这10个快递员打电话,你觉得有点累。阿梅很爱你,所以阿梅说:“这件事你不用担心,你专心修炼金刚腿就好,等快递来了,我会告诉你的。”当其中一位快递员来的时候做完之后,阿妹会叫你:“下来,有快递!”不过这个阿妹比较粗心,她不会告诉你快递是哪双鞋,只告诉你快递到了。因此,您只能依次查看所有快递单的状态,确认签收了哪一个。上面的例子是结合术语推导的,你是服务端软件,阿妹是select,10个快递员就是10个客户端(也就是10个connectionsocketfds)。阿妹负责为你管理这10个connectionsocketfds。当任何一个fd响应,即可以读取数据或发送数据时,Amei(select)会告诉你有fd可以读写。但是Amei(select)不会告诉你哪个fd是可读可写的,所以你要循环遍历所有的fd,看看哪个fd是可读可写的。到了机械记忆的时候了:开始select的时候,需要添加三组不同的socketfds作为select的参数。传统上,这组fds称为fd_set,三组fd_sets依次为可读集,可写集合,异常集合。三组fd_sets由系统内核维护。每当select监控和管理的三个fd_set中的任何一个可读可写或发生异常时,都会通知调用者。调用者调用select后,调用者会被select阻塞,等待可读可写事件等事件的发生。一旦出现可读、可写或异常,需要将三个fd_set全部从内核态复制到用户态,然后调用者通过轮询的方式遍历所有fd,取出可读、可写或异常的fd并进行相应的操作.如果一个调用者忽略了一个可操作的fd,那么下次剩下的fd可操作时,上次没有被调用者处理的fd会继续再次返回给调用者,也就是说在遍历这个fd时,ignoredfd在调用者忽略它之前仍然是可读可写的。以上是我个人的理解和总结,如有错误请大家指出,希望不要误导孩子。下面用一个php代码示例来操作一波select系统调用。在php中,可以通过stream_select或者socket_select来操作select系统调用。下面对socket_select进行代码演示:0){//判断listen_socket是否发生变化,如果有customerIf(in_array($listen_socket,$read)){//添加clientsocket到client数组$client_socket=socket_accept($监听套接字);$client[]=$client_socket;//然后listen_socket从read中移除$key=array_search($listen_socket,$read);取消设置($read[$key]);}//检查removelisten_socket中是否还有client_socketif(count($read)>0){$msg='helloworld';foreach($readas$socket_item){//从可读fd中读取数据内容,然后发送给其他客户端$content=socket_read($socket_item,2048);//循环遍历client数组,将内容发送给所有其他clientforeach($clientas$client_socket){//因为client数组包含listen_socket和当前发送者自己的socket,所以需要排除两者if($client_socket!=$listen_socket&&$client_socket!=$socket_item){socket_write($client_socket,$content,strlen($content));}}}}}//当select没有监听available操作fd时,直接继续进入下一个循环else{continue;}}保存文件为server.php,然后执行phpserver.php运行服务,同时打开三个终端,执行telnet127.0.0.19999,然后在输入“IamDOG!”在任意一个telnet终端,再看看另外两个telnet窗口,是不是感觉很牛逼?在不完整的屏幕截图下方:仍然没有意识到问题所在?如果我们看到有3个telnet客户端连接到服务器,可以互相发送消息,但是我们只用一个进程为三个客户端服务,你要的话可以多开几个telnet,但是服务器只需要一个进程即可完成,这就是IO多路复用的用武之地!最后重点分析一些socket_select函数。我们看一下这个函数的原型:intsocket_select(array&$read,array&$write,array&$except,int$tv_sec[,int$tv_usec=0])值得注意的是三者前面有个&$read、$write、$except的参数,也就是说这三个参数都是引用类型,可以改写。在上面的代码案例中,第一次执行服务端代码时,我们需要监听所有所有的fds都放在read数组中,但是当系统经过select的时候,这个数组的内容会发生变化,从原来所有的readfds变成只有可读的readfds,这就是为什么声明它是一个client数组,并且然后声明一个read数组,然后read=client.如果我们直接使用client作为socket_select的参数,那么client数组的内容就会被修改。如果client数组中保存了5个用户,只有1个是可读的,socket_select后,client中只剩下可读的fd,其他4个client会丢失。这时候客户端的表现就是connect莫名其妙的发生了,丢失了。【原文地址:https://blog.ti-node.com/blog...】