步骤1? ?修改配置文件MAIN_SERVER.SERVER_TYPE 为EASYSWOOLE_WEB_SOCKET_SERVER??
如dev.php
<?php
use EasySwoole\Log\LoggerInterface;
return [
'SERVER_NAME'=>"EasySwoole",
'MAIN_SERVER'=>[
'LISTEN_ADDRESS'=>'0.0.0.0',
'PORT'=>'19501',
"SERVER_TYPE"=>EASYSWOOLE_WEB_SOCKET_SERVER, //可选为 EASYSWOOLE_SERVER EASYSWOOLE_WEB_SERVER EASYSWOOLE_WEB_SOCKET_SERRVER
"SOCK_TYPE"=>SWOOLE_TCP,
"RUN_MODEL"=>SWOOLE_PROCESS,
"SETTING"=>[
'worker_num'=>8,
'reload_async'=>true,
'max_wait_time'=>3,
'package_max_length'=>1024*1024*1024,
'max_connection'=>150000,
'socket_buffer_size'=>1024*1024*1024
],
],
];
步骤2??EasySwooleEvent中mainServerCreate事件进行回调注册:
<?php
public static function mainServerCreate(\EasySwoole\EasySwoole\Swoole\EventRegister $register)
{
$config = new \EasySwoole\Socket\Config();
$config->setType($config::WEB_SOCKET);
$config->setParser(WebSocketParser::class);
$dispatcher = new Dispatcher($config);
$config->setOnExceptionHandler(function (\Swoole\Server $server, \Throwable $throwable, string $raw, WebSocket $client, 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);
});
//注册服务时间
$register->add(EventRegister::onOpen,[WebSocketEvents::class,'onOpen']);
$register->add(EventRegister::onClose,[WebSocketEvents::class,'onClose']);
}
步骤3??WebSocketEvents.php
<?php
namespace App\WebSocket;
use EasySwoole\MysqliPool\Mysql;
use EasySwoole\Mysqli\Config as MysqlConfig;
use EasySwoole\RedisPool\Config as RedisConfig;
use EasySwoole\RedisPool\Redis;
use EasySwoole\FastCache\Cache;
use SebastianBergmann\CodeCoverage\Report\PHP;
class WebSocketEvents {
//监听ws连接事件
public static function onOpen(\swoole_websocket_server $server, \swoole_http_request $request) {
echo $request->fd . '链接成功' . PHP_EOL;
//return true;
}
//监听ws关闭事件
public static function onClose(\swoole_server $server, int $fd, int $reactorId) {
//echo $reactorId . ' -- ' . $fd . ' websocket 关闭' . PHP_EOL;
//return true;
}
/**
* @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;
}
}
步骤4??websocket解析器? ?
<?php
namespace App\WebSocket;
use EasySwoole\Socket\AbstractInterface\ParserInterface;
use EasySwoole\Socket\Client\WebSocket;
use EasySwoole\Socket\Bean\Caller;
use EasySwoole\Socket\Bean\Response;
/**
* Class WebSocketParser
*
* 此类是自定义的 websocket 消息解析器
* 此处使用的设计是使用 json string 作为消息格式
* 当客户端消息到达服务端时,会调用 decode 方法进行消息解析
* 会将 websocket 消息 转成具体的 Class -> Action 调用 并且将参数注入
*
* @package App\WebSocket
*/
class WebSocketParser implements ParserInterface {
/**
* decode
* @param string $raw 客户端原始消息
* @param WebSocket $client WebSocket Client 对象
* @return Caller Socket 调用对象
*/
public function decode($raw, $client): ?Caller {
// new 调用者对象
$caller = new Caller();
// 解析 客户端原始消息
$data = json_decode($raw, true);
if (!is_array($data)) {
echo "decode message error1111! \n";
return null;
}
/**
* 设置被调用的类 这里会将ws消息中的 class 参数解析为具体想访问的控制器
* 如果更喜欢 event 方式 可以自定义 event 和具体的类的 map 即可
* 注 目前 easyswoole 3.0.4 版本及以下 不支持直接传递 class string 可以通过这种方式
*/
$class = '\\App\\WebSocket\\WSController\\' . ucfirst($data['class'] ?? 'Index');
$caller->setControllerClass($class);
$action = $data['action'];
$caller->setAction($action);
// 检查是否存在args
$args = isset($data['params']) && !empty($data['params']) ? $data['params'] : [];
// 设置被调用的Args
$caller->setArgs($args ?? []);
return $caller;
}
/**
* encode
* @param Response $response Socket Response 对象
* @param WebSocket $client WebSocket Client 对象
* @return string 发送给客户端的消息
*/
public function encode(Response $response, $client): ?string {
/**
* 这里返回响应给客户端的信息
* 这里应当只做统一的encode操作 具体的状态等应当由 Controller处理
*/
return $response->getMessage();
}
}
步骤五 新增Websoket 控制器? ?Index.php
App\WebSocket\WSController
<?php
/*
* ws控制器测试
**/
namespace App\WebSocket\WSController;
use EasySwoole\EasySwoole\ServerManager;
use EasySwoole\Socket\AbstractInterface\Controller;
class Index extends Controller {
public function index() {
$client = $this->caller()->getClient();
$server = ServerManager::getInstance()->getSwooleServer();
$post_info = $this->caller()->getArgs();
if ($server->getClientInfo($client->getFd())) {
$server->push($client->getFd(), json_encode(['data' => 'success']));
}
}
public function index1(){
var_dump('这里是index1');
}
}
步骤六? 连接websoket? 进行测试??
前端html 代码如下:
<!DOCTYPE html>
<html>
<head>
<title>测试</title>
</head>
<body>
<script type="text/javascript">
ws = new WebSocket("ws:127.0.0.1:19501")
ws.open = function () {
alert('success')
str = '{"class":"Index1","action":"index1","params":[]}'
console.log(str)
ws.send(str)
}
ws.onmessage = function (evt) {
alert(evt.data)
}
ws.onclose = function (evt) {
console.log('colose')
}
</script>
</body>
</html>
</html>
注: 其中发送的数据? ?JSON字符串? ? {"class":"Index1","action":"index1","params":[]}
class 表示请求到对应的Websoket控制器??
action 标识请求到对应的Websoket方法
params 为发送的数据,这里必须数组? 若为其他,可能接收不到!
结果如图:
?
|