怎么用PHP实现wscat

发布时间:2021-06-25 14:00:13 作者:chen
来源:亿速云 阅读:152

本篇内容介绍了“怎么用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”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

推荐阅读:
  1. 怎么用PHP实现杨辉三角
  2. 用php实现反射的方法

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

php

上一篇:php如何实现观察者模式Observer

下一篇:php如何实现递归提成方案

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》