WebSocketClient.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. namespace app\lib\wclient;
  3. use app\lib\wclient\PacketHandler as PacketHandler;
  4. class WebSocketClient
  5. {
  6. private $client;
  7. private $state;
  8. private $host;
  9. private $port;
  10. private $handler;
  11. private $buffer;
  12. private $openCb;
  13. private $messageCb;
  14. private $closeCb;
  15. const HANDSHAKING = 1;
  16. const HANDSHAKED = 2;
  17. const WEBSOCKET_OPCODE_CONTINUATION_FRAME = 0x0;
  18. const WEBSOCKET_OPCODE_TEXT_FRAME = 0x1;
  19. const WEBSOCKET_OPCODE_BINARY_FRAME = 0x2;
  20. const WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8;
  21. const WEBSOCKET_OPCODE_PING = 0x9;
  22. const WEBSOCKET_OPCODE_PONG = 0xa;
  23. const TOKEN_LENGHT = 16;
  24. public function __construct()
  25. {
  26. $this->client = new \swoole\client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
  27. $this->client->on("connect", [$this, "onConnect"]);
  28. $this->client->on("receive", [$this, "onReceive"]);
  29. $this->client->on("close", [$this, "onClose"]);
  30. $this->client->on("error", [$this, "onError"]);
  31. $this->handler = new PacketHandler();
  32. $this->buffer = "";
  33. }
  34. public function connect($host, $port)
  35. {
  36. $this->host = $host;
  37. $this->port = $port;
  38. $this->client->connect($host, $port);
  39. }
  40. public function sendHandShake()
  41. {
  42. $this->state = static::HANDSHAKING;
  43. $request = $this->handler->buildHandShakeRequest($this->host, $this->port);
  44. $this->client->send($request);
  45. }
  46. public function onConnect($cli)
  47. {
  48. $this->sendHandShake();
  49. }
  50. public function onReceive($cli, $data)
  51. {
  52. if ($this->state == static::HANDSHAKING) {
  53. $this->buffer .= $data;
  54. $pos = strpos($this->buffer, "\r\n\r\n", true);
  55. if ($pos != false) {
  56. $header = substr($this->buffer, 0, $pos + 4);
  57. $this->buffer = substr($this->buffer, $pos + 4);
  58. if (true == $this->handler->verifyUpgrade($header)) {
  59. $this->state = static::HANDSHAKED;
  60. if (isset($this->openCb))
  61. call_user_func($this->openCb, $this);
  62. } else {
  63. echo "handshake failed\n";
  64. }
  65. }
  66. } else if ($this->state == static::HANDSHAKED) {
  67. $this->buffer .= $data;
  68. }
  69. if ($this->state == static::HANDSHAKED) {
  70. try {
  71. $frame = $this->handler->processDataFrame($this->buffer);
  72. } catch (\Exception $e) {
  73. $cli->close();
  74. return;
  75. }
  76. if ($frame != null) {
  77. if (isset($this->messageCb))
  78. call_user_func($this->messageCb, $this, $frame);
  79. }
  80. }
  81. }
  82. public function onClose($cli)
  83. {
  84. if (isset($this->closeCb))
  85. call_user_func($this->closeCb, $this);
  86. }
  87. public function onError($cli)
  88. {
  89. echo "error occurred\n";
  90. }
  91. public function on($event, $callback)
  92. {
  93. if (strcasecmp($event, "open") === 0) {
  94. $this->openCb = $callback;
  95. } else if (strcasecmp($event, "message") === 0) {
  96. $this->messageCb = $callback;
  97. } else if (strcasecmp($event, "close") === 0) {
  98. $this->closeCb = $callback;
  99. } else {
  100. echo "$event is not supported\n";
  101. }
  102. }
  103. public function send($data, $type = 'text')
  104. {
  105. switch ($type) {
  106. case 'text':
  107. $_type = self::WEBSOCKET_OPCODE_TEXT_FRAME;
  108. break;
  109. case 'binary':
  110. case 'bin':
  111. $_type = self::WEBSOCKET_OPCODE_BINARY_FRAME;
  112. break;
  113. case 'ping':
  114. $_type = self::WEBSOCKET_OPCODE_PING;
  115. break;
  116. case 'close':
  117. $_type = self::WEBSOCKET_OPCODE_CONNECTION_CLOSE;
  118. break;
  119. case 'ping':
  120. $_type = self::WEBSOCKET_OPCODE_PING;
  121. break;
  122. case 'pong':
  123. $_type = self::WEBSOCKET_OPCODE_PONG;
  124. break;
  125. default:
  126. echo "$type is not supported\n";
  127. return;
  128. }
  129. $data = \swoole_websocket_server::pack($data, $_type);
  130. $this->client->send($data);
  131. }
  132. public function getTcpClient()
  133. {
  134. return $this->client;
  135. }
  136. }