思路

要实现多进程,有两种思路可以考虑:

  1. 每接入一个客户端,fork出一个子进程来处理响应,处理完毕后关闭子进程。
  2. 主程序开启N个子进程,在没有请求的时候子进程进入轮询睡眠状态,当有客户端连接之后由子进程去接入处理,处理完毕继续睡眠。
    这两种方式,先说第一种。进程数不需要代码控制,在负载较低的情况下比较灵活,但是对于系统来说,开启子进程也不是件小事,对于系统的资源消耗很大,频繁的开启关闭大量进程会严重拖累服务器的运行,实际中这种方式是不能使用的。第二种方式的效率很高,这种方式呗叫做Leader-Follower模型,进程可以复用,占用资源很低。Apache、PHP-FPM都是基于此模型来实现的。缺点就是子进程数量有限,不好控制,如果同时开启大量进程对于系统来说同样是巨大的消耗。

下面用代码来简单说明下两种模型是怎么实现的。避免篇幅过长,我贴上关键部分的代码,在上篇文章的基础上进行修改而成。

方式一

// continuously to handle the client's request
while (true) {
  $clientConnect = socket_accept($socketServer);  // connect the client

  if ($clientConnect) {
      socket_getpeername($clientConnect, $addr, $port);
      $output->writeln("client $addr connect with port $port..");

      if(pcntl_fork() == 0){ //重点在这,fork子进程来处理!
          while (true) {
              $data = socket_read($clientConnect, 1024);  // read data from client(1024 bytes one time)
              if (!empty($data)) {
                   $output->writeln('receive data from client : ' . $data);
                   $data = strtoupper($data);  // handle the request data
                   socket_write($clientConnect, $data);  // respond the client
              } else {
                   $output->writeln("client $addr:$port disconnect !");
                   socket_close($clientConnect);   // close the connection
                   break;
              }
           }
           die();
       }
     }
}

我本地测试同时开启了三个client 接入,效果如果:

>bin/console Mutilserver:start
client 127.0.0.1 connect with port 63552..
receive data from client : Hello World!
client 127.0.0.1 connect with port 63553..
receive data from client : Hello World!
client 127.0.0.1 connect with port 63554..
receive data from client : Hello World!
client 127.0.0.1:63552 disconnect !
client 127.0.0.1:63553 disconnect !
client 127.0.0.1:63554 disconnect !

方式二

// continuously to handle the client's request
for ($i = 0; $i < 2; $i++) {
    if (pcntl_fork() == 0) { //重点在这,fork子进程来处理!
        while(true){
             $clientConnect = socket_accept($socketServer);  // connect the client
             if ($clientConnect) {
                 socket_getpeername($clientConnect, $addr, $port);
                 $output->writeln("client $addr connect with port $port..");
                 while(true){
                      $data = socket_read($clientConnect, 1024);  // read data from client(1024 bytes one time)
                      if (!empty($data)) {
                          $output->writeln('receive data from client : ' . $data);
                          $data = strtoupper($data);  // handle the request data
                          socket_write($clientConnect, $data);  // respond the client
                      } else {
                           $output->writeln("client $addr:$port disconnect !");
                           socket_close($clientConnect);   // close the connection
                           break;
                      }
                  }
             }
             sleep(10);
        }
    }
}

同样开启三个client接入,输出如下:

>php bin/console Mutilserver2:start
client 127.0.0.1 connect with port 63677..
receive data from client : Hello World!
client 127.0.0.1 connect with port 63678..
receive data from client : Hello World!
client 127.0.0.1:63677 disconnect !
client 127.0.0.1 connect with port 63679..
receive data from client : Hello World!
client 127.0.0.1:63679 disconnect !
client 127.0.0.1:63678 disconnect !

从输出结果来看,看不出区别,但是第二种方式在主进程运行之后子进程并没有退出,依旧可以开启client继续接入直到你主动关闭子进程。

>ps aux | grep php
skymei 24120  0.0  0.0 4337716 1536 s000  S+  5:25PM  0:00.00 php bin/console Mutilserver2:start
skymei 24119  0.0  0.0 4337716 1536 s000  S+  5:25PM  0:00.00 php bin/console Mutilserver2:start
skymei 24115  0.0  0.1 4337716 22376 s000 S+  5:25PM  0:00.35 php bin/console Mutilserver2:start

在这两种方式里都用到了pcntl_fork() 函数,这个是由php的扩展pcntl提供的,在linux、unix平台上可以在主进程中开启子进程,具体的使用这边就不赘述了。虽然以上两种方式都实现了同时处理多客户端请求,但是缺点也是很明显,pcntl本身用的人就很少,会在线上使用的基本没有,而且僵尸进程,子进程异常重启,主进程崩溃等情况也都需要花费精力去维护,消耗系统资源也是个问题。所以推荐看下篇文章中的多路复用方式,更为高效稳定!

参考文章

PHP并发IO编程之路