Websocket

简介

CabalPHP 默认支持websocket

只需要在routes中添加路由即可:

  1. // $route->ws('/chat', '类@前缀');
  2. $route->ws('/chat', 'WebsocketController@on');

注册后当请求 ws://host:port/chat 会触发以下事件

  • HandShake(\Server $server, Request $request, $vars = []) 非必选
    • 刚开始连接时握手事件,可以增加逻辑判断,拒绝非法请求。
  • Open(\Server $server, Request $request) 非必选
    • 连接成功的打开事件,可以发送欢迎消息或初始化消息等。
  1. Message(\Server $server, Frame $frame, $vars = []) 必选
    • 接受到消息的时候触发的事件
  • Close(\Server $server, $fd, $reactorId) 非必选
    • 连接断开触发的事件,清理连接存储数据等。

对应到 WebsocketController@on 则是:

  1. WebsocketController@onHandShake
  2. WebsocketController@onOpen
  3. WebsocketController@onMessage
  4. WebsocketController@onClose

fdSession

fdSession 是一个可以用来存储连接相关信息的缓存对象,数据存储在 worker 进程中,会随着 worker 销毁释放。

HandShakeOpen 事件中可以用 $request->fdSession() 获得

onMessage 事件中可以用 $frame->fdSession() 获得

使用方法和 session 类似。

?> HandShake 事件中可以正常使用 $request->session(),而在 Open 事件中可以读取 $request->session()

聊天室示例

一个简单的聊天室:

?> 复杂业务可以在 Message 事件中使用 Cabal-Route 转发请求哦!

  1. <?php
  2. namespace App\Controller;
  3. use Cabal\Core\Http\Request;
  4. use Cabal\Core\Http\Frame;
  5. use Cabal\Core\Http\Response;
  6. class WebsocketController
  7. {
  8. public function chat(\Server $server, Request $request, $var = [])
  9. {
  10. $response = new Response();
  11. $response->getBody()
  12. ->write(
  13. $server->plates()->render('chat')
  14. );
  15. return $response;
  16. }
  17. public function onHandShake(\Server $server, Request $request, $vars = [])
  18. {
  19. $session = $request->session();
  20. $session['test'] = date('Y-m-d H:i:s');
  21. $fdSession = $request->fdSession();
  22. $fdSession['test'] = date('Y-m-d H:i:s');
  23. $onlines = $server->onlines->add();
  24. }
  25. public function onOpen(\Server $server, Request $request)
  26. {
  27. }
  28. public function onMessage(\Server $server, Frame $frame, $vars = [])
  29. {
  30. $fdSession = $frame->fdSession();
  31. $data = trim($frame->data);
  32. if (strlen($data) >= 6 && strtolower(substr($data, 0, 6)) == '/name ') {
  33. $nickname = substr($data, 6);
  34. if (mb_strlen($nickname, 'utf-8') > 12 || mb_strlen($nickname, 'utf-8') < 2) {
  35. $server->push($frame->fd, json_encode([
  36. 'systemMsg' => '昵称必须是2至12个字!',
  37. ]));
  38. } else {
  39. $fdSession['nickname'] = $nickname;
  40. $server->push($frame->fd, json_encode([
  41. 'systemMsg' => "昵称修改为 " . $nickname . " 成功!",
  42. ]));
  43. }
  44. return;
  45. } elseif (strlen($data) >= 5 && strtolower(substr($data, 0, 5)) == '/join') {
  46. $onlines = $server->onlines->get();
  47. $server->push($frame->fd, json_encode([
  48. 'systemMsg' => "欢迎你加入聊天室!",
  49. ]));
  50. foreach ($server->connections as $fd) {
  51. $connectionInfo = $server->connection_info($fd);
  52. if (isset($connectionInfo['websocket_status']) && $connectionInfo['websocket_status'] == WEBSOCKET_STATUS_FRAME) {
  53. $server->push($fd, json_encode([
  54. 'onlineNums' => $onlines,
  55. ]));
  56. }
  57. }
  58. return;
  59. } elseif (strlen($data) < 1) {
  60. // $server->push($frame->fd, json_encode([
  61. // 'systemMsg' => "内容不能为空哦!",
  62. // ]));
  63. return;
  64. }
  65. $nickname = isset($fdSession['nickname']) ? $fdSession['nickname'] : "游客" . $frame->fd;
  66. foreach ($server->connections as $fd) {
  67. $connectionInfo = $server->connection_info($fd);
  68. if (isset($connectionInfo['websocket_status']) && $connectionInfo['websocket_status'] == WEBSOCKET_STATUS_FRAME) {
  69. $server->push($fd, json_encode([
  70. 'nickname' => $nickname,
  71. 'datetime' => date('Y-m-d H:i:s'),
  72. 'msg' => $frame->data,
  73. ]));
  74. }
  75. }
  76. }
  77. public function onClose(\Server $server, $fd, $reactorId)
  78. {
  79. $onlines = $server->onlines->sub();
  80. foreach ($server->connections as $fd) {
  81. $connectionInfo = $server->connection_info($fd);
  82. if (isset($connectionInfo['websocket_status']) && $connectionInfo['websocket_status'] == WEBSOCKET_STATUS_FRAME) {
  83. $server->push($fd, json_encode([
  84. 'onlineNums' => $onlines,
  85. ]));
  86. }
  87. }
  88. }
  89. }

聊天室客户端:

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Websocket</title>
  6. <script>
  7. var ws = new WebSocket('ws://' + location.host + '/ws/chat');
  8. ws.onmessage = function (event) {
  9. var data = event.data;
  10. data = eval('(' + data + ')');
  11. if ('systemMsg' in data) {
  12. var line = document.createElement('div');
  13. line.className = 'sysMsg';
  14. line.innerHTML = data.systemMsg;
  15. document.getElementById('msg').appendChild(line);
  16. } else if ('onlineNums' in data) {
  17. document.getElementById('online-nums').innerHTML = data.onlineNums;
  18. } else {
  19. var line = document.createElement('div');
  20. line.innerHTML = '<span>[' + data.nickname + ']</span> ' + data.msg + ' <span class="datetime">' + data.datetime + '</span>';
  21. document.getElementById('msg').appendChild(line);
  22. }
  23. };
  24. ws.onopen = function(event) {
  25. ws.send('/join')
  26. }
  27. function send(e, input) {
  28. if (e.charCode == 13) {
  29. ws.send(input.value);
  30. input.value = '';
  31. }
  32. }
  33. </script>
  34. <style>
  35. .warp{width:400px}
  36. #msg{padding:10px;border:1px solid #eee;min-height:500px;font-size:14px;line-height:1.8;line-height:1.8}
  37. #msg span{color:#2b94ff}
  38. #msg .datetime{color:#ddd}
  39. #msg .sysMsg{color:#fe684c}
  40. .infos{font-size:14px;color:#777;padding:8px 13px;text-align:right}
  41. input{padding:8px 12px;font-size:14px;width:374px;border:1px solid #eee;border-top:none}
  42. </style>
  43. </head>
  44. <body>
  45. <div class="warp">
  46. <div id="msg"></div>
  47. <div>
  48. <input type="text" onkeypress="send(event,this)" placeholder="回车发送 /name 昵称 改名" />
  49. </div>
  50. <div class="infos">
  51. 在线人数:
  52. <span id="online-nums"></span>
  53. </div>
  54. </div>
  55. </body>
  56. </html>