swoole是什麼
swoole是用來補缺php異步處理缺陷的新技術,支持協程,用戶也可以完全拋開原來的fpm模式調用,而直接使用php+swoole開發項目,能更加處理網站的高併發,大數據問題;它是php異步網絡通訊引擎;異步多線程;
swoole用來做什麼
在線直播,聊天室,遊戲行業
swoole如何用
安裝php,swoole就是php的一個擴展,可以用在http,websocket等請求。
swoole課程1
swoole課程2-php源碼編譯安裝
- configure (shell腳本):主要用於編譯安裝源代碼庫和軟件,對源代碼庫進行一些設置(需要gcc,autoconfig);eg:./configure --prefix=/home/usr/local/php (安裝路徑)
- make -j (構建\編譯)
- make install (安裝)
知識點
- php的啓動,是在bin目錄下,啓動命令 ./bin/php test.php (執行php)
- 設置當前用戶的快捷啓動操作:安裝目錄下 vi ~/.bash_profile export PATH 處,增加 alias php=/home/usr/local/php 然後source ~./bash_profile
- souce filename 和sh filename的區別: source 不會生成新的子shell,只是可能腳本修改需要重新讀取執行了這個shell腳本。而sh 則會產生新的子shell,不改變父級shell的進程和變量
- 注意php.ini的默認文件需要自己複製php.ini-development,然後修改成php.ini,源碼安裝沒設置安裝路徑會默認放在lib文件夾下,但是人們習慣放在etc下,這兩個目錄下尋找
- php -i | grep php.ini 查找php.ini 生效的目錄
swoole課程3-swoole編譯安裝
- 下載: git clone / 下載上傳壓縮包,直接解壓
- 需要先用phpize 先生成configure 文件,phpize是用來安裝php擴展模塊的。在swoole的文件目錄下,/home/usr/lcoal/bin/phpize 會自動生成 configure 腳本文件
- swoole目錄下 ./configure --with-php0-config=/home/usr/local/php/bin/php-config (check操作) ; make &&make install;最後會生成一個so的擴展文件(會自動顯示該so擴展文件的路徑,一般在lib/php/extensions/ 的目錄下);在lib/php.ini 下,增加 extension=swoole;
- 查看php的模塊 php -m
swoole課程4-網絡通訊
tcp基本四步走
- 創建server對象
- 監聽連接進入事件
- 監聽數據接收事件; 發送客戶端, fd, "Server: ".$data);
- 監聽關閉事件
- 啓動服務器
$serv = new Swoole\Server("127.0.0.1", 9501);
$serv->set([
'worker_num' => 4, //worker process num
'max_request'=>10000
]);
//監聽連接進入事件
$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對象
- UDP服務器與TCP服務器不同,UDP沒有連接的概念。啓動Server後,客戶端無需Connect,直接可以向Server監聽的9502端口發送數據包;裏邊傳輸數據用address,data).
- 啓動服務器
$serv = new Swoole\Server("127.0.0.1",9502,SWOOLE_PROCESS,SWOOLE_SOCK_UDP);
$serv->set([
'worker_num' => 4, //worker process num
'max_request'=>10000
]);
//UDP服務器與TCP服務器不同,UDP沒有連接的概念。啓動Server後,客戶端無需Connect,直接可以向Server監聽的9502端口發送數據包。對應的事件爲onPacket
$serv->on('Packet',function($serv,$data,$clientInfo){
$serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);
var_dump($clientInfo);
var_dump($data);
});
//啓動服務器
$serv->start();
http服務基本三步走
- 創建對象
- 設置靜態資源目錄
- 接收前端數據,響應返回數據
- 開啓http服務器
$http = new swoole_http_server("0.0.0.0", 9501);
// 配置 這裏設置了該項,瀏覽器路由加了,index.html ,代碼就不會執行西下邊的輸出
$http->set([
'enable_static_handler'=>true,
'document_root'=>'/home/swoole-mooc/demo/data',
]);
$http->on('request',function($request,$response){
print_r($request->get);
$response->cookie('login','cook-name',time()+3600);//給當前服務設置一個cookie
$response->header("Content-Type", "text/html; charset=utf-8");
$response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});
$http->start();
上邊如果請求找不到對應的html文件,就會走到request中的end方法,找到就不會
websockt
什麼是websocket?
websocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器和服務器全雙工通信----允許服務器主動發送信息給客戶端。
爲什麼需要websocket?
http的通信只能由客戶端發起,需要不斷髮送消息需要輪詢發送請求
websocket的特點?
- 建立在TCP協議之上
- 性能開銷小通信搞笑
- 客戶端可以和任意服務器通信
- 協議標識符ws,wss
- 持久化網絡通信協議
websocket\server
- 創建對象
- 監聽websocket連接打開事件
- 監聽ws消息事件:(onMessage),必須實現的事件,裏邊推送數據用fd,$data);
- 關閉ws消息事件
- 開啓服務
$server = new Swoole\WebSocket\Server("0.0.0.0", 9502);
//監聽websocket連接打開事件
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
echo "server: handshake success with fd{$request->fd}\n";
});
//監聽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, "this is server");
});
//關閉ws消息事件
$server->on('close', function ($ser, $fd) {
echo "client {$fd} closed\n";
});
//啓動websocket服務器
$server->start();
websocket前端代碼:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>Websocket的測試111</h1>
<h1>2222</h1>
<script type="text/javascript">
var wsurl = "ws://xx.xx.xx.xx:9502";
var websocket = new WebSocket(wsurl);
websocket.onopen=function(evt){
websocket.send("hello world");
console.log("鏈接成功");
}
websocket.onmessage=function(evt){
console.log("後端數據:"+evt.data);
}
websocket.onclose=function(evt){
console.log("close");
}
</script>
</body>
</html>
task任務
使用場景:執行耗時操作,比如發送郵件,廣播
實現要求:
- 需要實現onTask()、onFinish(),並且設置task_worker_num.。
- 注意:onTask事件僅在task進程中發生,onFinish事件僅在worker進程中發生
function onTask(swoole_server $serv, int $task_id, int $src_worker_id, mixed $data){};
function onFinish(swoole_server $serv, int $task_id, string data是onTask返回的值。
swoole課程5-異步非阻塞IO場景
swoole課程6-進程、內存、協程
什麼是進程?
進程就是正在運行的程序的一個實例。
進程概念:主進程-》子進程-》管理進程-》work進程
進程使用場景:比如需要耗時讀取多個文件裏的內容,執行速度慢,這是可以開啓多個進程,互不影響讀取速度加快。
進程間的通訊,通過管道通訊。
echo date("Ymd H:i:s").PHP_EOL;
$workers=[];
$urls=array("aaa,bbb,ccc,ddd,eee,fff");
for ($i=0; $i <6 ; $i++) {
// 創建子進程
$process= new swoole_process(function(swoole_process $work) use($i,$urls){
//匿名函數調用函數外部變量值的方法,使用use
$content = curlData($urls[$i]);
$work->write($content.PHP_EOL);//數據寫入管道中
},true);
$pid = $process->start();
$workers[$pid] = $process;
}
foreach ($workers as $process) {
echo $process->read();//從管道獲取數據
}
function curlData($url){
sleep(1);
return $url.PHP_EOL;
}
echo date("Ymd H:i:s").PHP_EOL;
內存模塊
Lock、Buffer、Table、Mmap、Atomic。。。。。。
swoole_table(內存表)
swoole_table(內存表)是一個基於共享內存的鎖實現的超高性能,併發數據結構。
//內存。類似數據庫能力
use Swoole\Table;
$table = new Swoole\Table(1024);
//內存增加一列
$table->column('id',Table::TYPE_INT,4);
$table->column('name',Table::TYPE_STRING,16);
$table->column('age',Table::TYPE_INT,3);
$table->create();
//表中增加一條數據 ,hello爲key
$table->set("hello",[
"id"=>1,
"name"=>"syf",
"age"=>30
]);
//原子自增信息,只針對 TYPE_INT生效
$table->incr("hello","age",12);
//內存中獲取數據
print_r($table->get("hello"));
協程 Coroutine
協程可以理解爲純用戶態的線程,通過協作而不是搶佔來進行切換。應用層可以使用同步編程方式,底層自動實現異步IO。
優勢:
- 開發者可以再無感知情況下使用同步編程方式實現異步操作,避免了異步回調帶來離散代碼邏輯和多重調用的無法維護障礙
- 相較於傳統的php,開發者不需要使用yield關鍵詞來標識一個協程IO操作,不在需要對每一級調用都修改yield,提高開發效率。
$http = new swoole_http_server("0.0.0.0", 9501);
$http->on("request",function($request,$response){
$redis = new Swoole\Coroutine\Redis();
$redis->connect("127.0.0.1",6379);
$val = $redis->get("hello");//獲得redis中key=hello值,輸出在頁面中
// $response->header("Content-type","text/html");
$response->header("Content-type","text/plain");
$response->end($val);
});
$http->start();
swoole課程7-賽事直播
開發思想學習
- 配置項多處使用時建立配置文件,eg:redis.php
return [
'host'=>'127.0.0.1',
'port'=>'6379',
'out_time'=>120,
'timeout'=>5,
'test_code'=>6789,
'live_game_key'=>'clientKey',
];
- 封裝對象,美化代碼,是面向過程的開發轉爲面向對象
class Http
{
CONST HOST = "0.0.0.0";
CONST PORT = 9501;
public $http = null;
public function __construct()
{
$this->http = new swoole_http_server(self::HOST,self::PORT);
$this->http->set([
'enable_static_handler'=>true,
'document_root'=>'/home/swoole-mooc/thinkphp51/public/static',
'work_num'=>5,
'task_worker_num'=>2,
]);
$this->http->on('workerstart',[$this,'onWorkerStart']);
$this->http->on('request',[$this,'onRequest']);
$this->http->on('task',[$this,'onTask']);
$this->http->on('finish',[$this,'onFinish']);
$this->http->on('close',[$this,'onClose']);
$this->http->start();
}
public function onWorkerStart(){
define('APP_PATH', __DIR__ . '/../application/');
// 加載框架引導文件
require __DIR__ . '/../thinkphp/start.php';
}
public function onRequest($request,$response){
$_GET = [];
$_POST = [];
// $_SERVER = [];
if(isset($request->get)){
foreach ($request->get as $k=> $v) {
$_GET[$k]=$v;
}
}
if(isset($request->post)){
foreach ($request->post as $k => $v) {
$_POST[$k]=$v;
}
}
if(isset($request->header)){
foreach ($request->header as $k => $v) {
$_SERVER[strtoupper($k)]=$v;
}
}
if(isset($request->server)){
foreach ($request->server as $k => $v) {
$_SERVER[strtoupper($k)]=$v;
}
}
$_POST['http_server'] = $this->http;
ob_start();
try{
// 執行應用並響應
think\Facade::make('app', [APP_PATH])
->run()
->send();
} catch (\Exception $e){
echo "有錯誤".$e->getMessage().PHP_EOL;
}
$res = ob_get_contents();
ob_end_clean();
// var_dump($_SERVER);
$response->header("Content-Type", "text/html; charset=utf-8");
$response->end($res);
}
public function onTask($http,$task_id,$workId,$data){
//分發任務,會進入 Task.php 來找到相應方法,來進行任務。
$obj = new app\common\lib\task\Task();
$method = $data['method'];
$flag = $obj->$method($data['data']);
return $flag;
}
public function onFinish($http,$task_id,$data){
echo "task is finish".PHP_EOL;
}
public function onClose($http,$fd){
echo "client is shutdown".$fd.PHP_EOL;
}
}
new Http();
- 一個對象需要多次連接,消耗資源時,可以使用單例模式,僅僅實例化一次,魔術方法_call()使用,簡化代碼
/**
* 設置同步redis,這裏的重寫,是爲了不讓redis每次都重新連接,造成內存資源的浪費
*/
class Predis
{
public $redis = '';
private static $_instance = null;
//採用單例模式設計,
public static function getInstance()
{
if(empty(self::$_instance)){
self::$_instance = new self();
}
return self::$_instance;
}
/**/
public function __construct()
{
$this->redis = new \Redis();
$result = $this->redis->connect(config('redis.host'),
config('redis.port'),config('redis.timeout'));
if($result===false){
throw new \Exception("redis connect error");
}
}
/*
redis ->set()
*/
public function set($key,$value,$time=0)
{
if (!$key) {
return '';
}
if(is_array($value)){
$value = json_encode($value);
}
if(!$time){
$this->redis->set($key,$value);
}
return $this->redis->setex($key,$time,$value);
}
/*
redis->get
*/
public function get($key)
{
if(!$key){
return '';
}
return $this->redis->get($key);
}
/*
redis->add
*/
// public function sAdd($key,$value)
// {
// return $this->redis->sadd($key,$value);
// }
/*
redis->rem
*/
// public function sRem($key,$value)
// {
// return $this->redis->srem($key,$value);
// }
/*
redis->sMember
*/
public function sMembers($key)
{
return $this->redis->sMembers($key);
}
public function __call($name,$arguments)
{
if(count($arguments)==2){
return $this->redis->$name($arguments[0],$arguments[1]);
} else {
return "不等於2";
}
}
- swoole中task任務和work進程的使用,task任務的統一封裝執行
class Task
{
/**
* 異步發送驗證碼
*
* @param type
* @return void
*/
public function sms($value,$serv)
{
try {
$respond = SMS::sendSms($value['phone'],$value['code']);
} catch (\Exception $e) {
echo $e->getMessage();
}
if($respond){
//這裏不能用異步redis來存儲,
// $redis = new \Swoole\Coroutine\Redis();
// $redis->connect(config('redis.host'),config('redis.port'));
// $val = $redis->set(Redis::setRedisKey($phone),config('redis.test_code'),config('redis.out_time'));
//方法一:同步redis
// $val = \Cache::set('key1',123456);
// $getval = \Cache::get('key1');
// 方法二:同步redis
$val = Predis::getInstance()->set($value['phone'],6789);
// $getval = Predis::getInstance()->get($phone);
}else{
return false;
}
return true;
}
public function pushLive($data,$serv)
{
$clients = Predis::getInstance()->sMembers(config('redis.live_game_key'));
foreach ($clients as $fd) {
$serv->push($fd,json_encode($data));
}
}
}