學習swoole筆記

swoole是什麼

swoole是用來補缺php異步處理缺陷的新技術,支持協程,用戶也可以完全拋開原來的fpm模式調用,而直接使用php+swoole開發項目,能更加處理網站的高併發,大數據問題;它是php異步網絡通訊引擎;異步多線程;

swoole用來做什麼

在線直播,聊天室,遊戲行業

swoole如何用

安裝php,swoole就是php的一個擴展,可以用在http,websocket等請求。

swoole課程1

swoole課程2-php源碼編譯安裝

  1. configure (shell腳本):主要用於編譯安裝源代碼庫和軟件,對源代碼庫進行一些設置(需要gcc,autoconfig);eg:./configure --prefix=/home/usr/local/php (安裝路徑)
  2. make -j (構建\編譯)
  3. 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編譯安裝

  1. 下載: git clone / 下載上傳壓縮包,直接解壓
  2. 需要先用phpize 先生成configure 文件,phpize是用來安裝php擴展模塊的。在swoole的文件目錄下,/home/usr/lcoal/bin/phpize 會自動生成 configure 腳本文件
  3. 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;
  4. 查看php的模塊 php -m

swoole課程4-網絡通訊

tcp基本四步走

  1. 創建server對象
  2. 監聽連接進入事件
  3. 監聽數據接收事件; 發送客戶端, serv>send(serv->send(fd, "Server: ".$data);
  4. 監聽關閉事件
  5. 啓動服務器
$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基本三步走

  1. 創建server對象
  2. UDP服務器與TCP服務器不同,UDP沒有連接的概念。啓動Server後,客戶端無需Connect,直接可以向Server監聽的9502端口發送數據包;裏邊傳輸數據用server>sendto(server->sendto(address,port,port,data).
  3. 啓動服務器
$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服務基本三步走

  1. 創建對象
  2. 設置靜態資源目錄
  3. 接收前端數據,響應返回數據
  4. 開啓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
  1. 創建對象
  2. 監聽websocket連接打開事件
  3. 監聽ws消息事件:(onMessage),必須實現的事件,裏邊推送數據用server>push(server->push(fd,$data);
  4. 關閉ws消息事件
  5. 開啓服務
$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);data){};這裏的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。
優勢:

  1. 開發者可以再無感知情況下使用同步編程方式實現異步操作,避免了異步回調帶來離散代碼邏輯和多重調用的無法維護障礙
  2. 相較於傳統的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-賽事直播

開發思想學習

  1. 配置項多處使用時建立配置文件,eg:redis.php
return [
  'host'=>'127.0.0.1',
  'port'=>'6379',
  'out_time'=>120,
  'timeout'=>5,
  'test_code'=>6789,
  'live_game_key'=>'clientKey',
];

  1. 封裝對象,美化代碼,是面向過程的開發轉爲面向對象
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();
  1. 一個對象需要多次連接,消耗資源時,可以使用單例模式,僅僅實例化一次,魔術方法_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";
    }
  }
  1. 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));
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章