- 安装 socket 工具包
composer require easyswoole/socket --ignore-platform-reqs
- 配置 Websocket服务
1.打开 /dev.php 修改 SERVER_TYPE,
// 修改该配置后, 你的easyswoole服务既可做 http服务也可做websocket服务。swoole本身的websocket服务也支持http服务一个道理
'SERVER_TYPE' => EASYSWOOLE_WEB_SOCKET_SERVER,
2. 增加 websocket心跳检测 heartbeat_check_interval
'SETTING' => [
'worker_num' => 1,
'reload_async' => true,
'max_wait_time'=>3,
'heartbeat_check_interval'=>60, // 该参数项为心跳检测,严格参考swoole 配置说明
],
3 . 创建 websocket 控制器目录 ,webscoekt 事件类,websocket解析类
1. 创建 webscoekt 事件类
/App/Websocket/WebSocketEvent.php
<?php
namespace App\WebSocket;
/**
* Class WebSocketEvent
* 此类是 WebSocet 中一些非强制的自定义事件处理
* @package App\WebSocket
*/
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)
{
$time = time();
echo "{$request->fd}创建连接事件 : {$time} \n";
/** 此处自定义握手规则 返回 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;
}
/**
* 关闭事件
* 所有客户端关闭时触发的方法
* @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}触发关闭事件 : {$time} \n";
print_r($info);
/**
* 判断连接是否是 server 主动关闭
* 参见 https://wiki.swoole.com/wiki/page/p-event/onClose.html
*/
if ($reactorId < 0) {
echo "server close \n";
}
}
}
/**
* 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;
}
}
2.创建 Webcoket解析类 WebSocketParser.php
/App/Websocket/WebSocketParser.php
<?php
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 消息内容
* @param WebSocketClient $client 当前的客户端
* @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 {
// 设置心跳执行的类和方法
$caller->setControllerClass(\App\WebSocket\Controller\Base::class);
$caller->setAction('heartbeat');
}
return $caller;
}
/**
* 打包下发的消息
* @param Response $response 控制器返回的响应
* @param WebSocketClient $client 当前的客户端
* @return string|null
*/
public function encode(Response $response, $client): ?string
{
return $response->getMessage();
}
}
3 . 注册 websocket服务
在 easyswoole 的框架事件类EasySwooleEvent 的mainServerCreate 方法配置
/**
* **************** websocket控制器 **********************
*/
//创建一个 Dispatcher 配置
$conf = new \EasySwoole\Socket\Config();
//设置Dispatcher为WebSocket 模式
$conf->setType(\EasySwoole\Socket\Config::WEB_SOCKET);
try {
$conf->setParser(new WebSocketParser());//设置解析器对象
$dispatch = new Dispatcher($conf);//创建Dispatcher对象并注入config对象
} catch (Exception $e) {
}
//给server注册相关事件在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);
});
//自定义关闭事件
$register->set(EventRegister::onClose, function (\swoole_server $server, int $fd, int $reactorId) use ($websocketEvent) {
$websocketEvent->onClose($server, $fd, $reactorId);
});
4 . 创建控制器目录,让easyswoole 的websocket服务像http服务那样调用方便
/App/Websocket/Controller/Base.php // 基础控制器类
/App/Websocket/Controller/Index.php // 默认控制器类
<?php
namespace App\WebSocket\Controller;
use EasySwoole\Socket\AbstractInterface\Controller;
use EasySwoole\Socket\Client\WebSocket as WebSocketClient;
use Exception;
/**
* 基础控制器
* Class Base
* @package App\WebSocket\Controller
*/
class Base extends Controller
{
/**
* 获取当前的用户
* @return array|string
* @throws Exception
*/
protected function currentUser()
{
/** @var WebSocketClient $client */
}
protected function actionNotFound(?string $actionName)
{
// 关闭客户端
\EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer()->disconnect($fd);
echo "您的请求 {$actionName} 不存在 ... \n";
}
protected function afterAction(?string $actionName)
{
echo "请求之后执行 \n";
}
}
// Index控制器
<?php
namespace App\WebSocket\Controller;
class Index extends Base
{
/**
* 心跳执行的方法
* 该方法建议 迁移到 基类控制器 Base 中
* 推荐使用 easyswoole 自带的websocket客户端调试
* http://www.easyswoole.com/wstool.html
*/
public function heartbeat(){
$fd = $this->caller()->getClient()->getFd();
$this->caller()->getClient()->getFd(); // 请求用户的fd
$data = $this->caller()->getArgs(); // 获取请求参数
var_dump($data);
var_dump(\EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer()->worker_id);
echo "心跳 heartbeat {$fd} \n";
}
public function index(){
$this->caller()->getClient()->getFd(); // 请求用户的fd
$data = $this->caller()->getArgs(); // 获取请求参数
var_dump($data);
$this->response()->setMessage('your fd is '. $this->caller()->getClient()->getFd()); // 推送消息
echo "接收到客户端连接 \n";
}
}
以上是 easyswoole 的websocket 服务端搭建过程
推荐 (websocket客户端调试工具)[http://www.easyswoole.com/wstool.html]
说明:
1.在 客户端发送 类似 {"class":"Index","action":"index","params":{"name":"wzylmlkjok"}} 格式的数据就可以访问到 easyswoole的websocket的 Index控制器的index方法 。
2.所有客户端的连接事件位于 WebSocketEvent.php 的 onHandShake 方法 ; 所有的关闭事件位于WebSocketEvent.php 的 onClose方法
网友评论