SWOOLE学习笔记(未完待续)

swoole

初步

  • 环境准备
    和php扩展安装一样,会和扩展产生冲突,比如xdebug,phptrace,aop,molten
    我这边本地用的是window来学习 直接使用了phpstudy进行安装了
  • php --ri swoole 查看swoole的配置
  • window启动服务 swoole-cli 文件名

swoole初体验

<?php
$http = new Swoole\Http\Server('0.0.0.0', 9501);
$http->on('Request', function ($request, $response) {
    echo "接收到了请求";
    $response->header('Content-Type', 'text/html; charset=utf-8');
    $response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>');
});
echo "服务启动";
$http->start();

HTTP,TCP,UDP

  • 超全局变量 R E Q U E S T , _REQUEST, REQUEST,_SERVER
  • 全局变量会导致进程隔离问题,内存泄漏问题。
  • 列子如下
$http = new Swoole\Http\Server('0.0.0.0', 9501);
$i = 1;
$http->set([
    'worker_num'=>2,
]);
$http->on('Request', function ($request, $response) {
    global $i;
    $response->end($i++);
});
$http->start();
  • 监听TCP用的是connect时间 测试tcp一般是使用telnet
//创建Server对象,监听 9501 端口
$server = new Swoole\Server('0.0.0.0', 9501);
//监听连接进入事件
$server->on('Connect', function ($server, $fd) {
    echo "Client: Connect.\n";
});
//监听数据接收事件 $fd 客户端的一个id
$server->on('Receive', function ($server, $fd, $reactor_id, $data) {
    $server->send($fd, "Server: {$data}");
});
//监听连接关闭事件
$server->on('Close', function ($server, $fd) {
    echo "Client: Close.\n";
});
//启动服务器
$server->start();
  • 监听udp udp是不可靠的速度比tcp快
$server = new Swoole\Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
//监听数据接收事件
$server->on('Packet', function ($server, $data, $clientInfo) {
    var_dump($clientInfo);
    $server->sendto($clientInfo['address'], $clientInfo['port'], "Server:{$data}");
});
//启动服务器
$server->start();

tcp,udp客户端

  • tcp客户端
$client = new Swoole\Client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9501, -1)) {
   exit("connect failed. Error: {$client->errCode}\n");
}
$client->send("hello world\n");
echo $client->recv();
$client->close();
  • udp客户端
$client = new Swoole\Client(SWOOLE_SOCK_UDP);
$client->sendto('127.0.0.1', 9501, "hello world\n");
echo $client->recv();
$lient->close();

其他的一些方法

  • 第一个 isConnected() 用于返回客户端是否连接的布尔值。前提当然是要调用了 connect() 并成功建立连接之后才会返回 true
  • getSocket() 用于返回一个 socket 扩展的句柄资源符,目前我们的系统环境中暂时没有安装 socket 扩展,所以这个函数还用不了。
  • getsockname() 用于获取客户端的 socket 在本地的 host 和 port 端口。可以看到注释中我们程序自动在本地开了 47998 这个端口用于和服务端的 TCP 进行通信使用。
  • 我们可以使用 getpeername() 获得对端 socket 的 IP 地址和端口。

WebSocket 服务

  • 服务端
//创建WebSocket Server对象,监听0.0.0.0:9501端口
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9501);

//监听WebSocket连接打开事件
$ws->on('Open', function ($ws, $request) {
    while(1){
        $time = date("Y-m-d H:i:s");
        $ws->push($request->fd, "hello, welcome {$time}\n");
        Swoole\Coroutine::sleep(10);
    }
});
//监听WebSocket消息事件
$ws->on('Message', function ($ws, $frame) {
    echo "Message: {$frame->data}\n";
    $ws->push($frame->fd, "server: {$frame->data}");
});
//监听WebSocket连接关闭事件
$ws->on('Close', function ($ws, $fd) {
    echo "client-{$fd} is closed\n";
});
$ws->start();
  • 客户端
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="text" id="txt"/><br/>
<button onclick="send()">发送</button>
<p id="response">
</p>
<script type="text/javascript">
  var wsServer = 'ws://192.168.56.133:9501';
  var websocket = new WebSocket(wsServer);
  websocket.onopen = function (evt) {
    console.log("Connected to WebSocket server.");
  };

  websocket.onclose = function (evt) {
    console.log("Disconnected");
  };
  websocket.onmessage = function (evt) {
    console.log('Retrieved data from server: ' + evt.data);
    document.getElementById("response").innerHTML = document.getElementById("response").innerHTML + 'Retrieved data from server: ' + evt.data + '<br/>';
  };

  websocket.onerror = function (evt, e) {
    console.log('Error occured: ' + evt.data);
  };
  function send(){
      websocket.send(document.getElementById('txt').value);
  }
</script>
</body>
</html>

异步任务

$http = new Swoole\Http\Server('0.0.0.0', 9501);
$http->set([
    'task_worker_num'=>4,
]);
$http->on('Request', function ($request, $response) use($http) {
    echo "接收到了请求", PHP_EOL;
    $response->header('Content-Type', 'text/html; charset=utf-8');
    //监听服务
    $http->task("发送邮件");
    $http->task("发送广播");
    $http->task("执行队列");
    $response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>');

});
//处理异步任务(此回调函数在task进程中执行)
$http->on('Task', function ($serv, $task_id, $reactor_id, $data) {
    echo "New AsyncTask[id={$task_id}]".PHP_EOL;
    sleep(rand(1,5));
    //返回任务执行的结果
    $serv->finish("{$data} -> OK");
});
//处理异步任务的结果(此回调函数在worker进程中执行)
$http->on('Finish', function ($serv, $task_id, $data) {
	//处理服务
    echo "AsyncTask[{$task_id}] Finish: {$data}".PHP_EOL;
});
echo "服务启动", PHP_EOL;
$http->start();
  • worker_num 进程数量
  • task_worker_num 异步进程数量 也是会产生进程的

redis服务器

  • 可以用来写redis

进程,线程,协程

进程

  • 包工头
  • 前面swoole中的task也是进程 不是线程
  • 就是我们执行的程序。程序和程序之间没有共享内容,都是自己独立的内存空间。
  • 调度切换由操作系统完成,用户无感知,切换内容非常多,开销大,效率低
  • 需要通信的话一般是通过信号传递,或者外部工具。

线程

  • 农民工
  • 进程下面的小弟,同一个进程间的多个线程共享内存。
  • 真正的并行执行,可以利用 CPU 的核数。
  • 调度切换比进程小,但一样是操作系统完成,开销和效率中等。

协程

  • 协程,从官方意义上来说,不是进程也不是线程,它更类似于一个不带返回值的函数调用。但是,如果通俗一点说,你把它想象成线程也问题不大,只不过这个线程是用户态的。什么意思呢?进程和线程是系统来控制的,包括切换调度,而协程是可以自己操作的,不需要操作系统参与,创建销毁的成本非常低。但同时,它又不像多线程可以充分利用 CPU 。在 Swoole 中,我们在协程中需要借助于多进程模型来利用到多核 CPU 的优势。
  • 当然,并不是说协程就无敌了。协程只有在等待 IO 的过程中才能重复利用线程。一条线程在等待耗时的 IO 操作时会阻塞,其实操作系统这个时候认识的只有线程,当它阻塞了,这个线程里面的协程也是在阻塞态的。如果没有异步调用的支持,其实协程也发挥不了什么作用。异步调用又是个啥?就是 异步IO ,epoll 有了解过不?或者说,JavaScript 中的事件循环。
  • 既然是循环,其实想必你也清楚了,我们来总结一下。协程只是说在一个线程中,通过自己控制自己,来让一个线程中可以执行多个协程程序,当前面的协程阻塞时,通过异步 IO 放在那里去执行,然后切换到其它的协程运行。在这个时候,操作系统并不需要切换或者创新新的线程,减少了线程的切换时间。注意,真正并行的只有线程,或者两个不相干的进程,而协程,并不是并行处理的,在线程中,它也是在 CPU 的时间分片机制下的切换执行。一个线程中的一个协程运行时,其它的协程是挂起状态的。它的切换成本非常低,而且是用户态可控的。协程和进程、线程完全不是一个维度的概念,就像上面说的,它就是个函数。
  • 线程的小弟,但其实更像是一个函数。
  • 在线程之上的串行运行,依赖于线程,而且依赖于异步 IO ,如果没有 异步IO 的支持,它和线程没什么区别。
  • 更加轻量级,用户态切换,开销成本低,效率非常高。

Swoole 异步进程服务系统

Server两种运行模式
  • 默认SWOOLE_BASE
  • SWOOLE_PROCESS
SWOOLE_BASE模式
  • 传统的异步非阻塞模式:是通过一个主线线程来处理所有的请求,然后对 I/O 操作进行异步线程处理,避免创建、销毁线程以及线程切换的消耗。当 I/O 任务完成后,通过观察者执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环。
  • 当一个请求进来的时候,所有的 Worker 都会争抢这一个连接,并最终会有一个 Worker 进程成功直接和客户端建立连接,之后这个连接中所有的数据收发直接和这个 Worker 通讯,不再经过主进程的 Reactor 线程转发。
  • 通过一个主线线程来处理所有的请求
---------php|----php
		  		 |----php
base模式下的进程
SWOOLE_PROCESS模式
  • SWOOLE_PROCESS 的所有客户端都是和主进程建立的,内部实现比较复杂,用了大量的进程间通信、进程管理机制。适合业务逻辑非常复杂的场景,因为它可以方便地进行进程间的互相通信。
  • 在 SWOOLE_PROCESS 中,所有的 Worker 不会去争抢连接,也不会让某一个连接与某个固定的 Worker 通讯,而是通过一个主进程进行连接。剩下的事件处理则交给不同的 Worker 去执行,当到达 Worker 之后,同样地也是使用回调方式进行处理了,后续内容基本就和 BASE 差不多了。也就是说,Worker 功能的不同是它和 SWOOLE_BASE 最大的差异,它实现了连接与数据请求的分离,不会因为某些连接数据量大某些量小导致 Worker 进程不均衡。
  • 不会去争抢连接 而是会多一个进程去操作
-------php-----|----php
	|				|----php
	|
	|-php
两种模式的差异
  • 如果客户端之间不需要交互,也就是我们普通的 HTTP 服务的话,SWOOLE_BASE 模式是一个很好的选择,但是它除了 send() 和 close() 方法之外,不支持跨进程执行。但其实,这两种模式在底层的处理上没什么太大的区别,都是走的异步IO机制。只是说它们的连接方式不同。SWOOLE_BASE 的每个 Worker 都可以看成是 SWOOLE_PROCESS 的 Reactor 线程和 Worker 进程两部分的组合。
  • BASE 模式因为更简单,所以不容易出错,它也没有 IPC 开销,而 PROCESS 模式有 2 次 IPC (我的理解是要进行线程的调度一次是调起master,另一个是调起worker,但是base就是worker直接走完就行了,不需要线程的切换看到后面好像发现理解错了?就只是通信而已? )开销,master 进程与 worker 进程需要 Unix Socket 进行通信。IPC 这东西就是同一台主机上两个进程间通信的简称。它一般有两种形式,一个是通过 Unix Socket 的方式,就是我们最常见的类似于 php-fcgi.sock 或者 mysql.sock 那种东西。另一种就是 sysvmsg 形式,这是 Linux 提供的一种消息队列,一般使用的比较少。
一些重要的线程和进程
Master 进程
  • 它是一个多线程进程。用于管理线程,它会创建 Master 线程以及 Reactor 线程,并且还有心跳检测线程、UDP 收包线程等等。
Reactor 线程
  • 这个线程我们不止一次提到了,它是在 Master 进程中创建的,负责客户端 TCP 连接、处理网络 IO 、处理协议、收发数据,它不执行任何 PHP 代码,用于将 TCP 客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包。我们在 Swoole 代码中操作不了线程,为什么呢?其实 PHP 本身就是不支持多线程的,Swoole 是一种多进程应用框架。在这里的线程是在底层用 C/C++ 封装好的。因此,也并没有为我们提供可以直接操作线程的接口。但我们已经学习过了,协程本身就是工作在线程之上的,而且,协程也已经是现在的主流方向了,所以在 Swoole 中,进程管理和协程,才是我们学习的重点。
Worker 进程
  • Worker 是接受 Reactor 线程投递过来的请求数据包,并执行具体的 PHP 回调函数来进行数据处理的。在处理完成之后,将生成的响应数据发送回 Reactor 线程,再由 Reactor 发送给客户端。Worker 进程可以是异步非阻塞模式的,也可以是同步阻塞模式的,并且是以多进程方式运行的。
TaskWorker 进程
  • 它是接受收 Worker 进程投递过来的任务,处理任务后将结果返回给 Worker 进程,这种模式是同步阻塞的模式,同样它也是以多进程的方式运行的。
Manager 进程
  • 这个进程主要负责创建、回收 Worker/TaskWorkder 进程。其实就是一个进程管理器。

在这里插入图片描述
在这里插入图片描述

swoole服务运行流程

在这里插入图片描述

单进程管理Process
  • 使用多进程能力
for ($i = 0; $i < 2; $i++) {
    $process = new \Swoole\Process(function () {
        $t = rand(10, 20);
        echo 'Child Process #' . getmypid() . 'start and sleep ' . $t . 's', PHP_EOL;
        sleep($t);
        echo 'Child Process #' . getmypid() . ' exit', PHP_EOL;
    });
    $process->start();
}
//-------------回收之后这个进程就是被阻塞了无法做其他事情
for ($n = 2; $n--;) {
	//等待两个程序回收之后再退出
    $status = \Swoole\Process::wait(true);
    echo "Recycled #{$status['pid']}, code={$status['code']}, signal={$status['signal']}" . PHP_EOL;
}
echo 'Parent #' . getmypid() . ' exit' . PHP_EOL;
//while(1) sleep(100);
|||
|||
|||  对上面写法的一个升级 就是让主进程可以去做其他的事情
|||
VVV

Swoole\Process::signal(SIGCHLD, function ($sig) {
    //必须为false,非阻塞模式
    while ($ret = Swoole\Process::wait(false)) {
        echo "PID={$ret['pid']}\n";
    }
});
echo 'Parent #' . getmypid() . ' exit' . PHP_EOL;
//while(1) sleep(100);
Swoole\Timer::tick(2000, function () {});

//----------------   
  • 僵尸进程 子进程退出后没有回收
  • 证明进程间是相互没有联系的 对变量操作都是无法去共享的
$obj = new stdClass();
$obj->parent = 1;
var_dump($obj);

(new \Swoole\Process(function () use ($obj) {
    $obj->child1 = 1;
    var_dump($obj);
}))->start();

(new \Swoole\Process(function () use ($obj) {
    $obj->child2 = 1;
    var_dump($obj);
}))->start();
// [root@localhost source]# php 3.3单进程管理Process.php
// object(stdClass)#1 (1) {
// ["parent"]=>
//   int(1)
// }
// object(stdClass)#1 (2) {
// ["parent"]=>
//   int(1)
//   ["child1"]=>
//   int(1)
// }
// object(stdClass)#1 (2) {
// ["parent"]=>
//   int(1)
//   ["child2"]=>
//   int(1)
// }
  • 回调参数的方法
(new \Swoole\Process(function () {
    var_dump(func_get_args());
}))->start();
//  [root@localhost source]# php 3.3单进程管理Process.php
//  array(1) {
//      [0]=>
//    object(Swoole\Process)#1 (6) {
//    ["pipe"]=>
//      int(4)
//      ["msgQueueId"]=>
//      NULL
//      ["msgQueueKey"]=>
//      NULL
//      ["pid"]=>
//      int(1956)
//      ["id"]=>
//      NULL
//     ["callback":"Swoole\Process":private]=>
//      object(Closure)#2 (0) {
//      }
//    }
//  }
  • 改名
(new \Swoole\Process(function (\Swoole\Process $process) {
    $process->name('Child Test1');
    sleep(10);
}))->start();

(new \Swoole\Process(function (\Swoole\Process $process) {
    $process->name('Child Test2');
    sleep(10);
}))->start();

swoole_set_process_name("Parent Test");

// [root@localhost ~]# ps -ef | grep Test
// root      1942  1413  0 21:45 pts/0    00:00:00 Parent Test
// root      1943  1942  0 21:45 pts/0    00:00:00 Child Test1
// root      1944  1942  0 21:45 pts/0    00:00:00 Child Test2
Process 对象的 name() 方法是 swoole_set_process_name() 这个全局函数的别名。所以我们在主进程使用的是 swoole_set_process_name() 演示的。如果是主进程,改名方法要在 start() 之后使用。如果是子进程,要在子进程的回调函数中使用。下面这样是无效的哦。
  • 执行外部程序
// php -r "echo 1+1;"
(new \Swoole\Process(function (\Swoole\Process $process) {
    echo $process->exec('/usr/local/php/bin/php', ['-r', 'echo 1+1;']);
    // 2
}))->start();
  • 守护进程
Swoole\Process::daemon();
守护进程的概念也不用我多说了吧,最明显的就是我们的进程如果不是守护进程,那么在命令行运行起来的时候界面会一直保持在程序运行的状态中。而开启了守护进程之后,进程就转移到后台运行了,就像 nohup 的作用一样。
  • exit和kill
(new \Swoole\Process(function(\Swoole\Process $pro){
    $pro->exit(9);
    sleep(20);
}))->start();
//  Array
//  (
//      [pid] => 2086
//      [code] => 9
//      [signal] => 0
//  )
//  PID=2086

$process = new \Swoole\Process(function(\Swoole\Process $pro){
    sleep(20);
});
$process->start();
Swoole\Process::kill($process->pid);
//  Array
//  (
//      [pid] => 2087
//      [code] => 0
//      [signal] => 15
//  )
//  PID=2087

Swoole\Process::signal(SIGCHLD, function ($sig) {
    //必须为false,非阻塞模式
    while ($ret = Swoole\Process::wait(false)) {
        print_r($ret);
        echo "PID={$ret['pid']}\n";
    }
});
  • 优先级
Swoole\Process::setAffinity([0,1]);

$process = new \Swoole\Process(function(\Swoole\Process $pro){
    echo $pro->getPriority(PRIO_PROCESS), PHP_EOL;
});
$process->start();
$process->setPriority(PRIO_PROCESS, -10);
进程间的相互通信
  • 通过管道
$workers = [];
for ($i = 1; $i < 3; $i++) {
    $process = new Swoole\Process(function (Swoole\Process $worker) {
        var_dump($worker);
        $data = $worker->read();
        echo "Child {$worker->pid}:来自 Master 的数据 - \"{$data}\"。", PHP_EOL;
        $worker->write("发送给领导,来自 {$worker->pid} 的数据,通过管道 {$worker->pipe} !");
    });
    $process->start();
    $workers[$process->pid] = $process;
}

foreach ($workers as $pid => $process) {
    $process->write("{$pid},你好!");
    $data = $process->read();
    echo "Master:来自子进程 {$pid} 的数据 - \"{$data}\"", PHP_EOL;
}

// [root@localhost source]# php 3.4进程间通信.php
// Child 2076:来自 Master 的数据 - "2076,你好!"。
// Master:来自子进程 2076 的数据 - "发送给领导,来自 2076 的数据,通过管道 4 !"
// Child 2077:来自 Master 的数据 - "2077,你好!"。
// Master:来自子进程 2077 的数据 - "发送给领导,来自 2077 的数据,通过管道 6 !
  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值