Fork me on GitHub

swoole创建websocket服务器


swoole算是nodejs在php中的一种实现,异步响应请求,性能超强

1 安装准备

1.1 安装swoole前必须保证系统已经安装了下列软件

php-5.3.10 或更高版本
gcc-4.4 或更高版本
make
autoconf
pcre (centos系统可以执行命令:yum install pcre-devel)

1.2 下载并解压

下载地址 https://github.com/swoole/swoole-src/releases
进入页面后选择download链接下的tar.gz的压缩包
下载源代码包后,解压

tar xzvf xxx.tar.gz  

在终端进入源码目录,执行下面的命令进行编译和安装

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

编译参数根据自己的需求选择,详情参看官方文档。

1.3 编译安装成功后,修改php.ini

在php.ini中加入 extension=swoole.so
通过在命令行使用 php-m查看,是否安装了swoole

注意:如通重新编译的话需要 make clean

2 构建Swoole基本实例

2.1 tcp服务器实例

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

服务端代码: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

posted @ 2017-11-15 08:57  archer-wong  阅读(2850)  评论(0编辑  收藏  举报