2 HTTP服务(常用)
netstat -anp | grep 8811 --检测端口号是否启动
php --info 可以看到swoole版本号 本文基于4.3.1
$http = new swoole_http_server("0.0.0.0", 8811);
$http->on('request', function ($request, $response) {
var_dump($request->get);
$response->header("Content-Type", "text/html; charset=utf-8");
$response->cookie("singwa",'xsssss', time() + 1800);
$response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>".json_encode($request->get));
});
$http->start();
开启http服务:(监听到了请求)
测试:
1)直接curl请求
2)浏览器访问
http返回的cookies: singwa
在服务器中除了会打印参数外 还多一个null值,原因是favicon.ico也是一个请求 参数为null
设置document_root并设置enable_static_handler为true后,底层收到Http请求会先判断document_root路径下是否存在此文件,如果存在会直接发送文件内容给客户端,不再触发onRequest回调。
$http = new swoole_http_server("0.0.0.0", 8811);
$http->set([
'document_root' => '/usr/local/apache/htdocs/swoole/data',
'enable_static_handler' => true,
]);
$http->on('request', function ($request, $response) {
var_dump($request->get); // 浏览器打开会多出一个null?
$response->header("Content-Type", "text/html; charset=utf-8");
$response->cookie("singwa",'xsssss', time() + 1800);
$response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>".json_encode($request->get));
});
$http->start();
3 WebSocket服务
什么是WebSocket?
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
为什么要使用WebSocket?
缺陷:HTTP的通信只能由客户端发起
WebSocket特点:
*建立在TCP协议之上
*性能开销小通信高效
*客户端可以与任意服务器通信
*协议标识符ws wss
*持久化网络通信协议(长连接)
ws.php
<?php
class Ws{
// ws连接
public $ws;
public function __construct(){
$this->ws = new Swoole\WebSocket\Server('0.0.0.0',8812);
$this->ws->on('open',[$this,'onOpen']);
$this->ws->on('message',[$this,'onMessage']);
$this->ws->on('close',[$this,'onClose']);
$this->ws->start();
}
/**
* 监听ws连接事件
* @param $ws
* @param $request
*/
public function onOpen($server,$request){
var_dump($request->fd);
}
public function onMessage($server,$frame){
echo "server-receive-message:{$frame->data}\n";
$server->push($frame->fd, "singwa push success!".date("Y-m-d H:i:s"));
}
public function onClose($server,$fd,$reactorId){
echo "clientid:{$fd} close!";
}
}
new Ws();
ws_client.html(可以通过8811 http服务启动页面,也可以通过ws set参数启动)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ws</title>
</head>
<body>
<h1>Singwa-Swoole-测试 </h1>
</body>
<script type="text/javascript">
var wsUrl = "ws://192.168.119.3:8812";
var websocket = new WebSocket(wsUrl);
// 实例对象的onopen属性
websocket.onopen = function(evt){
console.log("connect-swoole-success!");
websocket.send("singwa is good!");
}
websocket.onmessage = function(evt){
console.log("ws-server-return-data:"+evt.data);
}
websocket.onclose = function(evt){
console.log("close");
}
websocket.onerror = function(evt,e) {
console.log("error:"+evt.data);
}
</script>
</html>
4 Task任务使用
使用场景:
执行耗时的操作(发送邮件 广播等)
如何使用:
设置onTask
设置onFinish
设置task_worker_num
http,web socket,tcp都可以做异步task投放
ws.php
<?php
/**
* Created by PhpStorm.
* User: ZhangWei
* Date: 2019-05-09
* Time: 21:07
*/
class Ws{
CONST HOST='0.0.0.0';
CONST PORT=8812;
// ws连接资源
public $ws;
public function __construct(){
$this->ws = new Swoole\WebSocket\Server(self::HOST,self::PORT);
$this->ws->set(
[
'worker_num' => 2,
'task_worker_num' => 2,
]
);
$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();
}
/**
* 监听ws连接事件
* @param $ws
* @param $request
*/
public function onOpen($server,$request){
var_dump($request->fd);
}
/**
* 监听客户端消息事件
* @param $server
* @param $frame
*/
public function onMessage($server,$frame){
echo "server-receive-message:{$frame->data}\n";
// todo 10s
$data = [
'task' => 1,
'fd' => $frame->fd
];
$server->task($data); // 函数非阻塞,执行完毕立刻返回
$server->push($frame->fd, "singwa push success!".date("Y-m-d H:i:s"));
}
/**
* 监听task回调事件
* @param $server
* @param $task_id 任务id
* @param $src_worker_id worker进程id
* @param $data task传来的值
*/
public function onTask($server,$task_id,$src_worker_id,$data){
print_r($data);
sleep(10);
return "on tash finish\n";
}
/**
* 监听task结束事件
* @param $server
* @param $task_id
* @param $data
*/
public function onFinish($server,$task_id,$data){
echo "taskId:{$task_id}\n";
echo "finish-task-success:".$data;
}
/**
* 监听客户端关闭
* @param $server
* @param $fd
* @param $reactorId
*/
public function onClose($server,$fd,$reactorId){
echo "clientid:{$fd} close!\n";
}
}
new Ws();
ws_client.html 内容不变,还是上节中的。
5 Swoole定时器
常规定时器:
crontab
swoole定时器:
swoole_timer_tick 每个多久执行
swoole_timer_after 多久后执行
swoole_timer_clear 使用定时器ID来删除定时器
ws.php
<?php
/**
* Ws基类
* User: ZhangWei
* Date: 2019-05-09
* Time: 21:07
*/
class Ws{
CONST HOST='0.0.0.0';
CONST PORT=8812;
// ws连接资源
public $ws;
public function __construct(){
$this->ws = new Swoole\WebSocket\Server(self::HOST,self::PORT);
$this->ws->set(
[
'worker_num' => 2,
'task_worker_num' => 2,
]
);
$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();
}
/**
* 监听ws连接事件
* @param $ws
* @param $request
*/
public function onOpen($server,$request){
var_dump($request->fd);
if($request->fd == 1){
// 第一个连接时 每两秒中执行一次
swoole_timer_tick(2000,function($timer_id){
echo "2s: timerId:{$timer_id}\n";
});
}
}
/**
* 监听客户端消息事件
* @param $server
* @param $frame
*/
public function onMessage($server,$frame){
echo "server-receive-message:{$frame->data}\n";
// todo 10s
$data = [
'task' => 1,
'fd' => $frame->fd
];
//$server->task($data); // 函数非阻塞,执行完毕立刻返回
swoole_timer_after(5000,function() use ($server,$frame){
$server->push($frame->fd,"server-timer-after:5s\n");
});
$server->push($frame->fd, "singwa push success!".date("Y-m-d H:i:s"));
}
/**
* 监听task回调事件
* @param $server
* @param $task_id 任务id
* @param $src_worker_id worker进程id
* @param $data task传来的值
*/
public function onTask($server,$task_id,$src_worker_id,$data){
print_r($data);
sleep(10);
return "on tash finish\n";
}
/**
* 监听task结束事件
* @param $server
* @param $task_id
* @param $data
*/
public function onFinish($server,$task_id,$data){
echo "taskId:{$task_id}\n";
echo "finish-task-success:".$data;
}
/**
* 监听客户端关闭
* @param $server
* @param $fd
* @param $reactorId
*/
public function onClose($server,$fd,$reactorId){
echo "clientid:{$fd} close!\n";
}
}
new Ws();
6 异步回调系统
异步回调模块已过时,目前仅修复 BUG,不再进行维护, 且在V4.3.0中移除了异步模块。请使用 Coroutine 协程模块。
AsynclO成为独立扩展,可安装后使用。
异步文件系统io
读取文件
read.php
// // Swoole\Async::readfile
$result = swoole_async_readfile(__DIR__."/1.txt",function($filename,$content){
echo "filename:{$filename}".PHP_EOL;
echo "content:{$content}".PHP_EOL;
});
echo "start".PHP_EOL; // 先执行此,再执行回调
文件不存在会返回false
成功打开文件立即返回true
数据读取完毕后会回调指定的callback函数。
swoole_async_readfile会将文件内容全部复制到内存,所以不能用于大文件的读取
如果要读取超大文件,请使用swoole_async_read函数
swoole_async_readfile最大可读取4M的文件,受限于SW_AIO_MAX_FILESIZE宏
swoole_async_read(__DIR__."/1.txt",function($filename,$content){
echo "filename:{$filename}".PHP_EOL;
echo "content:{$content}".PHP_EOL;
return false;
},1,0);
与swoole_async_readfile不同,它是分段读取,可以用于读取超大文件。每次只读$size个字节,不会占用太多内存。
在读完后会自动回调$callback函数,回调函数接受2个参数:
bool callback(string $filename, string $content);
$filename,文件名称
$content,读取到的分段内容,如果内容为空,表明文件已读完
$callback函数,可以通过return true/false,来控制是否继续读下一段内容。
return true,继续读取
return false,停止读取并关闭文件
此处return false,只读一次。
写入文件
write.php
$content = date('Y-m-d H:i:s').PHP_EOL;
Swoole\Async::writefile(__DIR__.'/1.log',$content,function($filename){
// todo
echo "success".PHP_EOL;
},FILE_APPEND);
echo "start".PHP_EOL; // 先输出start 再走回调函数
参数1为文件的名称,必须有可写权限,文件不存在会自动创建。打开文件失败会立即返回false
参数2为要写入到文件的内容,最大可写入4M
参数3为写入成功后的回调函数,可选
参数4为写入的选项,可以使用FILE_APPEND表示追加到文件末尾
如果文件已存在,底层会覆盖旧的文件内容
FILE_APPEND在1.9.1或更高版本可用
Linux原生异步IO不支持FILE_APPEND,并且写入的内容长度必须为4096的整数倍,否则底层会自动在末尾填充0
tail -f 文件名 实时查看文件内容
http_server.php 写入访问信息到日志
$http = new swoole_http_server("0.0.0.0", 8811);
$http->set([
'document_root' => '/usr/local/apache/htdocs/swoole/data',
'enable_static_handler' => true,
]);
$http->on('request', function ($request, $response) {
$content = [
'date' => date("Y-m-d 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->header("Content-Type", "text/html; charset=utf-8");
$response->cookie("singwa",'xsssss', time() + 1800);
$response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>".json_encode($request->get));
});
$http->start();
异步MySQL
7 进程
什么是进程?
进程就是一个正在运行的程序的一个实例。
/*
* php process.php 会开启子进程
*
* 在子进程里开启http服务
* */
$process = new swoole_process(function(swoole_process $pro){
// todo
$pro->exec("/home/work/soft/php/php73/bin/php",[__DIR__.'/../server/http_server.php']);
},false);
$pid = $process->start();
echo $pid.PHP_EOL;
// 回收
swoole_process::wait();
4684是echo的4683创建进程后的id
如何去追踪进程之间的关系呐?
4686,4687,4688,4689是Reactor线程
理解Master/Reactor/Manager/Worker/TaskWorker ?
https://baijiahao.baidu.com/s?id=1622252306493560200&wfr=spider&for=pc
8 swoole多进程使用场景
背景
执行多个url
原始方案
同步顺序执行
问题
执行慢
解决方案
引入swoole_process
按需开启N个子进程执行
curl.php
echo "process-start-time:".date("Y-m-d H:i:s").PHP_EOL;
require_once 'function.php';
$workers = [];
$urls = [
'http://baidu.com',
'http://qq.com',
'http://sina.com.cn',
'http://baidu.com?search=singwa',
'http://baidu.com?search=singwa2',
'http://baidu.com?search=imooc',
];
for ($i=0;$i<6;$i++){
// 子进程
$process = new swoole_process(function(swoole_process $worker) use ($urls,$i){
$content = curlGet($urls[$i]);
$worker->write(json_encode($content));
},true);
$pid = $process->start();
$workers[$pid] = $process;
}
foreach($workers as $process){
echo $process->read().PHP_EOL;
}
echo "process-end-time:".date("Y-m-d H:i:s").PHP_EOL;
swoole_process::wait();
function.php
/**
* curl发送数据
* @param $url
* @param $data
* @param $type
* @return mixed
*/
function curlData($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 3000);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$r = curl_exec($ch);
curl_close($ch);
return $r;
}
/**
* curl
* @param $url
* @param $data
* @param int $timeout
* @return mixed
*/
function curlPostData($url, $data, $timeout=10)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
//curl_setopt($ch, CURLOPT_HEADER, false);
//curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$rst = curl_exec($ch);
curl_close($ch);
return $rst;
}
/**
* [curlGet description]
* @param string $url [description]
* @param integer $timeOut [description]
* @param [type] $header [description]
* @param [type] $data [description]
* @return [type] [description]
*/
function curlGet($url = '', $timeOut = 3, $header = null){
$return = [
'result' => '',
'error' => '',
'info' => '',
];
if (empty($url)) {
$return['error'] = 'url empty';
return $return;
}
$timeOut = intval($timeOut);
$timeOut <= 0 && $timeOut = 3;
$curl = curl_init();
if(is_array($header)){
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($curl, CURLOPT_TIMEOUT, $timeOut);
$ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36';
curl_setopt($curl, CURLOPT_USERAGENT, $ua);
$return['result'] = curl_exec($curl);
$return['error'] = curl_error($curl);
$return['info'] = curl_getinfo($curl);
curl_close($curl);
return $return;
}
9 Swoole Table
介绍:
swoole_table一个基于共享内存和锁实现的超高性能,并发数据结构。
用于解决多进程/多线程数据共享和同步加锁问题。
table.php 简单实用
// 创建内存表
$table = new swoole_table(1024);
// 创建字段
$table->column('id',$table::TYPE_INT,4);
$table->column('name',$table::TYPE_STRING,20);
$table->column('age',$table::TYPE_INT,4);
$table->create();
$table->set('singwa_imooc',['id'=>1,'name'=>'singwa','age'=>18]);
print_r($table->get('singwa_imooc'));
table.php 高级使用
// 创建内存表(当进程执行完毕 内存表会自动释放)
$table = new swoole_table(1024);
// 创建字段
$table->column('id',$table::TYPE_INT,4);
$table->column('name',$table::TYPE_STRING,20);
$table->column('age',$table::TYPE_INT,4);
$table->create();
$table->set('singwa_imooc',['id'=>1,'name'=>'singwa','age'=>18]);
print_r($table->get('singwa_imooc'));
// 另一种方案 数组形式
$table['singwa_imooc2'] = [
'id' => 2,
'name' => 'singwa2',
'age' => 28
];
$table->incr('singwa_imooc2','age',2); // 30 自增2
$table->decr('singwa_imooc2','age',10); // 20 自减10
print_r($table['singwa_imooc2']); // 对象
echo 'delete start'.PHP_EOL;
$table->del('singwa_imooc2'); // 删除key
print_r($table['singwa_imooc2']); // 为空
10 Swoole协程(Coroutine)
协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换
协程相关api 查看文档
redis.php
/**
* 1 redis
* 2 mysql
* 同步执行时间 = redis时间 + mysql时间
* redis+mysql
* 通过异步执行 = max(redis时间 , mysql时间)
*/
$http = new swoole_http_server("0.0.0.0", 8001);
$http->on("request", function ($request, $response) {
// 获取redis中的key值 然后输出浏览器
$redis = new Swoole\Coroutine\Redis();
$redis->connect('127.0.0.1',6379);
$value = $redis->get($request->get['key']);
/*$swoole_mysql = new Swoole\Coroutine\MySQL();
$swoole_mysql->connect([
'host' => '127.0.0.1',
'port' => 3306,
'user' => 'root',
'password' => 'root',
'database' => 'swoole',
]);
$res = $swoole_mysql->query('show tables');*/
$response->header("Content-Type","text/plain");
$response->end($value);
});
$http->start();
注意
测试tcp服务器方法
netstat -anp | grep 9501
通过telnet方式登录远程主机:telnet 127.0.0.1 9501
tcp客户端脚本
查看当前worker进程数:ps -aft | grep tcp_server.php
cat /proc/cpuinfo | grep ‘cores’ | uniq 查看系统核心数
连接:telnet 127.0.0.1 9501
ctrl+] ,quit退出