這個列子主要討論Tcp
,WebSocket
和http
之間的通訊。長連線和長連線通訊,長連線和短連線通訊。其他協議同理可得
本列子是基於one框架 (https://github.com/lizhichao/one) 開發.
配置協議 監聽埠
由於swoole的模型 WebSocket server 包含 http server , http server 包含 tcp server 。
所以我們配置主服務為 WebSocket server ,新增兩個http 和 tcp 監聽。配置檔案如下:
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請求的,配置路由即可。
配置路由
// 首頁
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框架的路由支援任何協議,使用方法也是統一的。
處理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 協議
其中__construct
,onHandShake
,onOpen
,onClose
不是必須的。
onHandShake
,onOpen
是配合使用的,如果onOpen
返回false伺服器會拒絕連線。
在 onOpen
,onMessage
,onClose
可以拿到當前使用者的session資訊和http是相通的。
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);
}
}
到此基本就完成了。你可以去看完整的程式碼 : 點這裡
其他的一些列子 : https://github.com/lizhichao/one-demo
本作品採用《CC 協議》,轉載必須註明作者和本文連結