一個簡單混合協議通信列子,物聯網和互聯網通信。

這個列子主要討論TcpWebSockethttp之間的通信。長鏈接和長鏈接通信,長鏈接和短鏈接通信。其餘協議同理可得php

Tcp: 表明硬件設備
WebSocket: 表明客戶端 
http: 表明網頁  
複製代碼

本列子是基於one框架 github.com/lizhichao/o… 開發.react

配置協議 監聽端口

因爲swoole的模型 WebSocket server 包含 http server , http server 包含 tcp server 。git

因此咱們配置主服務爲 WebSocket server ,添加兩個http 和 tcp 監聽。配置文件以下:github

return [
    'server' => [
        'server_type' => \One\Swoole\OneServer::SWOOLE_WEBSOCKET_SERVER,
        'port' => 8082,
        'action' => \App\Test\MixPro\Ws::class,
        'mode' => SWOOLE_PROCESS,
        'sock_type' => SWOOLE_SOCK_TCP,
        'ip' => '0.0.0.0',
        'set' => [
            'worker_num' => 5
        ]
    ],
    'add_listener' => [
        // http 監聽
        [
            'port' => 8081,
            'action' => \App\Server\AppHttpPort::class, 
            'type' => SWOOLE_SOCK_TCP,
            'ip' => '0.0.0.0',
            'set' => [
                'open_http_protocol' => true,
                'open_websocket_protocol' => false
            ]
        ],
        // tcp 監聽
        [
            'port' => 8083,
            'pack_protocol' => \One\Protocol\Text::class, // tcp 打包,解包協議,方便在終端調試 咱們使用 text 協議. 換行符 表示一個包的結束
            'action' => \App\Test\MixPro\TcpPort::class,
            'type' => SWOOLE_SOCK_TCP,
            'ip' => '0.0.0.0',
            'set' => [
                'open_http_protocol' => false,
                'open_websocket_protocol' => false
            ]
        ]
    ]
];

複製代碼

接下來去 \App\Test\MixPro\Ws\App\Test\MixPro\TcpPort 實現各類事件處理。 \App\Server\AppHttpPort 是框架內置的,經過路由處理http請求的,配置路由便可。web

配置路由

// 首頁
Router::get('/mix', [
    'use'    => HttpController::class . '@index',
    'middle' => [\App\Test\MixPro\TestMiddle::class . '@isLogin'] // 中間件 若是用戶登陸了 直接跳轉到相應的頁面
]);

Router::group([
        'middle' => [\App\Test\MixPro\TestMiddle::class . '@checkSession'] // 中間件 讓用戶登陸後 才能進入聊天頁面 http websocket 都能獲取到這個 session
    ], function () {

    // websocket 頁面
    Router::get('/mix/ws', HttpController::class . '@ws');
    
    // http 頁面
    Router::get('/mix/http', HttpController::class . '@http');
    
    // http 輪訓消息接口
    Router::post('/mix/http/loop', HttpController::class . '@httpLoop');
    
    // http 發送消息接口
    Router::post('/mix/http/send', HttpController::class . '@httpSend');

});

複製代碼

配置的都是 http 協議路由。 websocket和tpc咱們直接在回調action處理。若是你的項目複雜也能夠配置相應的路由。one框架的路由支持任何協議,使用方法也是統一的。json

處理tcp協議

其中__construct,onConnect,onClose 不是必須的。
若是你想在服務器運行開始時最一些事情就寫到 __construct裏面。
onConnect 當有客戶端鏈接時觸發,每一個客戶端觸發一次
onClose 當有客戶端鏈接斷開時觸發,每一個客戶端觸發一次服務器

class TcpPort extends Tcp {
    use Funs;

    private $users = [];

    /** * @var Ws */
    protected $server;

    /** * @var Client */
    protected $global_data;

    public function __construct($server, $conf) {
        parent::__construct($server, $conf);
        $this->global_data = $this->server->global_data;
    }

    // 終端鏈接上服務器時
    public function onConnect(\swoole_server $server, $fd, $reactor_id) {
        $name             = uuid();
        $this->users[$fd] = $name;
        $this->sendTo('all', json_encode(['v' => 1, 'n' => $name]));
        $this->sendToTcp($fd, json_encode(['v' => 4, 'n' => $this->getAllName()]));
        $this->global_data->bindId($fd, $name);
        $this->send($fd, "你的名字是:" . $name);
    }

    // 消息處理 像某個name 發送消息
    public function onReceive(\swoole_server $server, $fd, $reactor_id, $data) {
        $arr = explode(' ', $data);
        if (count($arr) !== 3 || $arr[0] !== 'send') {
            $this->send($fd, "格式不正確");
            return false;
        }
        $n = $arr[1];
        $d = $arr[2];
        $this->sendTo($n, json_encode(['v' => 3, 'n' => $d]));
    }

    // 下線 通知全部其餘終端,解除與fd的關係綁定。
    public function onClose(\swoole_server $server, $fd, $reactor_id) {
        echo "tcp close {$fd} \n";
        $this->global_data->unBindFd($fd);
        $this->sendTo('all', json_encode(['v' => 2, 'n' => $this->users[$fd]]));
        unset($this->users[$fd]);
    }

}

複製代碼

定義了一個公共的traitFuns主要實現兩個方法,獲取全部的終端(tcp,ws,http),和向某個用戶發送消息 。在ws、http都會用到這個
在構造函數咱們初始化了一個 global_data 用來保存,名稱和fd的關係。你也可使用方式儲存。由於fd沒次鏈接都不一樣。global_data是one框架內置的。
終端鏈接上服務器時觸發事件 onConnect ,咱們給這個終端取個名字,並把關係保存在 global_data。 通知全部終端有個新終端加入,並告訴剛加入的終端當前有哪些終端在線。websocket

處理 websocket 協議

其中__construct,onHandShake,onOpenonClose 不是必須的。swoole

onHandShake,onOpen 是配合使用的,若是onOpen返回false服務器會拒絕鏈接。 在 onOpenonMessageonClose能夠拿到當前用戶的session信息和http是相通的。session

class Ws extends WsServer {
    use Funs;

    private $users = [];

    /** * @var Client */
    public $global_data = null;

    public function __construct(\swoole_server $server, array $conf) {
        parent::__construct($server, $conf);
        $this->global_data = new Client();
    }
    
    // 初始化session
    public function onHandShake(\swoole_http_request $request, \swoole_http_response $response) {
        return parent::onHandShake($request, $response);
    }

    // ws 發送消息
    public function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame) {
        $data = $frame->data;
        $arr  = json_decode($data, true);
        $n    = $arr['n'];
        $d    = $arr['d'];
        $this->sendTo($n, json_encode(['v' => 3, 'n' => $d]));

    }

    // 判斷用戶是否登陸 若是沒有登陸拒絕鏈接
    public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request) {
        $name = $this->session[$request->fd]->get('name');
        if ($name) {
            $this->users[$request->fd] = $name;
            $this->sendTo('all', json_encode(['v' => 1, 'n' => $name]));
            $this->global_data->bindId($request->fd, $name);
            return true;
        } else {
            return false;
        }
    }

    // ws 斷開清除信息
    public function onClose(\swoole_server $server, $fd, $reactor_id) {
        echo "ws close {$fd} \n";
        $this->global_data->unBindFd($fd);
        $this->sendTo('all', json_encode(['v' => 2, 'n' => $this->users[$fd]]));
        unset($this->users[$fd]);
    }
}

複製代碼

處理 http 協議

主要是 httpLoop 方法,輪訓獲取消息。由於http是短鏈接,發給http的信息咱們是先存放在$global_data,而後直接這裏讀取。防止鏈接間隙丟信息。

class HttpController extends Controller {

    use Funs;

    /** * @var Ws */
    protected $server;

    /** * @var Client */
    protected $global_data;


    public function __construct($request, $response, $server = null) {
        parent::__construct($request, $response, $server);
        $this->global_data = $this->server->global_data;
    }

    /** * 首頁 */
    public function index() {
        $code = sha1(uuid());
        $this->session()->set('code', $code);
        return $this->display('index', ['code' => $code]);
    }

    /** * ws頁面 */
    public function ws() {
        $name = $this->session()->get('name');
        if (!$name) {
            $name = uuid();
            $this->session()->set('name', $name);
        }
        return $this->display('ws',['users' => $this->getAllName(),'name' => $name]);
    }

    /** * http 頁面 */
    public function http() {
        $name = $this->session()->get('name');
        if (!$name) {
            $name = uuid();
            $this->session()->set('name', $name);
        }
        $this->global_data->set("http.{$name}", 1, time() + 60);
        $this->sendTo('all', json_encode(['v' => 1, 'n' => $name]));
        return $this->display('http', ['list' => $this->getAllName(), 'name' => $name]);
    }

    /** * http輪訓 */
    public function httpLoop() {
        $name = $this->session()->get('name');
        $this->global_data->set("http.{$name}", 1, time() + 60);
        $i = 0;
        do {
            $data = $this->global_data->getAndDel("data.{$name}");
            $i++;
            \co::sleep(0.1);
        } while ($data === null && $i < 300);
        if ($data) {
            foreach ($data as &$v) {
                $v = json_decode($v, true);
            }
        } else {
            $data = [];
        }
        return $this->json($data);
    }

    /** * http發送消息 */
    public function httpSend() {
        $n = $this->request->post('n');
        $d = $this->request->post('d');
        if ($n && $d) {
            $this->sendTo($n, json_encode(['v' => 3, 'n' => $d]));
            return '1';
        }
        return '0';
    }

    public function __destruct() {

    }

    public function __call($name, $arguments) {
        return $this->server->$name(...$arguments);
    }

}

複製代碼

到此基本就完成了。你能夠去看完整的代碼 : 點這裏

其餘的一些列子 : github.com/lizhichao/o…

相關文章
相關標籤/搜索