一:单进程阻塞
设计流程:
- 创建一个socket,绑定端口bind,监听端口listen
- 进入while循环,阻塞在accept操作上,等待客户端连接进入,进入睡眠状态,直到有新的客户发起connet到服务器,accept函数返回客户端的socket
- 利用fread读取客户端socket当中的数据,收到数据后服务器程序进程处理,然后使用fwrite向客户端发送响应
代码:
<?php class Worker{ //监听socket protected $socket = NULL; //连接事件回调 public $onConnect = NULL; //接收消息事件回调 public $onMessage = NULL; public function __construct($socket_address) { $this->socket=stream_socket_server($socket_address); } public function start() { while (true) { $clientSocket = stream_socket_accept($this->socket); if (!empty($clientSocket) && is_callable($this->onConnect)) { //触发连接事件的回掉 call_user_func($this->onConnect, $clientSocket); } //读取内容 $buffer = fread($clientSocket, 65535); if (!empty($buffer) && is_callable($this->onMessage)) { call_user_func($this->onMessage, $clientSocket, $buffer); } fclose($clientSocket); } } } $worker = new Worker('tcp://0.0.0.0:9810'); $worker->onConnect = function ($args) { echo "新的连接来了.{$args}.PHP_EOL"; }; $worker->onMessage = function ($conn, $message) { var_dump($conn, $message); $content="hello word qwe"; $http_resonse = "HTTP/1.1 200 OK "; $http_resonse .= "Content-Type: text/html;charset=UTF-8 "; $http_resonse .= "Connection: keep-alive "; $http_resonse .= "Server: php socket server "; $http_resonse .= "Content-length: ".strlen($content)." "; $http_resonse .= $content; fwrite($conn, $http_resonse); }; $worker->start();
cli下运行:
浏览器:
缺点:一次只能处理一个连接,不支持多个连接同时处理
二:预派生子进程模式
设计流程:
- 创建一个socket,绑定服务器端口(bind),监听端口(listen)
- 通过
pcntl_fork
函数创建N个子进程 - 一个子进程创建成功后都去阻塞监听新的客户端连接
- 客户端连接时,其中一个子进程被唤醒,处理客户端请求
- 请求完成后,等待主进程回收子进程pcntl_wait
通过调用fork函数来创建子进程,会返回两个pid(主进程id、子进程id)
显示规则:
- 在父进程:fork函数返回子进程id
- 在子进程:fork函数返回0
代码:
<?php class Worker { //监听socket protected $socket = NULL; //连接事件回调 public $onConnect = NULL; //接收消息事件回调 public $onMessage = NULL; public $workerNum = 10; public function __construct($socket_address) { $this->socket = stream_socket_server($socket_address); } //创建子进程 public function fork() { for ($i = 0; $i < $this->workerNum; $i++) { $pid = pcntl_fork(); if ($pid < 0) { exit('创建失败'); } else if ($pid > 0) { //父进程空间,返回子进程id } else { //子进程空间,返回父进程id 0 $this->accept(); } } $status = 0; $pid = pcntl_wait($status); echo "子进程" . $pid . PHP_EOL; } public function accept(){ while (true) { $clientSocket = stream_socket_accept($this->socket); var_dump("正在执行任务的pid为:".posix_getpid()); if (!empty($clientSocket) && is_callable($this->onConnect)) { call_user_func($this->onConnect, $clientSocket); } $buffer = fread($clientSocket, 65535); if (!empty($buffer) && is_callable($this->onMessage)) { call_user_func($this->onMessage, $clientSocket, $buffer); } fclose($clientSocket); } } public function start() { $this->fork(); } } $worker = new Worker('tcp://0.0.0.0:9801'); $worker->onConnect = function ($args) { echo "新的连接来了.{$args}.PHP_EOL"; }; $worker->onMessage = function ($conn, $message) { // var_dump($conn, $message); $content = "hello word qwe"; $http_resonse = "HTTP/1.1 200 OK "; $http_resonse .= "Content-Type: text/html;charset=UTF-8 "; $http_resonse .= "Connection: keep-alive "; $http_resonse .= "Server: php socket server "; $http_resonse .= "Content-length: " . strlen($content) . " "; $http_resonse .= $content; fwrite($conn, $http_resonse); }; $worker->start();
cli执行结果:
缺点:严重依赖进程的数量解决并发问题,一个客户端连接就需要占用一个进程
三:单进程阻塞复用模型
设计流程:
- 保存所有的socket,通过select系统调用,监听socket描述符的可读事件
- socket在内核监控,一旦发现可读,会从内核空间传递给用户空间,通过逻辑判断是服务端socket可读,还是客户端socket可读
- 如果是服务端socket可读,说明有新的客户端建立,将socket保留到监听数组中
- 如果是客户端socket可读,说明当前已经可以去读取客户端发送过来的内容了,读取了内容,响应给客户端
代码:
<?php class Worker { //监听socket protected $socket = NULL; //连接事件回调 public $onConnect = NULL; //接收消息事件回调 public $onMessage = NULL; public $workerNum = 4 ; public $allSocket; public function __construct($socket_address) { $this->socket = stream_socket_server($socket_address); stream_set_blocking($this->socket,0); $this->allSocket[(int)$this->socket]=$this->socket; } public function fork() { // for ($i = 0; $i < $this->workerNum; $i++) { // $pid = pcntl_fork(); // if ($pid < 0) { // exit('创建失败'); // } else if ($pid > 0) { // // } else { $this->accept(); // } // } // $status = 0; // $pid = pcntl_wait($status); // echo "子进程" . $pid . PHP_EOL; } public function accept(){ while (true) { $write =$except =[]; $read= $this->allSocket; stream_select($read,$write,$except,60); foreach($read as $index =>$val){ if ($val == $this->socket){ $clientSocket = stream_socket_accept($this->socket); var_dump(posix_getpid()); if (!empty($clientSocket) && is_callable($this->onConnect)) { call_user_func($this->onConnect, $clientSocket); } $this->allSocket[(int)$clientSocket]=$clientSocket; }else{ $buffer = fread($val, 65535); if (empty($buffer)){ if (feof($val) || is_resource($val)){ fclose($val); unset($this->allSocket[(int)$val]); continue; } } if (!empty($buffer) && is_callable($this->onMessage)) { call_user_func($this->onMessage, $val, $buffer); } } } } } public function start() { $this->fork(); } } $worker = new Worker('tcp://0.0.0.0:9800'); $worker->onConnect = function ($args) { echo "新的连接来了.{$args}.PHP_EOL"; }; $worker->onMessage = function ($conn, $message) { // var_dump($conn, $message); $content = "hello word qwe"; $http_resonse = "HTTP/1.1 200 OK "; $http_resonse .= "Content-Type: text/html;charset=UTF-8 "; $http_resonse .= "Connection: keep-alive "; $http_resonse .= "Server: php socket server "; $http_resonse .= "Content-length: " . strlen($content) . " "; $http_resonse .= $content; fwrite($conn, $http_resonse); }; $worker->start();
缺点:select模式本身缺点(循环遍历处理事件、内核空间传递数据的消耗)、单线程对于大量任务处理乏力