1. 安裝socket包
composer require easyswoole/socket
2.注冊服務(wù)
public static function mainServerCreate(EventRegister $register)
{
//創(chuàng)建一個 Dispatcher 配置
$conf = new \EasySwoole\Socket\Config();
//設(shè)置Dispatcher為WebSocket 模式
$conf->setType(\EasySwoole\Socket\Config::WEB_SOCKET);
try {
$conf->setParser(new WebSocketParser());//設(shè)置解析器對象
$dispatch = new Dispatcher($conf); //創(chuàng)建Dispatcher對象并注入config對象
} catch (\Exception $e) {
}
//給server注冊相關(guān)事件在WebSocket模式下onMessage事件必須注冊 并且交給Dispatcher對象處理
$register->set(EventRegister::onMessage, function (\swoole_websocket_server $server, \swoole_websocket_frame $frame) use ($dispatch) {
$dispatch->dispatch($server, $frame->data, $frame);
});
$websocketEvent = new WebSocketEvent();
//自定義握手事件
$register->set(EventRegister::onHandShake, function (\swoole_http_request $request, \swoole_http_response $response) use ($websocketEvent) {
$websocketEvent->onHandShake($request, $response);
});
//自定義關(guān)閉事件
$register->set(EventRegister::onClose, function (\swoole_server $server, int $fd, int $reactorId) use ($websocketEvent) {
$websocketEvent->onClose($server, $fd, $reactorId);
});
}
3.配置服務(wù)類型
return [
'SERVER_NAME' => "YZGWCanteenServer",
'MAIN_SERVER' => [
'LISTEN_ADDRESS' => '0.0.0.0',
'PORT' => 9501,
'SERVER_TYPE' => EASYSWOOLE_WEB_SOCKET_SERVER, //可選為 EASYSWOOLE_SERVER EASYSWOOLE_WEB_SERVER EASYSWOOLE_WEB_SOCKET_SERVER
'SOCK_TYPE' => SWOOLE_TCP,
'RUN_MODEL' => SWOOLE_PROCESS,
'SETTING' => [
'worker_num' => 8,
'reload_async' => true,
'max_wait_time' => 3
],
'TASK' => [
'workerNum' => 4,
'maxRunningNum' => 128,
'timeout' => 15
]
],
4.自定義時事件
<?php
/**
* Description:this is description
* User:gan
* Date:2021/9/23
* Time:11:42 上午
*/
namespace App\WebSocket;
/**
* Class WebSocketEvent
* 此類是 WebSocet 中一些非強制的自定義事件處理
* @package App\WebSocket
*/
class WebSocketEvent
{
/**
* 握手事件
* 所有客戶端建立連接時觸發(fā)的方法
* @param \swoole_http_request $request
* @param \swoole_http_response $response
* @return bool
*/
public function onHandShake(\swoole_http_request $request, \swoole_http_response $response)
{
$time = time();
echo "{$request->fd}創(chuàng)建連接事件 : {$time} \n";
/** 此處自定義握手規(guī)則 返回 false 時中止握手 */
if (!$this->customHandShake($request, $response)) {
$response->end();
return false;
}
/** 此處是 RFC規(guī)范中的WebSocket握手驗證過程 必須執(zhí)行 否則無法正確握手 */
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 獲取到相應(yīng)的數(shù)據(jù)
* 進行自定義驗證后即可
* (注) 瀏覽器中 JavaScript 并不支持自定義握手請求頭 只能選擇別的方式 如get參數(shù)
*/
$headers = $request->header;
$cookie = $request->cookie;
// if (如果不滿足我某些自定義的需求條件卷拘,返回false蛔琅,握手失敗) {
// return false;
// }
return true;
}
/**
* 關(guān)閉事件
* 所有客戶端關(guān)閉時觸發(fā)的方法
* @param \swoole_server $server
* @param int $fd
* @param int $reactorId
*/
public function onClose(\swoole_server $server, int $fd, int $reactorId)
{
/** @var array $info */
$info = $server->getClientInfo($fd);
/**
* 判斷此fd 是否是一個有效的 websocket 連接
* 參見 https://wiki.swoole.com/wiki/page/490.html
*/
if ($info && $info['websocket_status'] === WEBSOCKET_STATUS_FRAME) {
$time = time();
echo "{$fd}觸發(fā)關(guān)閉事件 : {$time} \n";
print_r($info);
/**
* 判斷連接是否是 server 主動關(guān)閉
* 參見 https://wiki.swoole.com/wiki/page/p-event/onClose.html
*/
if ($reactorId < 0) {
echo "server close \n";
}
}
}
/**
* RFC規(guī)范中的WebSocket握手驗證過程
* 以下內(nèi)容必須強制使用
*
* @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 規(guī)范中約定的驗證過程
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'];
}
// 發(fā)送驗證后的header
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
// 接受握手 還需要101狀態(tài)碼以切換狀態(tài)
$response->status(101);
//var_dump('shake success at fd :' . $request->fd);
return true;
}
}
5.自定義解析器
<?php
/**
* Description:this is description
* User:ligan
* Date:2021/9/23
* Time:11:42 上午
*/
namespace App\WebSocket;
use EasySwoole\Socket\AbstractInterface\ParserInterface;
use EasySwoole\Socket\Bean\Caller;
use EasySwoole\Socket\Bean\Response;
use EasySwoole\Socket\Client\WebSocket as WebSocketClient;
class WebSocketParser implements ParserInterface
{
/**
* 解碼上來的消息
* @param string $raw 消息內(nèi)容
* @param WebSocketClient $client 當(dāng)前的客戶端
* @return Caller|null
*/
public function decode($raw, $client): ?Caller
{
$caller = new Caller;
// 聊天消息 {"controller":"broadcast","action":"roomBroadcast","params":{"content":"111"}}
if ($raw !== 'PING') {
$payload = json_decode($raw, true);
$class = isset($payload['controller']) ? $payload['controller'] : 'index';
$action = isset($payload['action']) ? $payload['action'] : 'actionNotFound';
$params = isset($payload['params']) ? (array)$payload['params'] : [];
$controllerClass = "\\App\\WebSocket\\Controller\\" . ucfirst($class);
if (!class_exists($controllerClass)) $controllerClass = "\\App\\WebSocket\\Controller\\Index";
$caller->setClient($caller);
$caller->setControllerClass($controllerClass);
$caller->setAction($action);
$caller->setArgs($params);
} else {
// 設(shè)置心跳執(zhí)行的類和方法
$caller->setControllerClass(\App\WebSocket\Controller\Index::class);
$caller->setAction('heartbeat');
}
return $caller;
}
/**
* 打包下發(fā)的消息
* @param Response $response 控制器返回的響應(yīng)
* @param WebSocketClient $client 當(dāng)前的客戶端
* @return string|null
*/
public function encode(Response $response, $client): ?string
{
return $response->getMessage();
}
}
6.業(yè)務(wù)代碼
Base.php
<?php
/**
* Description:this is description
* User:ligan
* Date:2021/9/23
* Time:11:43 上午
*/
namespace App\WebSocket\Controller;
use EasySwoole\Socket\AbstractInterface\Controller;
use EasySwoole\Socket\Client\WebSocket as WebSocketClient;
use Exception;
/**
* 基礎(chǔ)控制器
* Class Base
* @package App\WebSocket\Controller
*/
class Base extends Controller
{
protected function actionNotFound(?string $actionName)
{
echo "您的請求 {$actionName} 不存在 ... \n";
}
protected function afterAction(?string $actionName)
{
echo "請求之后執(zhí)行 \n";
}
}
Index.php
<?php
/**
* Description:this is description
* User:ligan
* Date:2021/9/23
* Time:11:43 上午
*/
namespace App\WebSocket\Controller;
class Index extends Base
{
/**
* 心跳執(zhí)行的方法
* User:ligan
* Date:2021/9/23
* Time:11:57 上午
*/
public function heartbeat()
{
$this->response()->setMessage("PONG"); // 推送消息
}
public function index()
{
$fd = $this->caller()->getClient()->getFd(); // 請求用戶的fd
$data = $this->caller()->getArgs(); // 獲取請求參數(shù)
$this->response()->setMessage('your fd is ' . $fd . json_encode($data)); // 推送消息
}
}
服務(wù)端業(yè)務(wù)如果想要推送到客戶端缘厢,根據(jù)fd的映射關(guān)系蕾域,然后直接push到指定的fd帚桩。
public function index()
{
//TODO: $fd需要查詢map關(guān)系瑟匆,redis等存儲
$fd = 1;
$server = ServerManager::getInstance()->getSwooleServer();
$fdInfo = $server->getClientInfo($fd);
if ($fdInfo["websocket_status"]) {
$server->push($fd, "hello http to websocket!");
}
}
如何遍歷全部鏈接
use EasySwoole\EasySwoole\ServerManager;
$server = ServerManager::getInstance()->getSwooleServer();
$start_fd = 0;
while(true)
{
$conn_list = $server->getClientList($start_fd, 10);
if ($conn_list===false or count($conn_list) === 0)
{
echo "finish\n";
break;
}
$start_fd = end($conn_list);
var_dump($conn_list);
foreach($conn_list as $fd)
{
$server->send($fd, "broadcast");
}
}
https://wiki.swoole.com/wiki/page/p-connection_list.html
如何獲取鏈接信息
use EasySwoole\EasySwoole\ServerManager;
$server = ServerManager::getInstance()->getSwooleServer();
$fdinfo = $server->getClientInfo($fd);