WebSocket Client

其实Swoole官方是有提供web socket client的例子的,在源码的example里面,鉴于很多人都招不到,我就做下搬运工。
直接上代码

  1. class WebSocketClient
  2. {
  3. const VERSION = '0.1.4';
  4. const TOKEN_LENGHT = 16;
  5. const TYPE_ID_WELCOME = 0;
  6. const TYPE_ID_PREFIX = 1;
  7. const TYPE_ID_CALL = 2;
  8. const TYPE_ID_CALLRESULT = 3;
  9. const TYPE_ID_ERROR = 4;
  10. const TYPE_ID_SUBSCRIBE = 5;
  11. const TYPE_ID_UNSUBSCRIBE = 6;
  12. const TYPE_ID_PUBLISH = 7;
  13. const TYPE_ID_EVENT = 8;
  14. const OPCODE_CONTINUATION_FRAME = 0x0;
  15. const OPCODE_TEXT_FRAME = 0x1;
  16. const OPCODE_BINARY_FRAME = 0x2;
  17. const OPCODE_CONNECTION_CLOSE = 0x8;
  18. const OPCODE_PING = 0x9;
  19. const OPCODE_PONG = 0xa;
  20. const CLOSE_NORMAL = 1000;
  21. const CLOSE_GOING_AWAY = 1001;
  22. const CLOSE_PROTOCOL_ERROR = 1002;
  23. const CLOSE_DATA_ERROR = 1003;
  24. const CLOSE_STATUS_ERROR = 1005;
  25. const CLOSE_ABNORMAL = 1006;
  26. const CLOSE_MESSAGE_ERROR = 1007;
  27. const CLOSE_POLICY_ERROR = 1008;
  28. const CLOSE_MESSAGE_TOO_BIG = 1009;
  29. const CLOSE_EXTENSION_MISSING = 1010;
  30. const CLOSE_SERVER_ERROR = 1011;
  31. const CLOSE_TLS = 1015;
  32. private $key;
  33. private $host;
  34. private $port;
  35. private $path;
  36. /**
  37. * @var swoole_client
  38. */
  39. private $socket;
  40. private $buffer = '';
  41. private $origin = null;
  42. /**
  43. * @var bool
  44. */
  45. private $connected = false;
  46. /**
  47. * @param string $host
  48. * @param int $port
  49. * @param string $path
  50. */
  51. function __construct($host = '127.0.0.1', $port = 8080, $path = '/', $origin = null)
  52. {
  53. $this->host = $host;
  54. $this->port = $port;
  55. $this->path = $path;
  56. $this->origin = $origin;
  57. $this->key = $this->generateToken(self::TOKEN_LENGHT);
  58. }
  59. /**
  60. * Disconnect on destruct
  61. */
  62. function __destruct()
  63. {
  64. $this->disconnect();
  65. }
  66. /**
  67. * Connect client to server
  68. *
  69. * @return $this
  70. */
  71. public function connect()
  72. {
  73. $this->socket = new \swoole_client(SWOOLE_SOCK_TCP);
  74. if (!$this->socket->connect($this->host, $this->port))
  75. {
  76. return false;
  77. }
  78. $this->socket->send($this->createHeader());
  79. return $this->recv();
  80. }
  81. public function getSocket()
  82. {
  83. return $this->socket;
  84. }
  85. /**
  86. * Disconnect from server
  87. */
  88. public function disconnect()
  89. {
  90. $this->connected = false;
  91. $this->socket->close();
  92. }
  93. public function close($code = self::CLOSE_NORMAL, $reason = '')
  94. {
  95. $data = pack('n', $code) . $reason;
  96. return $this->socket->send(swoole_websocket_server::pack($data, self::OPCODE_CONNECTION_CLOSE, true));
  97. }
  98. public function recv()
  99. {
  100. $data = $this->socket->recv();
  101. if ($data === false)
  102. {
  103. echo "Error: {$this->socket->errMsg}";
  104. return false;
  105. }
  106. $this->buffer .= $data;
  107. $recv_data = $this->parseData($this->buffer);
  108. if ($recv_data)
  109. {
  110. $this->buffer = '';
  111. return $recv_data;
  112. }
  113. else
  114. {
  115. return false;
  116. }
  117. }
  118. /**
  119. * @param string $data
  120. * @param string $type
  121. * @param bool $masked
  122. * @return bool
  123. */
  124. public function send($data, $type = 'text', $masked = false)
  125. {
  126. switch($type)
  127. {
  128. case 'text':
  129. $_type = WEBSOCKET_OPCODE_TEXT;
  130. break;
  131. case 'binary':
  132. case 'bin':
  133. $_type = WEBSOCKET_OPCODE_BINARY;
  134. break;
  135. default:
  136. return false;
  137. }
  138. return $this->socket->send(swoole_websocket_server::pack($data, $_type, true, $masked));
  139. }
  140. /**
  141. * Parse received data
  142. *
  143. * @param $response
  144. */
  145. private function parseData($response)
  146. {
  147. if (!$this->connected)
  148. {
  149. $response = $this->parseIncomingRaw($response);
  150. if (isset($response['Sec-Websocket-Accept'])
  151. && base64_encode(pack('H*', sha1($this->key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))) === $response['Sec-Websocket-Accept']
  152. )
  153. {
  154. $this->connected = true;
  155. return true;
  156. }
  157. else
  158. {
  159. throw new \Exception("error response key.");
  160. }
  161. }
  162. $frame = swoole_websocket_server::unpack($response);
  163. if ($frame)
  164. {
  165. return $frame->data;
  166. }
  167. else
  168. {
  169. throw new \Exception("swoole_websocket_server::unpack failed.");
  170. }
  171. }
  172. /**
  173. * Create header for websocket client
  174. *
  175. * @return string
  176. */
  177. private function createHeader()
  178. {
  179. $host = $this->host;
  180. if ($host === '127.0.0.1' || $host === '0.0.0.0')
  181. {
  182. $host = 'localhost';
  183. }
  184. return "GET {$this->path} HTTP/1.1" . "\r\n" .
  185. "Origin: {$this->origin}" . "\r\n" .
  186. "Host: {$host}:{$this->port}" . "\r\n" .
  187. "Sec-WebSocket-Key: {$this->key}" . "\r\n" .
  188. "User-Agent: PHPWebSocketClient/" . self::VERSION . "\r\n" .
  189. "Upgrade: websocket" . "\r\n" .
  190. "Connection: Upgrade" . "\r\n" .
  191. "Sec-WebSocket-Protocol: wamp" . "\r\n" .
  192. "Sec-WebSocket-Version: 13" . "\r\n" . "\r\n";
  193. }
  194. /**
  195. * Parse raw incoming data
  196. *
  197. * @param $header
  198. *
  199. * @return array
  200. */
  201. private function parseIncomingRaw($header)
  202. {
  203. $retval = array();
  204. $content = "";
  205. $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));
  206. foreach ($fields as $field)
  207. {
  208. if (preg_match('/([^:]+): (.+)/m', $field, $match))
  209. {
  210. $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./',
  211. function ($matches)
  212. {
  213. return strtoupper($matches[0]);
  214. },
  215. strtolower(trim($match[1])));
  216. if (isset($retval[$match[1]]))
  217. {
  218. $retval[$match[1]] = array($retval[$match[1]], $match[2]);
  219. }
  220. else
  221. {
  222. $retval[$match[1]] = trim($match[2]);
  223. }
  224. }
  225. else
  226. {
  227. if (preg_match('!HTTP/1\.\d (\d)* .!', $field))
  228. {
  229. $retval["status"] = $field;
  230. }
  231. else
  232. {
  233. $content .= $field . "\r\n";
  234. }
  235. }
  236. }
  237. $retval['content'] = $content;
  238. return $retval;
  239. }
  240. /**
  241. * Generate token
  242. *
  243. * @param int $length
  244. *
  245. * @return string
  246. */
  247. private function generateToken($length)
  248. {
  249. $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"§$%&/()=[]{}';
  250. $useChars = array();
  251. // select some random chars:
  252. for ($i = 0; $i < $length; $i++)
  253. {
  254. $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)];
  255. }
  256. // Add numbers
  257. array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9));
  258. shuffle($useChars);
  259. $randomString = trim(implode('', $useChars));
  260. $randomString = substr($randomString, 0, self::TOKEN_LENGHT);
  261. return base64_encode($randomString);
  262. }
  263. /**
  264. * Generate token
  265. *
  266. * @param int $length
  267. *
  268. * @return string
  269. */
  270. public function generateAlphaNumToken($length)
  271. {
  272. $characters = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
  273. srand((float)microtime() * 1000000);
  274. $token = '';
  275. do
  276. {
  277. shuffle($characters);
  278. $token .= $characters[mt_rand(0, (count($characters) - 1))];
  279. } while (strlen($token) < $length);
  280. return $token;
  281. }
  282. }

调用

  1. $opt = getopt("c:n:k:");
  2. print_r($opt);
  3. if (empty($opt['c']) || empty($opt['n']))
  4. {
  5. echo "examples: php client.php -c 100 -n 10000" . PHP_EOL;
  6. return;
  7. }
  8. $clients = $opt['c'];
  9. $count = $opt['n'];
  10. $size = empty($opt['k']) ? 0 : $opt['k'];
  11. require __DIR__ . "/WebSocketClient.php";
  12. $host = '127.0.0.1';
  13. $prot = 9501;
  14. $client = new WebSocketClient($host, $prot);
  15. $data = $client->connect();
  16. //echo $data;
  17. $data = "data";
  18. if (!empty($size))
  19. {
  20. $data = str_repeat("A", $size * 1024);
  21. }
  22. for ($i = 0; $i < $count; $i++)
  23. {
  24. $client->send("hello swoole, number:" . $i . " data:" . $data);
  25. $recvData = "";
  26. //while(1) {
  27. $tmp = $client->recv();
  28. if (empty($tmp))
  29. {
  30. break;
  31. }
  32. $recvData .= $tmp;
  33. //}
  34. echo $recvData . "size:" . strlen($recvData) . PHP_EOL;
  35. }
  36. echo PHP_EOL . "======" . PHP_EOL;
  37. sleep(1);
  38. echo 'finish' . PHP_EOL;