PHP Swoole 安装


网络通信引擎 异步非堵塞IO场景
源码  //www.php.net/downloads.php
https://www.php.net/distributions/php-7.4.6.tar.gz


tar -xzvf  php-7.4.6.tar.gz # 解压命令
./configure --prefix=/home/study/php # 安装至某 路径
make # 编译
make install # 安装
执行文件放在 bin 目录
php -m  # 查看 PHP 扩展
PHP执行命令
alias 命令=命令的绝对路径
vim /.bash_profile
alias php=/home/work/soft/php/bin/php # 添加
source /.bash_profile # 注意

source FileName
在当前bash环境下读取并执行FileName中的命令
用于重新执行刚修改的初始化文档 如 .bash_profile 和 .profile等等
注 该命令通常用命令 . 来替代
如 source /etc/profile 与 . /etc/profile是等效的
php -i | grep php.ini # 查找PHP的配置文件

Swoole源码编译

swoole源码  //gitee.com/swoole/swoole.git
phpize 用来扩展php模块  通过phpize 建立php 外挂模块 解决没有configure问题
/usr/local/php/bin/phpize # 在需要执行的目录执行这行代码
./configure --with-php-config=/usr/local/php/bin/php-config   
make
make install
在PHP的扩展目录中 swoole.so 文件

PECL
PECL 发布时间晚于 Github 发布时间
Swoole 项目已收录到 PHP 官方扩展库,除了手工下载编译外,还可以通过 PHP 官方提供的 pecl 命令,一键下载安装
pecl install swoole
No releases for package "pecl/swoole" exist
install failed
地址 //pecl.php.net/package/swoole
解决方案:
1.更新channel
$ pecl channel-update https://pecl.php.net/channel.xml
Update of Channel "pecl.php.net" succeeded
2.清除pear缓存
$ pear clear-cache
reading directory /var/cache/pear
96 cache entries cleared
$ pear update-channels
Updating channel "doc.php.net"
Update of Channel "doc.php.net" succeeded
Updating channel "pear.php.net"
Update of Channel "pear.php.net" succeeded
Updating channel "pecl.php.net"
Channel "pecl.php.net" is up to date
$ pear upgrade
重新执行
pecl install https://pecl.php.net/get/swoole-4.5.1.tgz
enable sockets supports? [no] : yes
enable openssl support? [no] : yes
enable http2 support? [no] : yes
enable mysqlnd support? [no] : yes



PHP7支持swoole

php.ini文件添加 extension=swoole.so
查看是否添加成功 php -m
在swoole/examples/server下执行php echo.php
查看是否执行端口 9501
netstat -anp|grep 9501

网络通信引擎  

TCP服务&TCP客户端
TCP服务
Swoole 创建TCP服务器 | 创建UDP服务器
//创建Server对象 监听 127.0.0.1:9501 端口
$serv = new swoole_server("127.0.0.1", 9501);//swoole_server->set函数用于设置swoole_server运行时的各项参数
$serv->set(['worker_num' => 6 , // worker进程数 cpu 1-4倍
  'max_request' => 10000,
]);

$serv->on('connect', function ($serv, $fd, $reactor_id) {  echo "Client: {$reactor_id} - {$fd}-Connect.\n"; });

$serv->on('receive', function ($serv, $fd, $reactor_id, $data) {   $serv->send($fd, "Server: {$reactor_id} - {$fd}".$data); });
$serv->on('close', function ($serv, $fd) {//监听连接关闭事件
  echo "Client: Close.\n";
});
$serv->start();//启动服务器
测试tcp服务器方法
netstat -anp | grep 9501
通过telnet方式登录远程主机 telnet 127.0.0.1 9501
tcp客户端脚本 查看当前worker进程数 ps -aft | grep tcp_server.php
Tips 为了保证程序执行的完整性 当修改tcp服务器脚本后最好设置平滑重启worker进程

TCP客户端

<?php // 连接 swoole tcp 服务
$client = new swoole_client(SWOOLE_SOCK_TCP);
if(!$client->connect("127.0.0.1", 9501)) {  echo "连接失败";  exit; }
// php cli常量
fwrite(STDOUT, "请输入消息:");
$msg = trim(fgets(STDIN));
$client->send($msg);// 发送消息给 tcp server服务器
$result = $client->recv();// 接受来自server 的数据
echo $result;

HTTP服务

$http = new swoole_http_server("0.0.0.0", 8811);//添加测试一 获取参数并打印出来
//$http->on('request', function ($request, $response) {
//    $response->cookie("singwa",'xsssss', time() + 1800);
//    $response->end('sss'.json_encode($request->get));
//});

$http->set(['enable_static_handler' => true,
      'document_root' => "/home/work/hdtocs/swoole_mooc/data",
  ]
);
$http->on('request', function($request, $response) {//print_r($request->get);
  $content = [
      'date:' => date("Ymd H:i:s"),
      'get:' => $request->get,
      'post:' => $request->post,
      'header:' => $request->header,
  ];
  swoole_async_writefile(__DIR__."/access.log", json_encode($content).PHP_EOL, function($filename){      // todo
  }, FILE_APPEND);
  $response->cookie("singwa", "xsssss", time() + 1800);
  $response->end("sss". json_encode($request->get));
});
$http->start();

WebSocket服务

WebSocket协议 基于TCP 新的网络协议 实现浏览器与服务器 全双工(full-duplex)通信 允许服务器主动发送信息给客户端
为什么需要WebSocket
HTTP 通信只能由客户端发起,WebSocket特点  建立在TCP协议之上 性能开销小 通信高效 
客户端 与任意服务器通信 协议标识符ws  wss持久化网络通信协议

Swoole服务端实现

面向过程  procedure_ws_server.php
$server = new swoole_websocket_server("0.0.0.0", 9912);//配置静态文件根目录 可选
$server->set(['enable_static_handler' => true,
      'document_root' => "/home/wwwroot/server.com/public/",
  ]
);//监听websocket连接打开事件
$server->on('open', 'onOpen');
function onOpen($server, $request) {
  print_r($request->fd);
}// 监听ws消息事件
$server->on('message', function (swoole_websocket_server $server, $frame) {
  echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
  $server->push($frame->fd, "singwa-push-secesss");
});
$server->on('close', function ($ser, $fd) {
  echo "client {$fd} closed\n";
});
$server->start();

SwooleWebSocket服务优化

基础类库 面向对象 object_ws_server.php
class Ws {
  CONST HOST = "0.0.0.0";
  CONST PORT = 9912;
  public $ws = null;
  public function __construct() {
      $this->ws = new swoole_websocket_server(self::HOST, self::PORT);  //配置静态文件根目录 可选
      $this->ws->set( ['enable_static_handler' => true,
              'document_root' => "/home/wwwroot/server.com/public/",
          ]
      );
      $this->ws->on("open", [$this, 'onOpen']);
      $this->ws->on("message", [$this, 'onMessage']);
      $this->ws->on("close", [$this, 'onClose']);
      $this->ws->start();
  }
 
  public function onOpen($ws, $request) {
      print_r($request->fd);
  }
 
  public function onMessage($ws, $frame) {
      echo "ser-push-message:{$frame->data}\n";
      $ws->push($frame->fd, "server-push:".date("Y-m-d H:i:s"));
  }
 
  public function onClose($ws, $fd) {
      echo "clientid:{$fd}\n";
  }
}
$obj = new Ws();

Swoole客户端实现

ws_client.html
<!DOCTYPE html><html><head><meta charset="UTF-8"><title></title></head>
<body>
<h1>swoole-ws测试</h1>
<script>
  var wsUrl = "ws://120.77.206.215:9912";
  var websocket = new WebSocket(wsUrl);  //实例对象的onopen属性
  websocket.onopen = function(evt) {
    websocket.send("hello-sinwa");
    console.log("conected-swoole-success");
  }
  // 实例化 onmessage
  websocket.onmessage = function(evt) {
    console.log("ws-server-return-data:" + evt.data);
  }
  //onclose
  websocket.onclose = function(evt) {
    console.log("close");
  }
  //onerror
  websocket.onerror = function(evt, e) {
    console.log("error:" + evt.data);
  }
</script>
</body>
</html>

WebSocket 静态文件目录  通过HTTP服务测试

Swoole异步Task任务使用

执行耗时的操作 发送邮件 广播等
投递异步任务之后程序会 继续往下执行 不 等待任务执行完  继续向下执行
class Ws {
  CONST HOST = "0.0.0.0";
  CONST PORT = 9912;
  public $ws = null;
  public function __construct() {
      $this->ws = new swoole_websocket_server(self::HOST, self::PORT);
      $this->ws->set(  [ 'worker_num' => 2, 'task_worker_num' => 2,         ]
      );   //注册Server的事件回调函数
      $this->ws->on("open", [$this, 'onOpen']);
      $this->ws->on("message", [$this, 'onMessage']);
      $this->ws->on("task", [$this, 'onTask']);
      $this->ws->on("finish", [$this, 'onFinish']);
      $this->ws->on("close", [$this, 'onClose']);
      $this->ws->start();
  }
 
  public function onOpen($ws, $request) {
      var_dump($request->fd);
  }
 
  public function onMessage($ws, $frame) {
      echo "ser-push-message:{$frame->data}\n";
      // todo 10s
      $data = [
          'task' => 1,
          'fd' => $frame->fd,
      ];
      //投递异步任务
      //注意 程序会继续往下执行 不会等待任务执行完后再继续向下执行
      $ws->task($data);
      //客户端会马上收到以下信息
      $ws->push($frame->fd, "server-push:".date("Y-m-d H:i:s"));
  }
 
  public function onTask($serv, $taskId, $workerId, $data) {
      print_r($data);
      // 耗时场景 10s
      sleep(10);
      return "on task finish"; // 告诉worker 并返回给onFinish的$data
  }
 
  public function onFinish($serv, $taskId, $data) {
      echo "taskId:{$taskId}\n";
      echo "finish-data-sucess:{$data}\n";
  }
 
  public function onClose($ws, $fd) {
      echo "clientid:{$fd}\n";
  }
}
$obj = new Ws();

Swoole异步非堵塞IO

异步 阻塞 和 IO模型
同步和异步 消息通知机制
同步 调用发出之后不会立即返回 但一旦返回 则返回最终结果
异步 调用发出之后 被调用方立即返回消息 但返回 并非最终结果 被调用者通过状态 通知机制等来通知调用者 或通过回调函数来处理结果
阻塞(block)和非阻塞(nonblock) 调用者等待 被调用者返回调用结果时的状态
阻塞 调用结果返回之前 调用者会被挂起 调用者只 在得到返回结果之后 才能继续
非阻塞 调用者在结果返回之前 不会被挂起

Swoole IO模型

blocking IO 阻塞式IO
nonblocking IO 非阻塞IO
multiplexing IO 多路复用IO
signal driven IO 事件驱动式IO
asynchronous IO 异步IO
真正执行IO过程的阶段是 内核 内存数据拷贝到 进程内存中
Swoole异步毫秒定时器 异步高精度定时器 粒度为毫秒级
swoole_timer_tick(2000, function ($timer_id) {//每隔2000ms触发一次
  echo "tick-2000ms\n";
});
swoole_timer_after(3000, function () {//3000ms后执行此函数
  echo "after 3000ms.\n";
});

Swoole异步文件系统IO

异步文件系统IO

异步读

/  读取文件 __DIR__ 文件不存在会返回false
* 成功打开文件立即返回true
* 数据读取完毕后会回调指定的callback函数。
*/
$result = swoole_async_readfile(__DIR__."/1.txt", function($filename, $fileContent) {
  echo "filename:".$filename.PHP_EOL;  // \n \r\n
  echo "content:".$fileContent.PHP_EOL;
});
//命名空间风格
$result = Swoole\Async::readfile(__DIR__."/1.txt", function($filename, $fileContent) {
  echo "filename:".$filename.PHP_EOL;  // \n \r\n
  echo "content:".$fileContent.PHP_EOL;
});
var_dump($result);
echo "start".PHP_EOL;

异步写(如日志)

$http->on('request', function($request, $response) {
  $content = [
      'date:' => date("Ymd H:i:s"),
      'get:' => $request->get,
      'post:' => $request->post,
      'header:' => $request->header,
  ];
  swoole_async_writefile(__DIR__."/access.log", json_encode($content).PHP_EOL, function($filename){
      // todo
  }, FILE_APPEND);
  $response->end("response ". json_encode($request->get));
});

异步MySQL

class AsyncMySql {
 
  public $dbSource = "";
 
  public $dbConfig = [];
  public function __construct() {
      //new swoole_mysql;
      $this->dbSource = new Swoole\Mysql;
      $this->dbConfig = [
          'host' => '127.0.0.1',
          'port' => 3306,
          'user' => 'root',
          'password' => 'test',
          'database' => 'test',
          'charset' => 'utf8',
      ];
  }
  public function update() {}
  public function add() {}
 
  public function execute($id, $username) {
      $this->dbSource->connect($this->dbConfig, function($db, $result) use($id, $username)  {
          echo "mysql-connect".PHP_EOL;
          if($result === false) {
              var_dump($db->connect_error);
              // todo
          }
          $sql = "select * from cmf_user where id=1";
          //$sql = "update test set `username` = '".$username."' where id=".$id;
          // insert into
          // query (add select update delete)
          $db->query($sql, function($db, $result){ // select => result返回的是 查询的结果内容
              if($result === false) { // todo
                  var_dump($db->error);
              }elseif($result === true) {// add update delete
                  // todo
                  var_dump($db->affected_rows);
              }else {
                  print_r($result);
              }
              $db->close();
          });
      });
      return true;
  }
}
$obj = new AsyncMySql();
$flag = $obj->execute(1, 'singwa-111112');
var_dump($flag).PHP_EOL;
echo "start".PHP_EOL;

Swoole异步Redis

swoole用 redis 前置条件
redis 服务hiredis 库
编译 swoole 需要加入 -enable-async-redis
安装 hiredis
使用Redis客户端 需要安装hiredis库 下载hiredis源码后 执行
make -j
sudo make install
sudo ldconfig
hiredis下载地址
启用异步Redis客户端
编译swoole时 在configure指令中加入--enable-async-redis
# ./configure --with-php-config=/usr/local/php/bin/php-config --enable-async-redis
make clean
make -j
sudo make install
查看PHP的swoole扩展 php -m
查看hiredis是否编译安装成功 php --ri swoole
$redisClient = new swoole_redis;// Swoole\Redis
$redisClient->connect('127.0.0.1', 6379, function(swoole_redis $redisClient, $result) {
  echo "connect".PHP_EOL;
  var_dump($result);
  // 同步 redis (new Redis())->set('key',2);
 
 
  $redisClient->keys('*gw*', function(swoole_redis $redisClient, $result) {
      var_dump($result);
      $redisClient->close();
  });
});