swoole建立websocket服務器

swoole算是nodejs在php中的一種實現,異步響應請求,性能超強html

1 安裝準備

1.1 安裝swoole前必須保證系統已經安裝了下列軟件

php-5.3.10 或更高版本
gcc-4.4 或更高版本
make
autoconf
pcre (centos系統能夠執行命令:yum install pcre-devel)node

1.2 下載並解壓

下載地址 https://github.com/swoole/swoole-src/releases
進入頁面後選擇download連接下的tar.gz的壓縮包
下載源代碼包後,解壓laravel

tar xzvf xxx.tar.gz

在終端進入源碼目錄,執行下面的命令進行編譯和安裝git

cd swoole  
phpize  
./configure --enable-swoole-debug  
make   
sudo make install

編譯參數根據本身的需求選擇,詳情參看官方文檔。github

1.3 編譯安裝成功後,修改php.ini

在php.ini中加入 extension=swoole.so
經過在命令行使用 php-m查看,是否安裝了swooleweb

注意:如通從新編譯的話須要 make cleanjson

2 構建Swoole基本實例

2.1 tcp服務器實例

(來自w3cschool教程https://www.w3cschool.cn/swoole/bnte1qcd.html)centos

服務端代碼:Server.php瀏覽器

<?php
// Server
class Server
{
    private $serv;

    public function __construct() {
        $this->serv = new swoole_server("0.0.0.0", 9501);
        $this->serv->set(array(
            'worker_num' => 8,
            'daemonize' => false,
            'max_request' => 10000,
            'dispatch_mode' => 2,
            'debug_mode'=> 1
        ));

        $this->serv->on('Start', array($this, 'onStart'));
        $this->serv->on('Connect', array($this, 'onConnect'));
        $this->serv->on('Receive', array($this, 'onReceive'));
        $this->serv->on('Close', array($this, 'onClose'));

        $this->serv->start();
    }

    public function onStart( $serv ) {
        echo "Start\n";
    }

    public function onConnect( $serv, $fd, $from_id ) {
        $serv->send( $fd, "Hello {$fd}!" );
    }

    public function onReceive( swoole_server $serv, $fd, $from_id, $data ) {
        echo "Get Message From Client {$fd}:{$data}\n";
    }

    public function onClose( $serv, $fd, $from_id ) {
        echo "Client {$fd} close connection\n";
    }
}
// 啓動服務器
$server = new Server();

從代碼中能夠看出,建立一個swoole_server基本分三步:

  1. 經過構造函數建立swoole_server對象
  2. 調用set函數設置swoole_server的相關配置選項
  3. 調用on函數設置相關回調函數 關於set配置選項以及on回調函數的具體說明,請參考我整理的swoole文檔( 配置選項)
    這裏只給出簡單介紹。onStart回調在server運行前被調用,onConnect在有新客戶端鏈接過來時被調用,onReceive函數在有數據發送到server時被調用,onClose在有客戶端斷開鏈接時被調用。 這裏就能夠大概看出如何使用swoole:在onConnect處監聽新的鏈接;在onReceive處接收數據並處理,而後能夠調用send函數將處理結果發送出去;在onClose到處理客戶端下線的事件。

客戶端的代碼:Client.php

<?php
class Client
{
    private $client;

    public function __construct() {
        $this->client = new swoole_client(SWOOLE_SOCK_TCP);
    }

    public function connect() {
        if( !$this->client->connect("127.0.0.1", 9501 , 1) ) {
            echo "Error: {$fp->errMsg}[{$fp->errCode}]\n";
        }
        $message = $this->client->recv();
        echo "Get Message From Server:{$message}\n";

        fwrite(STDOUT, "請輸入消息:");  
        $msg = trim(fgets(STDIN));
        $this->client->send( $msg );
    }
}

$client = new Client();
$client->connect();

這裏,經過swoole_client建立一個基於TCP的客戶端實例,並調用connect函數向指定的IP及端口發起鏈接請求。隨後便可經過recv()和send()兩個函數來接收和發送請求。須要注意的是,這裏我使用了默認的同步阻塞客戶端,所以recv和send操做都會產生網絡阻塞。

使用方法

進入到文件目錄,在窗口1先啓動php Serve.php,而後再開一個窗口(窗口2)啓動php Client.php
窗口1內容:

# root @ WENGINE in /data/learnSwoole [9:24:57] C:130
$ php Server.php
Start
Get Message From Client 1:ceshi1
Client 1 close connection

窗口2內容:

# root @ WENGINE in /data/learnSwoole [9:23:07] 
$ php Client.php
Get Message From Server:Hello 1!
請輸入消息:ceshi1

2.2 web服務器

服務端代碼 http_server.php

$http = new swoole_http_server("0.0.0.0", 9501);

$http->on('request', function ($request, $response) {
    var_dump($request->get, $request->post);
    $response->header("Content-Type", "text/html; charset=utf-8");
    $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});

$http->start();

Http服務器只須要關注請求響應便可,因此只須要監聽一個onRequest事件。當有新的Http請求進入就會觸發此事件。事件回調函數有2個參數,一個是$request對象,包含了請求的相關信息,如GET/POST請求的數據。
另一個是response對象,對request的響應能夠經過操做response對象來完成。$response->end()方法表示輸出一段HTML內容,並結束此請求。
● 0.0.0.0 表示監聽全部IP地址,一臺服務器可能同時有多個IP,如127.0.0.1本地迴環IP、192.168.1.100局域網IP、210.127.20.2 外網IP,這裏也能夠單獨指定監聽一個IP
● 9501 監聽的端口,若是被佔用程序會拋出致命錯誤,中斷執行。

2.3 WebSocket服務器

服務端程序代碼 ws_server.php

//建立websocket服務器對象,監聽0.0.0.0:9502端口
$ws = new swoole_websocket_server("0.0.0.0", 9502);

//監聽WebSocket鏈接打開事件
$ws->on('open', function ($ws, $request) {
    var_dump($request->fd, $request->get, $request->server);
    $ws->push($request->fd, "hello, welcome\n");
});

//監聽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();

WebSocket服務器是創建在Http服務器之上的長鏈接服務器,客戶端首先會發送一個Http的請求與服務器進行握手。握手成功後會觸發onOpen事件,表示鏈接已就緒,onOpen函數中能夠獲得$request對象,包含了Http握手的相關信息,如GET參數、Cookie、Http頭信息等。
創建鏈接後客戶端與服務器端就能夠雙向通訊了。
● 客戶端向服務器端發送信息時,服務器端觸發onMessage事件回調
● 服務器端能夠調用$server->push()向某個客戶端(使用$fd標識符)發送消息
● 服務器端能夠設置onHandShake事件回調來手工處理WebSocket握手
運行程序

客戶端的代碼

可使用Chrome瀏覽器進行測試,JS代碼爲:

var wsServer = 'ws://127.0.0.1:9502';
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);
};

websocket.onerror = function (evt, e) {
    console.log('Error occured: ' + evt.data);
};
  • 不能直接使用swoole_client與websocket服務器通訊,swoole_client是TCP客戶端
  • 必須實現WebSocket協議才能和WebSocket服務器通訊,可使用swoole/framework提供的PHP WebSocket客戶端
  • WebSocket服務器除了提供WebSocket功能以外,實際上也能夠處理Http長鏈接。只須要增長onRequest事件監聽便可實現Comet方案Http長輪詢。

關於onRequest回調
swoole_websocket_server 繼承自 swoole_http_server

  • 設置了onRequest回調,websocket服務器也能夠同時做爲http服務器
  • 未設置onRequest回調,websocket服務器收到http請求後會返回http 400錯誤頁面
  • 若是想經過接收http觸發全部websocket的推送,須要注意做用域的問題,面向過程請使用「global」對swoole_websocket_server進行引用,面向對象能夠把swoole_websocket_server設置成一個成員屬性

能夠建立更多的服務器 參照官方文檔嘗試
https://wiki.swoole.com/wiki/page/475.html

3 使用laravel5.5實現的先後臺通訊實例

主要思路是使用php artisan 自建命令控制服務端,使用HTML5的websocket實現客戶端功能

服務端:app/Console/Commands/Websocket.php內容

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use swoole_http_request;
use swoole_http_response;
use swoole_websocket_server;

class WebSocket extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'websocket 
                            {cmd=start : can use start|stop|status|restart}
                            {--daemon : set to run in daemonize mode}
                            ';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'swoole server control';


    /**
     * server
     *
     * @var swoole_websocket_server
     */
    private $server;

    /**
     *      * TYPE_ADMIN
     *           */
    const TYPE_ADMIN = 0X00;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * 處理不一樣command信息
     *
     * @return mixed
     */
    public function handle()
    {
        $command = $this->argument('cmd');
        $option = $this->option('daemon');

        switch ($command) {
            case 'start':
                $this->initWs($option);
                break;
            case 'stop':
                $res = $this->sendAdminRequest($command);
                if ($res){
                    $this->info('stop the server successfully');
                } else {
                    $this->info('the server is not running');
                }
                break;
            case 'status':
                $res = $this->sendAdminRequest($command);
                if ($res){
                    $this->info($res);
                } else {
                    $this->info('the server is not running');
                }
                break;
            case 'restart':
                $res = $this->sendAdminRequest($command);
                if ($res){
                    $this->info('restart the server successfully');
                } else {
                    $this->info('the server is not running');
                }
                break;
            default:
                $this->info('請按照下面格式輸入命令:php artisan websocket {start|stop|status|restart}');
                break;
        }

    }
    
    //初始化服務端
    public function initWs($daemonize = false) 
    {
        if ($daemonize) {
            $this->info('Starting Websocke server in daemon mode...');
        } else {
            $this->info('Starting Websocke server in interactive mode...');
        }

        $server = new swoole_websocket_server('0.0.0.0', 9501);
        $server->set([
            'daemonize' => $daemonize,
            'log_file' => '/var/log/websocket.log'
        ]);

        $server->on('close', function ($server, $fd) {
            $this->info('close websocket server');
        });

        $server->on('open', function (swoole_websocket_server $server, $request) {
            $this->info('websocket open');
        });

        $server->on('open', [$this, 'onOpen']);
        $server->on('close', [$this, 'onClose']);
        $server->on('message', [$this, 'onMessage']);
        $server->on('request', [$this, 'onRequest']);

        $this->server = $server;
        $this->server->start();
    }

    public function onOpen(swoole_websocket_server $server, $request) 
    {
            $this->info('websocket open');
    }

    public function onClose($server, $fd) 
    {
            $this->info('close websocket server');
    }

    public function onMessage(swoole_websocket_server $server, $frame) 
    {
        $this->info($frame->data);
        $data = json_decode($frame->data, true);

        //對data進行邏輯處理
        $reply = '發送的信息是:' . $data['message'];
        $response = [
            'status' => true,
            'data' => $reply
        ];
        $server->push($frame->fd, json_encode($response));
    }

    //websocket客戶端一樣支持http協議
    public function onRequest(swoole_http_request $request, swoole_http_response $response) 
    {
        if ($request->post['type'] == self::TYPE_ADMIN) {
            $ret = json_encode($this->commandHandle($request->post['content']));
            return $response->end($ret);
        }
    }

    //操做命名
    public function commandHandle($command) {
        if ($command == 'status') {
            $this->info('handle status');
            return $this->server->stats();
        }
        if ($command == 'restart') {
            $this->info('handle restart');
            return $this->server->reload();
        }
        if ($command == 'stop') {
            $this->info('handle stop');
            return $this->server->stop() && $this->server->shutdown();
        }
        return 'Unknown Command';
    }

    //發送http請求
    public function sendAdminRequest($content) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:9501");
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
            'type' => self::TYPE_ADMIN,
            'content' => $content
        ]);

        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }

}

客戶端內容

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>websocket client</title>
    </head>
    <body>
        <div>
            <input id="message-content" type="text" name="message" />
            <button onclick="sendMessage()">發送消息</button>
        </div>
    </body>
    <script>
        var wsServer = 'ws://115.159.81.46: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('從服務器接收到json信息: ' + evt.data);
            alert('服務器返回信息:' + JSON.parse(evt.data).data);
        };

        websocket.onerror = function (evt, e) {
            console.log('Error occured: ' + evt.data);
        };

        function sendMessage(){
            var content = document.getElementById('message-content').value;
            var data = {
                message : content,
            }
            websocket.send(JSON.stringify(data));
        };
    </script>
</html>

啓動websocket服務器

進入系統根目錄,

php artisan websocket [--daemon] //是否使用daemon模式 php artisan websocket start|stop|status|restart //默認是start

相關文章
相關標籤/搜索