本篇内容介绍了“怎么用PHP实现wscat”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
使用
php websocket-client.php -c we://localhost:10001/hello
源码https://github.com/dreamsxin/cphalcon7/blob/master/examples/async/websocket-client.php
<?php class WebsocketClient { static public $debug = false; protected $url; protected $options; protected $scheme; protected $host; protected $user; protected $pass; protected $port; protected $path; protected $fullpath; protected $query; protected $socket; protected $callback = null; protected static $opcodes = array( 'continuation' => 0, 'text' => 1, 'binary' => 2, 'close' => 8, 'ping' => 9, 'pong' => 10, ); public function __construct($url, $callback = NULL, array $options = NULL) { $this->url = $url; $this->options = $options; $url_parts = parse_url($this->url); if ($url_parts) { $this->scheme = \Phalcon\Arr::get($url_parts, 'scheme'); $this->host = \Phalcon\Arr::get($url_parts, 'host'); $this->user = isset($url_parts['user']) ? $url_parts['user'] : ''; $this->pass = isset($url_parts['pass']) ? $url_parts['pass'] : ''; $this->port = isset($url_parts['port']) ? $url_parts['port'] : ($this->scheme === 'wss' ? 443 : 80); $this->path = isset($url_parts['path']) ? $url_parts['path'] : '/'; $this->query = isset($url_parts['query']) ? $url_parts['query'] : ''; $this->fragment = isset($url_parts['fragment']) ? $url_parts['fragment'] : ''; } if (!in_array($this->scheme, array('ws', 'wss'))) { throw new \Exception( "Url should have scheme ws or wss, not '$scheme' from URI '$this->socket_uri' ." ); } $this->fullpath = $this->path; if (!empty($this->query)) { $this->fullpath .= '?' . $this->query; } if (!empty($this->fragment)) { $this->fullpath .= '#' . $this->fragment; } if ($callback && \is_callable($callback)) { $callback = Closure::bind($callback, $this); } $this->stdin = Phalcon\Async\Stream\ReadablePipe::getStdin(); $this->stdout = Phalcon\Async\Stream\WritablePipe::getStdout(); $this->callback = $callback; } public function __destruct() { if ($this->socket && !$this->socket->is_closing) { $this->socket->close(); $this->socket = null; } } /** * Generate a random string for WebSocket key. * @return string Random string */ protected static function generateKey() { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$&/()=[]{}0123456789'; $key = ''; $chars_length = strlen($chars); for ($i = 0; $i < 16; $i++) { $key .= $chars[mt_rand(0, $chars_length-1)]; } return base64_encode($key); } public function sendHeader() { // Generate the WebSocket key. $this->socket->key = self::generateKey(); // Default headers (using lowercase for simpler array_merge below). $headers = array( 'Host' => $this->host . ":" . $this->port, 'User-Agent' => 'websocket-client-php', 'Connection' => 'Upgrade', 'Upgrade' => 'websocket', 'Sec-WebSocket-Key' => $this->socket->key, 'Sec-Websocket-Version' => '13', ); // Handle basic authentication. if ($this->user || $this->pass) { $headers['authorization'] = 'Basic ' . base64_encode($this->user . ':' . $this->pass) . "\r\n"; } // Deprecated way of adding origin (use headers instead). if (isset($this->options['origin'])) { $headers['origin'] = $this->options['origin']; } // Add and override with headers from options. if (isset($this->options['headers'])) { $headers = array_merge($headers, array_change_key_case($this->options['headers'])); } $header = "GET " . $this->fullpath . " HTTP/1.1\r\n"; $headers = array_map(function($key, $value) { return $key.': '.$value; }, array_keys($headers), $headers); $header .= implode("\r\n", $headers) . "\r\n\r\n"; // Send headers. $this->socket->write($header); return true; } public function connect($callback = NULL) { if ($callback && \is_callable($callback)) { $callback = Closure::bind($callback, $this); } $defer = new \Phalcon\Async\Deferred(); try { $tls = NULL; if ($this->scheme == 'wss') { $tls = new \Phalcon\Async\TlsClientEncryption(); $tls = $tls->withAllowSelfSigned(true); } $socket = \Phalcon\Async\Network\TcpSocket::connect($this->host, $this->port, $tls); $socket->is_closing = false; $socket->fragment_status = 0; $socket->fragment_length = 0; $socket->fragment_size = 4096; $socket->read_length = 0; $socket->huge_payload = ''; $socket->payload = ''; $socket->headers = NULL; $socket->isHandshake = false; $this->socket = $socket; $this->sendHeader(); // 接收握手数据 \Phalcon\Async\Task::async(function () use ($defer, $socket) { try { $buffer = ''; while (!$socket->isHandshake && null !== ($chunk = $socket->read())) { $buffer .= $chunk; $pos = strpos($buffer, "\r\n\r\n"); if ($pos) { if ($this->handShake($socket, $buffer)) { $socket->isHandshake = true; $buffer = substr($buffer, $pos+4); $defer->resolve('connected'); } } } $defer->resolve('connected'); } catch (\Throwable $e) { $defer->fail($e); } $socket->close(); $socket->is_closing = true; }); } catch (\Throwable $e) { self::err($e->getMessage()); exit; } $ws = $this; \Phalcon\Async\Task::await(Phalcon\Async\Deferred::transform($defer->awaitable(), function (?\Throwable $e, ?string $v = null) use ($ws, $callback) { if ($e) { self::err($e->getMessage()); return; } $ws->recv($ws->stdin); if ($callback && \is_callable($callback)) { $callback(); } })); } public function recv($stdin) { $socket = $this->socket; $callback = $this->callback; // 接收数据 \Phalcon\Async\Task::async(function () use ($socket, $callback, $stdin) { try { $buffer = ''; while (!$socket->is_closing && null !== ($chunk = $socket->read())) { $buffer .= $chunk; if ($this->process($socket, $buffer)) { $buffer = substr($buffer, $socket->read_length); if ($callback && \is_callable($callback)) { $callback($socket->payload); } $socket->fragment_status = 0; $socket->fragment_length = 0; $socket->read_length = 0; $socket->huge_payload = ''; $socket->payload = ''; } } } catch (\Throwable $e) { self::err($e->getMessage()); } $socket->close(); $socket->is_closing = true; echo PHP_EOL.\Phalcon\Cli\Color::success('disconnected'); $stdin->close(); }); } public function isClose() { return !$this->socket || $this->socket->is_closing; } public function send($data) { if ($this->isClose()) { self::err("No connection established or connection closed"); return false; } self::sendFragment($this->socket, $data); } public function handShake($socket, $buffer) { // Validate response. if (!preg_match('#Sec-WebSocket-Accept:(.*)$#mUi', $buffer, $matches)) { throw new \Exception('Connection failed: Server sent invalid upgrade response:'.PHP_EOL . $buffer); } $keyAccept = trim($matches[1]); $expectedResonse = base64_encode(pack('H*', sha1($this->socket->key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); if ($keyAccept !== $expectedResonse) { throw new \Exception('Server sent bad upgrade response.'); } $socket->isHandshake = true; return true; } static public function readFragment0($socket, $buffer) { if (strlen($buffer) < 2) { return false; } $data = substr($buffer, 0, 2); $socket->read_length = 2; // Is this the final fragment? // Bit 0 in byte 0 /// @todo Handle huge payloads with multiple fragments. $socket->final = (boolean) (ord($data[0]) & 1 << 7); // Should be unused, and must be false… // Bits 1, 2, & 3 $rsv1 = (boolean) (ord($data[0]) & 1 << 6); $rsv2 = (boolean) (ord($data[0]) & 1 << 5); $rsv3 = (boolean) (ord($data[0]) & 1 << 4); // Parse opcode $opcode_int = ord($data[0]) & 31; // Bits 4-7 $opcode_ints = array_flip(self::$opcodes); if (!array_key_exists($opcode_int, $opcode_ints)) { throw new \Exception('Bad opcode in websocket frame: '.$opcode_int); } $opcode = $opcode_ints[$opcode_int]; // record the opcode if we are not receiving a continutation fragment if ($opcode !== 'continuation') { $socket->last_opcode = $opcode; } // Masking? $socket->mask = (boolean) (ord($data[1]) >> 7); // Bit 0 in byte 1 $socket->payload = ''; // Payload length $socket->payload_length = (integer) ord($data[1]) & 127; // Bits 1-7 in byte 1 if ($socket->payload_length > 125) { $socket->fragment_status = 1; } else { $socket->fragment_status = 2; } return true; } static public function readFragment1($socket, $buffer) { if ($socket->payload_length === 126) { if ($socket->fragment_length - $socket->read_length < 2) { return false; } $data = substr($buffer, $socket->read_length, 2); // 126: Payload is a 16-bit unsigned int $socket->read_length += 2; } else { if ($socket->fragment_length - $socket->read_length < 8) { return false; } $data = substr($buffer, $socket->read_length, 8); // 127: Payload is a 64-bit unsigned int $socket->read_length += 8; } $socket->payload_length = bindec(self::sprintB($data)); $socket->fragment_status = 2; return true; } static public function readFragment2($socket, $buffer) { // Get masking key. if ($socket->mask) { if ($socket->fragment_length - $socket->read_length < (4 + $socket->payload_length)) { return false; } $masking_key = substr($buffer, $socket->read_length, 4); $socket->read_length += 4; } elseif ($socket->fragment_length - $socket->read_length < $socket->payload_length) { return false; } // Get the actual payload, if any (might not be for e.g. close frames. if ($socket->payload_length > 0) { $data = substr($buffer, $socket->read_length, $socket->payload_length); $socket->read_length += $socket->payload_length; if ($socket->mask) { // Unmask payload. for ($i = 0; $i < $socket->payload_length; $i++) { $socket->payload .= ($data[$i] ^ $masking_key[$i % 4]); } } else { $socket->payload = $data; } } $socket->fragment_status = 3; return true; } static public function sendFragment($socket, $payload, $opcode = 'text', $masked = true) { if (!in_array($opcode, array_keys(self::$opcodes))) { throw new \Exception('Bad opcode '.$opcode.', try text or binary.'); } // record the length of the payload $payload_length = strlen($payload); $fragment_cursor = 0; // while we have data to send while ($payload_length > $fragment_cursor) { // get a fragment of the payload $sub_payload = substr($payload, $fragment_cursor, $socket->fragment_size); // advance the cursor $fragment_cursor += $socket->fragment_size; // is this the final fragment to send? $final = $payload_length <= $fragment_cursor; // send the fragment self::send_frame($socket, $final, $sub_payload, $opcode, $masked); // all fragments after the first will be marked a continuation $opcode = 'continuation'; } } static public function send_frame($socket, $final, $payload, $opcode, $masked) { // Binary string for header. $frame_head_binstr = ''; // Write FIN, final fragment bit. $frame_head_binstr .= (bool) $final ? '1' : '0'; // RSV 1, 2, & 3 false and unused. $frame_head_binstr .= '000'; // Opcode rest of the byte. $frame_head_binstr .= sprintf('%04b', self::$opcodes[$opcode]); // Use masking? $frame_head_binstr .= $masked ? '1' : '0'; // 7 bits of payload length... $payload_length = strlen($payload); if ($payload_length > 65535) { $frame_head_binstr .= decbin(127); $frame_head_binstr .= sprintf('%064b', $payload_length); } elseif ($payload_length > 125) { $frame_head_binstr .= decbin(126); $frame_head_binstr .= sprintf('%016b', $payload_length); } else { $frame_head_binstr .= sprintf('%07b', $payload_length); } $frame = ''; // Write frame head to frame. foreach (str_split($frame_head_binstr, 8) as $binstr) $frame .= chr(bindec($binstr)); // Handle masking if ($masked) { // generate a random mask: $mask = ''; for ($i = 0; $i < 4; $i++) $mask .= chr(rand(0, 255)); $frame .= $mask; } // Append payload to frame: for ($i = 0; $i < $payload_length; $i++) { $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i]; } $socket->write($frame); } /** * Helper to convert a binary to a string of '0' and '1'. */ protected static function sprintB($string) { $ret = ''; for ($i = 0; $i < strlen($string); $i++) { $ret .= sprintf("%08b", ord($string[$i])); } return $ret; } /** * 解析数据包 * * @return string */ static public function process($socket, $buffer) { startfragment: self::info('process fragment_status:'.$socket->fragment_status.' buffer length:'.strlen($buffer)); $socket->fragment_length = strlen($buffer); switch ($socket->fragment_status) { case 0: // Just read the main fragment information first. if (!self::readFragment0($socket, $buffer)) { return false; } goto startfragment; break; case 1: if (!self::readFragment1($socket, $buffer)) { return false; } goto startfragment; break; case 2: if (!self::readFragment2($socket, $buffer)) { return false; } goto startfragment; break; case 3: { if ($socket->last_opcode === 'close') { // Get the close status. if ($socket->payload_length >= 2) { $status_bin = $socket->payload[0] . $socket->payload[1]; $status = bindec(sprintf("%08b%08b", ord($socket->payload[0]), ord($socket->payload[1]))); $socket->close_status = $status; $socket->payload = substr($socket->payload, 2); self::sendFragment($socket, $status_bin . 'Close acknowledged: ' . $status, 'close', true); // Respond. } $socket->is_closing = true; // A close response, all done. } // if this is not the last fragment, then we need to save the payload if (!$socket->final) { $socket->huge_payload .= $socket->payload; self::info('final:'.$socket->final.', payload:'.$socket->payload); return false; } else { // sp we need to retreive the whole payload $socket->huge_payload .= $socket->payload; $socket->payload = $socket->huge_payload; $socket->huge_payload = null; self::info('final:'.$socket->final.', payload:'.$socket->payload); return true; } break; } } return false; } static public function info($message) { if (self::$debug) { echo Phalcon\Cli\Color::info($message).PHP_EOL; } } static public function err($message) { echo Phalcon\Cli\Color::error($message).PHP_EOL; } } /** * 客户端测试 * php -d extension=phalcon.so websocket-client.php -c ws://localhost:10001 */ error_reporting(-1); ini_set('display_errors', (DIRECTORY_SEPARATOR == '\\') ? '0' : '1'); $opts = new \Phalcon\Cli\Options('Websocket CLI'); $opts->add([ 'type' => \Phalcon\Cli\Options::TYPE_STRING, 'name' => 'connect', 'shortName' => 'c', 'required' => true, 'help' => "<url> connect to a websocket server", ]); $vals = $opts->parse(); if (!isset($vals['connect'])) { return; } // WebsocketClient::$debug = true; $ws = new WebsocketClient($vals['connect'], function($data) { $this->stdout->write(Phalcon\Cli\Color::colorize('< '.$data, Phalcon\Cli\Color::FG_BLUE, NULL, Phalcon\Cli\Color::BG_LIGHT_GRAY)); $this->stdout->write(PHP_EOL.'> '); }); try { $ws->connect(function() use ($ws) { echo \Phalcon\Cli\Color::success('connected (press CTRL+C to quit)'); $this->stdout->write('> '); while (null !== ($chunk = $this->stdin->read(100))) { $ws->send(trim($chunk)); } }); } catch (\Throwable $e) { }
“怎么用PHP实现wscat”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。