PHP.ini 添加swoole
extension=swoole
创建tcp服务器
//创建Server对象,监听 127.0.0.1:9501端口
$serv = new swoole_server("127.0.0.1", 9501);
//监听连接进入事件
$serv->on('connect', function ($serv, $fd) {
echo "Client: Connect.\n";
});
//监听数据接收事件
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
$serv->send($fd, "Server: ".$data);
});
//监听连接关闭事件
$serv->on('close', function ($serv, $fd) {
echo "Client: Close.\n";
});
//启动服务器
$serv->start();
创建udp服务器
//创建Server对象,监听 127.0.0.1:9502端口,类型为SWOOLE_SOCK_UDP
$serv = new swoole_server("127.0.0.1", 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
//监听数据接收事件
$serv->on('Packet', function ($serv, $data, $clientInfo) {
$serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);
var_dump($clientInfo);
});
//启动服务器
$serv->start();
创建http服务器
use Swoole\http\server;
$http = new Server("127.0.0.1",9501);
$http->on('request',function($request,$response){
$response->end("<h1>Hello Swoole. #".rand(1000,9999)."</h1>");
});
$http->start();
swoole websocket服务
websocket特点
- 建立在TCP协议之上
- 性能开销小通信高效
- 客户端可以与任意服务器通信
- 协议表示符ws wss
- 持久化网络通信协议
swoole 增加内置了websocket 服务器支持
$server =new swoole_websocket_server('0.0.0.0',9501);
$server->on('open',function(){});
$server->on('message',function(){});
$server->on('close',function(){});
$server->start();
websocket 提供了三个回调函数 其中onmessage是必选, onHandShake,onOpen函数为可选
<script>
var wsUrl = "ws://singwa.swoole.com:8812";
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>
swoole_websocket_server 继承自 swoole_http_server
- 设置了 onRequest 回调,websocket服务器也可以同时作为http服务器
- 未设置 onRequest 回调,websocket服务器收到http请求会返回http 400 错误
websocket 服务优化
class Ws{
CONST HOST = '0.0.0.0';
CONST POST = 8812;
public $ws =null;
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连接事件
*
*/
public function onOpen($ws,$request){
}
/**监听ws消息事件
*
*/
public function onMessage($ws,$frame){
$ws->push('success');//发送数据到客户端
}
/**
* close
*/
public function onClose($ws,$fd){
}
}
$obj = new Ws();
swoole task 任务
使用场景:
执行一些耗时的操作(发送邮件 广播等)
如何使用:
- onTask
- onFinish
- 设置task_worker_num
可以在swoole里面的http,tcp,websocket( websocket_server基于http),等服务中使用
function onTask(swoole_server,$serv,int $task_id,int $src_worker_id,mixed $data);
t a s k i d 是 任 务 i d , 有 s w o o l e 扩 展 内 自 动 生 成 , 用 于 区 分 不 同 的 任 务 。 task_id 是任务id,有swoole扩展内自动生成,用于区分不同的任务。 taskid是任务id,有swoole扩展内自动生成,用于区分不同的任务。task_id 和$src_worker_id组合起来才是全局唯一性的。
class Ws{
CONST HOST = '0.0.0.0';
CONST POST = 8812;
public $ws =null;
public function __construct(){
$this->ws = new swoole_websocket_server('0.0.0.0',8812);
$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连接事件
*
*/
public function onOpen($ws,$request){
}
/**监听ws消息事件
*
*/
public function onMessage($ws,$frame){
//假如耗时10秒
//todo
$data = [
'task'=>1,
'fd'=>$frame->fd,
]
$ws->task($data);
$ws->push('success');//发送数据到客户端
}
public function onTask($serv,$taskId,$workId,$data){
sleep(10);
return 'on task finish';//告诉worker
}
public function onFinish($serv,$taskId,$data){
echo 'finish-data-success:'.$data;//$data 是上面的'on task finish'这个字符串
}
/**
* close
*/
public function onClose($ws,$fd){
}
}
$obj = new Ws();
执行之后,onMessage方法会立刻返回success信息到前端,不必等待10秒。因为把耗时任务投递给task机制后,会达到一个异步的效果,下面的代码不需要等待task执行完就能执行下去,所以会达到快速响应的效果。
进程
- 什么是进程
进程就是正在运行的程序的一个实例
开启一个子进程 new swoole_precess就是开始一个子进程
$process = new swoole_process(function(swoole_process $pro ){
//todo
//php http_server.php 相当于在终端执行PHP文件一样
$pro->exec('写PHP的安装绝对路径',[__DIR__.'/../server/http_server.php']);
},true);//假如这个参数为false 可以打印出来echo的东西,如果为true的情况,在方法内是打印不出东西的。
$pid = $process->start();
echo $pid . PHP_EOL;
- 比较重要的一个函数pstree 可以直观的看到它的子进程
pstree -p 22727 (某个进程数)
6-4 swoole内存table详解
- swoole_table 是一个基于共享内存和锁实现的超高性能,并发数据结构
使用场景:数据共享的时候。多进程的时候数据共享
//创建内存表
$table = new swoole_table(1024);
//内存表增加一列
$table = column('id',$table::TYPE_INT,4);
$table = column('name',$table::TYPE_STRING,64);
$table = column('age',$table::TYPE_INT,4);
$table->create();
$table->set('singwa_imooc',['id'=>1,'name'=>'singwa','age'=>30]);
var_dump($table->get('singwa_imooc')) ;
$table['singwa_imooc_2'] = [
'id'=>2,
'name'=>'singwa2',
'age'=>31,
];
//var_dump($table['singwa_imooc_2']);
$table->incr('singwa_imooc_2','age',2); //decr自减
内存操作模块有
- Lock
- Buffer
- Table
- Atomic
- mmap
- channel
- serialize
6-6协程精讲
- swoole 在2.0 开始内置协程(Coroutine)的能力,提供了具备协程能力IO接口(统一在命名空间 Swoole\Coroutine*);
- 协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。
- 开发者可以无感知的用同步的代码编写方式达到异步IO的效果和性能,避免了传统异步回调锁带来的代码逻辑和陷入多层回调中导致代码无法维护。。
- 协程客户端只能使用在onRequet, OnReceive, onConnect callback的回调里面才能生效。
假如需要获取redis的数据同时需要获取mysql的数据的时候就可以以同步的代码执行成异步IO的效果和性能。
$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['a']);
$response->heade("Content-Type",'text/plain');
$response->end($value);
});
$http->start();
7 swoole 适配thinkphp
- 让swoole的控制方法等能够在框架里正常运作
- worker_num 启动的进程数
- onWorkerStart 这是一个事件回调函数,此事件在worker 进程/Task进程启动时发生。这里创建的对象可以在进程生命周期内使用。
- 主要是在swoole启动的时候,在这个函数中热加载框架的应用目录和框架里面的文件。让进程生命周期内可以使用框架的东西。
- swoole 对于超全局变量,比如 _ G E T , \_GET, _GET,_POST等等,如果进程还在的时候,是不会释放的。
- Thinkphp 有个输出控制器方法的函数request()->action().PHP_EOL;
PHP_EOL是PHP里面的换行符 - tp 会把控制器,模块,方法放在一个path变量里面,这个值在worker进程是不会变的,所以导致不管访问什么方法都不会变。
- 解决方案,1 是使用http_server 的close方法把资源,变量等清空掉,类似注销。(会报错,但是报错它会重启swoole,所以能走下去)
- 2 修改tp框架的底层代码,其中有一个判断,假如path路由为空就返回上一次的路由。
详细:
因为在onworkerstart里面加载的tp的应用和文件,所以tp里面的path变量也会有不被释放的特性。 然后在底层代码中,有个路由检测的方法,里面会有对path路由的一个判断的代码(假如path路由不为空,直接返回路由)问题就出在这,因为swoole在生命周期内是不会释放变量的这么个特性。导致了第一次访问之后path一直有值,然后经过这个判断之后直接返回上一次的路由。
总结:
- swoole进程生命周期内不会释放超全局变量。
- onWorkerStart,worker/task进程启动时的事件回调函数。
8 赛事直播在线用户处理
redis-方案
sadd
smembers
- 使用有序集合,获取连接的用户,在用户连接的时候把fd添加到有序集合中,在用户关闭连接的时候,把对应的fd从有序集合中删除,然后push信息的时候,直接从redis里面有序集合拿到fd去推送信息。
用户打开页面连接到服务端:onopen回调(监听ws连接事件)
在该回调中使用redis的有序集合去记录fd。
用户关闭页面断开服务端:onclose
当用户关闭连接时。从redis的有序集合中删掉对应的fd
重启server.php服务的时候,如果有序集合key有值,必须先把值删掉
redis-方案-优化(基础类库优化)
php的 __call( n a m e , name, name,arguments){}方法,其中$name是方法名
其中swoole task机制就是异步机制 可以很好处理push很多用户的时候,慢的问题。
9 聊天室模块
- 主持人发送消息->推送给客户端->页面展示
- 登录用户->发送信息->页面展示
发送数据交互处理
$(function(){
$('#discuss-box').keydown(function(event){
if(event.keyCode==13){
var text = $(this).val();
var url = '';
var =data ={'content':text,'game_id':1};
$post(url,data,function(result){
//todo
$(this).val("");
},'json')
}
});
});
聊天室chartjs文件编写
- 聊天室的内容跟推送的内容用不同的端口区分开
swoole中connections的使用场景
直接监听新端口
$this->ws->listen(host,端口号,SWOOLE_SOCK_TCP);
聊天室功能开发
public function index(){
if(empty($_POST['game_id'])){
}
foreach($_POST['http_server']->post[1]->connections as $fd){
$_POST['http_server']->push($fd,json_encode($data));
}
}
10-2节笔记-监听服务
- 关键函数或者linux命令
- 每隔$time秒执行一次该方法 粒度为毫秒级
swoole_timer_tick($time,function(){})
*(都用绝对路径) 把脚本结果都打印到txt文件中做为日志记录
nohup php server.php > a.txt
- 能把查看的端口信息简化到1或者0返回给我们
netstat -anp 2>dev/null | grep 8811 | LISTEN | wc -l
shell_exec()
- 例如每隔2秒执行一次该方法:
swoole_timer_tick(2000,function($timer_id){
echo 'success';
})
- 查看端口是否开启
$shell = ‘netstat -anp | grep 8811’;
- 升级版
$shell = ‘netstat -anp | grep 8811 | grep LISTEN | wc -l’
- 最终版
s h e l l = ′ n e t s t a t − a n p 2 > d e v / n u l l ∣ g r e p 8811 ∣ L I S T E N ∣ w c − l ′ ; p h p s h e l l e x e c ( shell = 'netstat -anp 2>dev/null | grep 8811 | LISTEN | wc -l'; php shell_exec( shell=′netstat−anp2>dev/null∣grep8811∣LISTEN∣wc−l′;phpshellexec(shell);
平滑重启
- swoole_set_process_name(‘live_master’);//给进程一个名称
- pidof live_master //直接返回进程pid
echo "loading..."
pid=`pidof live_master`
echo $pid
kill -USR1 $pid
echo "loading success"
日志落盘处理(swoole异步文件操作)
public function writeLog() {
$datas = array_merge(['date' => date('ymd H:i:s')],$_GET,$_POST,$_SERVER);
$logs = "";
foreach($datas as $key => $value){
$logs .= $key . ":" . $value . ' ';
}
//写入文件,FILE_APPEND(写入文件的模式,追加)
swoole_async_writefile('路径','数据',function($filename){},FILE_APPEND);
}
多个请求过滤
- swoole 会默认请求一个图标地址favicon.ico 所以导致请求了2次初始文件,在初始文件回调函数把这个请求给过滤掉
public function onRequest($request,$response){
if($request->server['request_uri']=='/favicon.ico'){
$response->status(404);
$response->end();
return;
}
}
nginx负载转发到swoole服务器
location / {
root /home....;//静态文件
index index.html
if(!-e $request_filename){
proxy_pass https://127.0.0.1:8811;
}
}
- 在http 下面加个参数 hsotname -i
upstream swoole_http{
server ip:8811 weight =2;
server ; weight =2;#权重
}
location / {
root /home....;
index index.html
if(!-e $request_filename){
#proxy_pass https://127.0.0.1:8811;
proxy_pass http://swoole_http;
}
}