Socket

关于 Socket 控制器使用的具体示例,请查看 demo

安装

composer require easyswoole/socket

独立使用

此组件可脱离主框架使用,方便开发者自行创建服务进行调度,属于socket事件调度器。

use EasySwoole\Socket\AbstractInterface\Controller;
use EasySwoole\Socket\AbstractInterface\ParserInterface;
use EasySwoole\Socket\Bean\Response;
use EasySwoole\Socket\Bean\Caller;

class C extends Controller{

    private $hit = 0;
    protected $hitTime = 0;

    function __construct()
    {
        var_dump('controller create  '.spl_object_hash($this));
        parent::__construct();
    }

    protected function onRequest(?string $actionName): bool
    {

        $this->hit++;
        $this->hitTime = time();
        return true;
    }

    function test()
    {
        var_dump($this->hit,$this->hitTime);
//        co::sleep(10);
        $this->response()->setMessage('time:'.time());
    }

    protected function gc()
    {
        parent::gc(); // TODO: Change the autogenerated stub
        var_dump('controller has ben gc');
    }
}

class Parser implements ParserInterface{

    public function decode($raw, $client): ?Caller
    {
        // TODO: Implement decode() method.
        $ret =  new Caller();
        $ret->setControllerClass(C::class);
        $ret->setAction('test');
        return $ret;
    }

    /*
     * 如果这里返回null,则不给客户端任何数据
     */
    public function encode(Response $response, $client): ?string
    {
        // TODO: Implement encode() method.
        return $response->__toString();
    }

}

$server = new \Swoole\Server("127.0.0.1", 9501);
$server->set([
    'worker_num'=>1
]);

$conf = new \EasySwoole\Socket\Config();
$conf->setType($conf::TCP);
$conf->setParser(new Parser());
$conf->setMaxPoolNum(2);
$conf->setOnExceptionHandler(function (\swoole_server $server,\Throwable $throwable,string $raw,$client,Response $response){
    $response->setStatus('error');
    $response->setStatus($response::STATUS_RESPONSE_AND_CLOSE);
});

$dispatch = new \EasySwoole\Socket\Dispatcher($conf);
$server->on('receive', function ($server, $fd, $reactor_id, $data)use($dispatch) {
    $dispatch->dispatch($server,$data,$fd,$reactor_id);
});
$server->on('close', function ($server, $fd) {
    echo "connection close: {$fd}\n";
});
$server->start();

框架内使用

Tcp

控制器

<?php

namespace App\TcpController;

use EasySwoole\Socket\AbstractInterface\Controller;

class Index extends Controller
{
    public function index()
    {
        $this->response()->setMessage('this is index');
    }
}

解析器

<?php

namespace App\Parser;

use EasySwoole\Socket\AbstractInterface\ParserInterface;
use EasySwoole\Socket\Bean\Caller;
use EasySwoole\Socket\Bean\Response;

class TcpParser implements ParserInterface
{
    public function decode($raw, $client): ?Caller
    {
        $data = substr($raw, '4');
        $data = json_decode($data, true);
        $caller = new Caller();
        $controller = !empty($data['controller']) ? $data['controller'] : 'Index';
        $action = !empty($data['action']) ? $data['action'] : 'index';
        $param = !empty($data['param']) ? $data['param'] : [];
        $controller = "App\\TcpController\\{$controller}";
        $caller->setControllerClass($controller);
        $caller->setAction($action);
        $caller->setArgs($param);
        return $caller;
    }

    public function encode(Response $response, $client): ?string
    {
        return pack('N', strlen($response->getMessage())) . $response->getMessage();
    }
}

注册

mainServerCreate

$config = new \EasySwoole\Socket\Config();
$config->setType($config::TCP);
$config->setParser(TcpParser::class);
$dispatcher = new \EasySwoole\Socket\Dispatcher($config);
$config->setOnExceptionHandler(function (\Swoole\Server $server, \Throwable $throwable, string $raw, \EasySwoole\Socket\Client\Tcp $client, \EasySwoole\Socket\Bean\Response $response) {
    $response->setMessage('system error!');
    $response->setStatus($response::STATUS_RESPONSE_AND_CLOSE);
});
$register->set($register::onReceive, function (\Swoole\Server $server, int $fd, int $reactorId, string $data) use ($dispatcher) {
    $dispatcher->dispatch($server, $data, $fd, $reactorId);
});

Udp

控制器

<?php

namespace App\UdpController;

use EasySwoole\Socket\AbstractInterface\Controller;

class Index extends Controller
{
    public function index()
    {
        $this->response()->setMessage('this is index');
    }
}

解析器

<?php

namespace App\Parser;

use EasySwoole\Socket\AbstractInterface\ParserInterface;
use EasySwoole\Socket\Bean\Caller;
use EasySwoole\Socket\Bean\Response;

class UdpParser implements ParserInterface
{
    public function decode($raw, $client): ?Caller
    {
        $data = json_decode($raw, true);
        $caller = new Caller();
        $controller = !empty($data['controller']) ? $data['controller'] : 'Index';
        $action = !empty($data['action']) ? $data['action'] : 'index';
        $param = !empty($data['param']) ? $data['param'] : [];
        $controller = "App\\UdpController\\{$controller}";
        $caller->setControllerClass($controller);
        $caller->setAction($action);
        $caller->setArgs($param);
        return $caller;
    }

    public function encode(Response $response, $client): ?string
    {
        return json_encode($response->getMessage());
    }
}

注册

mainServerCreate

$config = new \EasySwoole\Socket\Config();
$config->setType($config::UDP);
$config->setParser(UdpParser::class);
$dispatcher = new \EasySwoole\Socket\Dispatcher($config);
$config->setOnExceptionHandler(function (\Swoole\Server $server, \Throwable $throwable, string $raw, \EasySwoole\Socket\Client\Udp $client, \EasySwoole\Socket\Bean\Response $response) {
    $response->setMessage('system error!');
    $response->setStatus($response::STATUS_RESPONSE_AND_CLOSE);
});
$server = \EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer();
$udpServer = $server->addListener('0.0.0.0', '9511', SWOOLE_UDP);
$udpServer->on($register::onPacket, function (\Swoole\Server $server, string $data, array $clientInfo) use ($dispatcher) {
    $dispatcher->dispatch($server, $data, $clientInfo['server_socket'], $clientInfo['address'], $clientInfo['port']);
});

Websocket

控制器

<?php

namespace App\WebSocketController;

use EasySwoole\Socket\AbstractInterface\Controller;

class Index extends Controller
{
    public function index()
    {
        $this->response()->setMessage('this is index');
    }
}

解析器

<?php

namespace App\Parser;

use EasySwoole\Socket\AbstractInterface\ParserInterface;
use EasySwoole\Socket\Bean\Caller;
use EasySwoole\Socket\Bean\Response;

class WebSocketParser implements ParserInterface
{
    public function decode($raw, $client): ?Caller
    {
        $data = json_decode($raw, true);
        $caller = new Caller();
        $controller = !empty($data['controller']) ? $data['controller'] : 'Index';
        $action = !empty($data['action']) ? $data['action'] : 'index';
        $param = !empty($data['param']) ? $data['param'] : [];
        $controller = "App\\WebSocketController\\{$controller}";
        $caller->setControllerClass($controller);
        $caller->setAction($action);
        $caller->setArgs($param);
        return $caller;
    }

    public function encode(Response $response, $client): ?string
    {
        return json_encode($response->getMessage());
    }
}

自定义握手

<?php

namespace App;

class WebSocketEvent
{
    /**
     * @param \Swoole\Http\Request $request
     * @param \Swoole\Http\Response $response
     * @return bool
     */
    public function onHandShake(\Swoole\Http\Request $request, \Swoole\Http\Response $response)
    {
        /** 此处自定义握手规则 返回 false 时中止握手 */
        if (!$this->customHandShake($request, $response)) {
            $response->end();
            return false;
        }

        /** 此处是  RFC规范中的WebSocket握手验证过程 必须执行 否则无法正确握手 */
        if ($this->secWebsocketAccept($request, $response)) {
            $response->end();
            return true;
        }

        $response->end();
        return false;
    }

    /**
     * @param \Swoole\Http\Request $request
     * @param \Swoole\Http\Response $response
     * @return bool
     */
    protected function customHandShake(\Swoole\Http\Request $request, \Swoole\Http\Response $response): bool
    {
        /**
         * 这里可以通过 http request 获取到相应的数据
         * 进行自定义验证后即可
         * (注) 浏览器中 JavaScript 并不支持自定义握手请求头 只能选择别的方式 如get参数
         */
        $headers = $request->header;
        $cookie = $request->cookie;

        // if (如果不满足我某些自定义的需求条件,返回false,握手失败) {
        //    return false;
        // }
        return true;
    }

    /**
     * RFC规范中的WebSocket握手验证过程
     * 以下内容必须强制使用
     *
     * @param \Swoole\Http\Request $request
     * @param \Swoole\Http\Response $response
     * @return bool
     */
    protected function secWebsocketAccept(\Swoole\Http\Request $request, \Swoole\Http\Response $response): bool
    {
        // ws rfc 规范中约定的验证过程
        if (!isset($request->header['sec-websocket-key'])) {
            // 需要 Sec-WebSocket-Key 如果没有拒绝握手
            var_dump('shake fai1 3');
            return false;
        }
        if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])
            || 16 !== strlen(base64_decode($request->header['sec-websocket-key']))
        ) {
            //不接受握手
            var_dump('shake fai1 4');
            return false;
        }

        $key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        $headers = array(
            'Upgrade' => 'websocket',
            'Connection' => 'Upgrade',
            'Sec-WebSocket-Accept' => $key,
            'Sec-WebSocket-Version' => '13',
            'KeepAlive' => 'off',
        );

        if (isset($request->header['sec-websocket-protocol'])) {
            $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
        }

        // 发送验证后的header
        foreach ($headers as $key => $val) {
            $response->header($key, $val);
        }

        // 接受握手 还需要101状态码以切换状态
        $response->status(101);
        var_dump('shake success at fd :' . $request->fd);
        return true;
    }
}

注册

mainServerCreate

<?php
$config = new \EasySwoole\Socket\Config();
$config->setType($config::WEB_SOCKET);
$config->setParser(WebSocketParser::class);
$dispatcher = new \EasySwoole\Socket\Dispatcher($config);
$config->setOnExceptionHandler(function (\Swoole\Server $server, \Throwable $throwable, string $raw, \EasySwoole\Socket\Client\WebSocket $client, \EasySwoole\Socket\Bean\Response $response) {
    $response->setMessage('system error!');
    $response->setStatus($response::STATUS_RESPONSE_AND_CLOSE);
});

// 自定义握手
/*$websocketEvent = new WebSocketEvent();
$register->set(EventRegister::onHandShake, function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) use ($websocketEvent) {
    $websocketEvent->onHandShake($request, $response);
});*/

$register->set($register::onMessage, function (\Swoole\Websocket\Server $server, \Swoole\Websocket\Frame $frame) use ($dispatcher) {
    $dispatcher->dispatch($server, $frame->data, $frame);
});