PHP 位运算相关


位运算,&(与运算)、|(或运算)、^(异或运算),及性质应用。


正文

引言

如果你在代码中碰到这种情况能知道确切含义不:

function encode()
{
    $retval = chr((($this->fin & 0x1) << 7) | (($this->rsv & 0x7) << 4) | ($this->opcode & 0xF));
    
    $pl = strlen($this->payload);
    if ($pl < 126) {
        $retval .= chr(($pl & 0x7F) | (($this->masked & 0x1) << 7));
    } elseif ($pl <= 0xFFFF) {
        $retval .= chr(126 | (($this->masked & 0x1) << 7));
        $retval .= pack('n', $pl);
    } else {
        $retval .= chr(127 | (($this->masked & 0x1) << 7));
        for ($i = 7; $i >=0; $i--) {
            $retval .= chr(($pl >> (8 * $i)) & 0xFF);
        }
    }
    
    if ($this->masked) {
        $retval .= chr($this->masking_key[0]) . chr($this->masking_key[1]) . 
                   chr($this->masking_key[2]) . chr($this->masking_key[3]);
        for ($i = 0; $i < $pl; $i++) {
            $retval .= chr(ord($this->payload[$i]) ^ $this->masking_key[$i % 4]);
        }
    } else {
        $retval .= $this->payload;
    }
    
    return $retval;
}

chr() 函数

首先 chr() 函数,是从不同 ASCII 值返回字符, ASCII 值可被指定为十进制值、八进制值或十六进制值。 八进制值被定义为带前置 0,十六进制值被定义为带前置 0x。

echo chr(52) . "<br>"; // Decimal value   4
echo chr(052) . "<br>"; // Octal value    *
echo chr(0x52) . "<br>"; // Hex value     R

ord() 函数

ord() 函数返回字符串的首个字符的 ASCII 值。特别注意,是首个字符。

位运算

由于计算机中数据都是以二进制的形式存在的,那么对二进制位进行直接操作, 必定能够使算法更为高效,位运算便应运而生。

位运算的基本类型

按位与 & :两个位都为1时,结果才为1

按位或 | :两个位都为0时,结果才为0

按位异或 ^ :两个位为“异”(值不同),结果才为1,否则为0

取反 ~ :对各个位分别取反(~0变为1,~1变为0)

左移 << :将各二进制位左移若干位,高位(左边)舍弃,低位(右边)补0

右移 >> :将各二进制位右移若干位,低位(右边)舍弃,正数时高位(左边)补0,负数时高位(左边)补1

解析1

$retval = chr((($this->fin & 0x1) << 7) | (($this->rsv & 0x7) << 4) | ($this->opcode & 0xF));
($this->fin & 0x1) << 7  表示,$this->fin 和 0x1 (1) 进行 按位与 运算,然后 左移 7位;
$this->fin 和 0x1 (1) 进行 按位与 是判断奇偶,偶数结果为0,奇数结果为1;
然后左移7位,0还是0,1变成了128(每左移1位,相当于乘以2)。
($this->rsv & 0x7) << 4  表示,$this->rsv 和 0x7 (111) 进行 按位与 运算,然后 左移 4位;
$this->rsv 和 0x7 (111) 进行 按位与 111,
然后左移4位,。
$this->opcode & 0xF  表示,$this->rsv 和 0xF (1111)进行 按位与 运算,
$this->opcode 和 0xF (1111) 进行  按位与 1111,。
然后这三个数再按位或 |,完整组合起来相当于 10000000、01110000、00001111,结果11111111。

解析2

$retval .= chr(($pl & 0x7F) | (($this->masked & 0x1) << 7));
$pl & 0x7F 表示,$this->rsv 和 0xF (1111111)进行 按位与 运算,

($this->masked & 0x1) << 7,上面有说。
然后这两个数再按位或 |,完整组合起来相当于 01111111、10000000,结果11111111。

解析3

$retval .= chr(126 | (($this->masked & 0x1) << 7));
126转为二进制是 01111110;($this->masked & 0x1) << 7,上面有说。

然后这两个数再按位或 |,完整组合起来相当于 01111110、10000000,结果11111110。

解析4

$retval .= chr(127 | (($this->masked & 0x1) << 7));
127转为二进制是 01111111;($this->masked & 0x1) << 7,上面有说。
然后这两个数再按位或 |,完整组合起来相当于 01111111、10000000,结果11111111。

解析5

$retval .= chr(($pl >> (8 * $i)) & 0xFF);

7 >= $i >= 0。

0xFF 转为十进制是 255,是2的8次方减1,转为二进制是11111111。

其他解析

$pl = strlen($this->payload); 计算字符长度

0xFFFF 转为十进制是 65535,是2的16次方减1,转为二进制是1111111111111111。

总结

看到最后,应该发现了,这是一个加密算法。只有知道算法的对方,才能把最后的乱码还原为可理解的有意义的字符串。 其实看过的人应该知道了,这个是PHP通过TCP连接向SOCKET服务器发送数据时的数据处理, SOCKET服务器内置了通用解密算法,所以SOCKET服务器的编程人员可以直接使用传递过来的值, 如下面示例中的abc

顺便提一下,有一个通用的加密常量:258EAFA5-E914-47DA-95CA-C5AB0DC85B11, 这是SOCKET协议标准中定义的。

示例

$this->fin = 1;
$this->rsv = 0;
$this->opcode = 1;
$this->payload = 'abc';
$this->masked = 1;
$this->masking_key = [56, 207, 225, 38];

function encode()
{
    $retval = chr((($this->fin & 0x1) << 7) | (($this->rsv & 0x7) << 4) | ($this->opcode & 0xF));
    // $this->fin & 0x1   值为 1
    // ($this->fin & 0x1) << 7   值为 128
    // $this->rsv & 0x7   值为 0
    // ($this->rsv & 0x7) << 4   值为 0
    // $this->opcode & 0xF   值为 1
    // (($this->fin & 0x1) << 7) | (($this->rsv & 0x7) << 4)   值为 128
    // (($this->fin & 0x1) << 7) | (($this->rsv & 0x7) << 4) | ($this->opcode & 0xF)   值为 129
    // chr(129)  ASCII扩展字符 ü
    
    $pl = strlen($this->payload);  // $pl 值为 3
    if ($pl < 126) {
        $retval .= chr(($pl & 0x7F) | (($this->masked & 0x1) << 7));
        // $pl & 0x7F   值为 3
        // $this->masked & 0x1   值为 1
        // ($this->masked & 0x1) << 7   值为 128
        // ($pl & 0x7F) | (($this->masked & 0x1) << 7)   值为 131
        // chr(131)  ASCII扩展字符 &acirc;
    } elseif ($pl <= 0xFFFF) {
        $retval .= chr(126 | (($this->masked & 0x1) << 7));
        $retval .= pack('n', $pl);
    } else {
        $retval .= chr(127 | (($this->masked & 0x1) << 7));
        for ($i = 7; $i >=0; $i--) {
            $retval .= chr(($pl >> (8 * $i)) & 0xFF);
        }
    }
    
    if ($this->masked) {
        $retval .= chr($this->masking_key[0]) . chr($this->masking_key[1]) . 
                   chr($this->masking_key[2]) . chr($this->masking_key[3]);
        // chr(56)  ASCII字符 8
        // chr(207)  ASCII扩展字符 ╧
        // chr(225)  ASCII扩展字符 &szlig;
        // chr(38)  ASCII字符 &

        for ($i = 0; $i < $pl; $i++) {
            $retval .= chr(ord($this->payload[$i]) ^ $this->masking_key[$i % 4]);
            // $i  值为 0
            // $this->payload[$i]  值为 a
            // ord($this->payload[$i])  值为 97
            // $i % 4  值为 0
            // $this->masking_key[$i % 4]  值为 56
            // ord($this->payload[$i]) ^ $this->masking_key[$i % 4]  值为 89
            // chr(89)  ASCII字符 Y
            // $i  值为 1
            // $this->payload[$i]  值为 b
            // ord($this->payload[$i])  值为 98
            // $i % 4  值为 1
            // $this->masking_key[$i % 4]  值为 207
            // ord($this->payload[$i]) ^ $this->masking_key[$i % 4]  值为 173
            // chr(173)  ASCII扩展字符 &iexcl;
            // $i  值为 2
            // $this->payload[$i]  值为 c
            // ord($this->payload[$i])  值为 99
            // $i % 4  值为 2
            // $this->masking_key[$i % 4]  值为 225
            // ord($this->payload[$i]) ^ $this->masking_key[$i % 4]  值为 130
            // chr(130)  ASCII扩展字符 é
        }
    } else {
        $retval .= $this->payload;
    }
    
    return $retval;  // ƒ8Ïá&Y­‚
}

代码组件

上面说到PHP通过TCP连接向WebSocket服务器发送数据,下面就给出完整的代码组件,并附上用法。

common/widgets/WSClient.php:

namespace common\widgets;

/**
 * Websocket client base class
 */
class WSClient
{
    const hashstring = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

    public $host;
    public $port;
    public $uri;
    public $secure;
    public $origin = 'null';

    public $errno = 0;
    public $errstr = "";

    private $async = false;
    private $socket = false;
    private $curframe;
    private $recvframes;

    function __construct($host, $uri = '/', $secure = false, $port = false)
    {
        $this->host = $host;
        $this->uri = $uri;
        if ($port === false) {
            $this->port = $secure ? 443 : 80;
        } else {
            $this->port = $port;
        }
        $this->secure = $secure;
        $this->recvframes = array();
    }

    function __destruct()
    {
        $this->disconnect();
    }

    /*
     * Connect to websocket server, switch protocols
     * name: WSClient::connect
     * @param boolean $async
     * @return boolean
     *
     */
    function connect($async = false)
    {
        $this->async = $async;

        $remote = ($this->secure ? 'tls' : 'tcp') . '://' . $this->host . ':' . $this->port;
        $this->socket = @stream_socket_client($remote, $this->errno, $this->errstr);
        if ($this->socket === false) return false;

        /*
            Create and send HTTP request
        */
        $seckey = base64_encode($this->makeKey());
        $request = "GET $this->uri HTTP/1.1\r\n".
            "Connection: Upgrade\r\n".
            "Upgrade: websocket\r\n".
            "Pragma: no-cache\r\n".
            "Cache-Control: no-cache\r\n".
            "Host: $this->host\r\n".
            "Origin: $this->origin\r\n".
            "Sec-WebSocket-Version: 13\r\n".
            "Sec-WebSocket-Key: $seckey\r\n".
            "\r\n";

        if (!$this->fwrite_stream($request)) {
            $this->errstr = "Error sending request";
            $this->disconnect();
            return false;
        }

        /*
            Read and check HTTP response
        */
        $response = array();
        do {
            $line = fgets($this->socket, 1024);
            if ($line === false) {
                $this->errstr = "Error reading response";
                $this->disconnect();
                return false;
            }
            $response[] = trim($line);
        } while (strlen(trim($line)) > 0);

//        var_dump($response);
        $parsed = $this->parseResponse($response);
//        var_dump($parsed);

        if ($parsed['responseCode'] != 101) {
            $this->errstr = "Invalid response code";
            $this->disconnect();
            return false;
        }

        if (!isset($parsed['headers']['Upgrade']) || strcasecmp($parsed['headers']['Upgrade'], 'websocket') ||
            !isset($parsed['headers']['Connection']) || strcasecmp($parsed['headers']['Connection'], 'Upgrade')) {
            $this->errstr = "Invalid response headers";
            $this->disconnect();
            return false;
        }

//        echo base64_decode($parsed['headers']['Sec-WebSocket-Accept'])."\n";
//        echo sha1($seckey.self::hashstring, true)."\n";

        if (!isset($parsed['headers']['Sec-WebSocket-Accept']) || strcmp(base64_decode($parsed['headers']['Sec-WebSocket-Accept']), sha1($seckey.self::hashstring, true))) {
            $this->errstr = "Invalid security key";
            $this->disconnect();
            return false;
        }

        $this->curframe = new WSFrame();

        return true;
    }

    /*
     * Disconnect from server
     * name: WSClient::disconnect
     *
     */
    function disconnect()
    {
        if ($this->socket) @fclose($this->socket);
        $this->socket = false;
    }

    /*
     * Get information about connection
     * name: WSClient::getMetadata
     * @return array
     *
     */
    function getMetadata()
    {
        if ($this->socket) {
            return stream_get_meta_data($this->socket);
        } else {
            return array();
        }
    }

    /*
     * Make 16-byte random string
     * @return string
     */
    function makeKey()
    {
        $key = "";
        mt_srand();
        for ($i = 0; $i < 16; $i++) $key .= chr(mt_rand(1, 255));
        return $key;
    }

    /*
     * Send string to stream
     * @param string $string
     * @return mixed
     */
    function fwrite_stream($string)
    {
        $count = 0;

        for ($written = 0; $written < strlen($string); $written += $fwrite)
        {
            $fwrite = @fwrite($this->socket, substr($string, $written));
            if ($fwrite === false) return false;

            if ($fwrite === 0) {
                $count ++;
                if ($count >= 10) {
                    return false;
                } else {
                    continue;
                }
            } else {
                $count = 0;
            }
        }

        return $written;
    }

    /*
     * Parse HTTP response
     * name: WSClient::parseResponse
     * @param array $response
     * @return array
     *
     */
    function parseResponse($response)
    {
        $res = array();

        if (!preg_match('/^HTTP\/([0-9\.]+)\s+([0-9]+)\s+(.*)/', $response[0], $parts)) return false;
        $res['httpVersion'] = $parts[1];
        $res['responseCode'] = $parts[2];
        $res['responseText'] = $parts[3];
        $res['headers'] = array();

        foreach ($response as $num=>$line)
        {
            if (!$num) continue;
            $parts = preg_split('/:\s+/', $line, 2);
            if (count($parts) != 2) continue;
            if (array_key_exists($parts[0], $res['headers'])) {
                if (!is_array($res['headers'][$parts[0]])) {
                    $res['headers'][$parts[0]] = array($res['headers'][$parts[0]]);
                }
                $res['headers'][$parts[0]][] = $parts[1];
            } else {
                $res['headers'][$parts[0]] = $parts[1];
            }
        }

        return $res;
    }

    /*
     * Prepare and send frame
     * name: WSClient::send
     * @param int $opcode
     * @param string $payload
     * @param int $masked
     * @param array $masking_key
     * @return boolean
     *
     */
    function send($opcode, $payload, $masked = 1, $masking_key = array())
    {
        $frame = new WSFrame();
        $frame->set($opcode, $payload, $masked, $masking_key);

        return $this->sendFrame($frame);
    }

    /*
     * Send prepared frame
     * name: WSClient::sendFrame
     * @param WSFrame $frame
     * @return boolean
     *
     */
    function sendFrame($frame)
    {
        if ($this->fwrite_stream($frame->encode()) === false) {
            $this->errstr = "Write error";
            $this->disconnect();
            return false;
        }

        return true;
    }

    /*
     * Read frame from websocket
     * name: WSClient::read
     * @return mixed
     *
     */
    function read()
    {
        if ($this->async) {
            $read   = array($this->socket);
            $write  = null;
            $except = null;
            $num = @stream_select($read, $write, $accept, 0, 200000);
            if ($num === false) {
                $this->errstr = "Select error";
                $this->disconnect();
                return false;
            }

            if ($num) {
                $recvbuf = @fread($this->socket, 8192);
                while (strlen($recvbuf) > 0) {
                    $decoded = $this->curframe->decode($recvbuf);
                    if ($this->curframe->framestate == FRAME_STATE_ERROR) {
                        $this->errstr = "Frame decoding error " . $this->curframe->errcode;
                        $this->disconnect();
                        return false;
                    } elseif ($this->curframe->framestate == FRAME_STATE_COMPLETED) {
                        if ($this->processFrame($this->curframe)) {
                            $recvbuf = substr($recvbuf, $decoded);
                            $this->curframe = new WSFrame();
                        } else {
                            $this->disconnect();
                            return false;
                        }
                    }
                }
            }
        } else {
            for (; ; ) {
                $recvbuf = @fread($this->socket, 1);
                $decoded = $this->curframe->decode($recvbuf);
                if ($this->curframe->framestate == FRAME_STATE_ERROR) {
                    $this->errstr = "Frame decoding error " . $this->curframe->errcode;
                    $this->disconnect();
                    return false;
                } elseif ($this->curframe->framestate == FRAME_STATE_COMPLETED) {
                    if ($this->processFrame($this->curframe)) {
                        $this->curframe = new WSFrame();
                        break;
                    } else {
                        $this->disconnect();
                        return false;
                    }
                }
            }
        }

        return count($this->recvframes);
    }

    /*
     * Process frame
     * name: WSClient::processFrame
     * @param WSFrame $frame
     * @return boolean;
     *
     */
    private function processFrame($frame)
    {
        switch ($frame->opcode)
        {
            case WS_FRAME_CLOSE:
                $this->errstr = "Disconnect requested by server";
                if ($frame->payload) {
                    $this->errno = unpack("n", substr($frame->payload, 0, 2));
                    $this->errstr .= " (" . $this->errno . ") " . substr($frame->payload, 2);
                }
                return false;
                break;

            case WS_FRAME_PING:
                $pong = new WSFrame();
                $pong->set(WS_FRAME_PONG, $frame->payload, 1);
                if (!$this->sendFrame($pong)) {
                    $this->errstr = "Error sending pong";
                    return false;
                }
                break;

            case WS_FRAME_PONG:
                break;

            default:
                array_push($this->recvframes, $frame);
        }
        return true;
    }

    /*
     * Get frame from receive buffer
     * name: WSClient::getFrame
     * @return WSFrame
     *
     */
    public function getFrame()
    {
        return array_shift($this->recvframes);
    }

}

common/widgets/WSFrame.php:

namespace common\widgets;

/* Opcodes */
define("WS_FRAME_INVALID", -1);
define("WS_FRAME_CONT", 0x0);
define("WS_FRAME_TEXT", 0x1);
define("WS_FRAME_BINARY", 0x2);
define("WS_FRAME_03", 0x3);
define("WS_FRAME_04", 0x4);
define("WS_FRAME_05", 0x5);
define("WS_FRAME_06", 0x6);
define("WS_FRAME_07", 0x7);
define("WS_FRAME_CLOSE", 0x8);
define("WS_FRAME_PING", 0x9);
define("WS_FRAME_PONG", 0xA);
define("WS_FRAME_0B", 0xB);
define("WS_FRAME_0C", 0xC);
define("WS_FRAME_0D", 0xD);
define("WS_FRAME_0E", 0xE);
define("WS_FRAME_0F", 0xF);

/* Frame decoding states */
define("FRAME_STATE_ERROR", -1);
define("FRAME_STATE_BEGIN", 0);
define("FRAME_STATE_LEN", 1);
define("FRAME_STATE_KEY", 2);
define("FRAME_STATE_PAYLOAD", 3);
define("FRAME_STATE_COMPLETED", 4);

/* Frame error codes */
define("FRAME_ERROR_NONE", 0);
define("FRAME_ERROR_INTERNAL", 1);
define("FRAME_ERROR_TOO_LARGE", 2);

/**
 * Websocket Frame
 */
class WSFrame
{
    public $fin;
    public $rsv;
    public $opcode;
    public $masked;
    public $length;
    public $masking_key;
    public $payload;

    public $errcode;
    public $framestate;

    private $framepos;
    private $lenlen;
    private $lenbuf;

    function __construct()
    {
        $this->clear();
    }

    /*
     * Clear object
     * name: WSFrame::clear
     *
     */
    function clear()
    {
        $this->fin = 0;
        $this->rsv = 0;
        $this->opcode = WS_FRAME_INVALID;
        $this->masked = 0;
        $this->length = 0;
        $this->masking_key = array();
        $this->payload = "";
        $this->errcode = FRAME_ERROR_NONE;
        $this->framepos = 0;
        $this->framestate = FRAME_STATE_BEGIN;
        $this->lenlen = 0;
        $this->lenbuf = array();
    }

    /*
     * Create simple frame
     * name: WSFrame::set
     * @param int $opcode
     * @param string $payload
     * @param int $masked
     * @param array $masking_key
     *
     */
    function set($opcode, $payload, $masked = 0, $masking_key = array())
    {
        $this->fin = 1;
        $this->payload = $payload;
        $this->length = strlen($payload);
        $this->opcode = $opcode;
        $this->masked = $masked;
        if ($masked) {
            if (count($masking_key) == 4) {
                $this->masking_key = $masking_key;
            } else {
                $this->genkey();
            }
        }
        $this->framestate = FRAME_STATE_COMPLETED;
    }

    /*
     * Generate random masking key
     * name: WSFrame::genkey
     *
     */
    function genkey()
    {
        mt_srand();
        $this->masking_key = array(
            mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255)
        );
    }

    /*
     * Decode portion of data
     * name: WSFrame::decode
     * @param string $data
     * @return int
     *
     */
    function decode($data)
    {
        $datalen = strlen($data);
        $datapos = 0;
        while ($datapos < $datalen)
        {
            if (($this->framestate == FRAME_STATE_ERROR) || ($this->framestate == FRAME_STATE_COMPLETED)) return $datapos;

            $byte = ord($data[$datapos]);
            switch ($this->framestate)
            {
                case FRAME_STATE_BEGIN:
                    if ($this->framepos == 0) {
                        // Process first byte of frame
                        $this->fin = ($byte >> 7) & 0x01;
                        $this->rsv = ($byte >> 4) & 0x07;
                        $this->opcode = $byte & 0x0F;
                    } elseif ($this->framepos == 1) {
                        // Process second byte of frame
                        $this->masked = ($byte >> 7) & 0x01;
                        $pl = $byte & 0x7F;
                        switch($pl) {
                            case 126:
                                $this->lenlen = 2;
                                $this->framestate = FRAME_STATE_LEN;
                                break;
                            case 127:
                                $this->lenlen = 8;
                                $this->framestate = FRAME_STATE_LEN;
                                break;
                            default:
                                $this->lenlen = 0;
                                $this->length = $pl;
                                $this->framestate = $pl ? ($this->masked ? FRAME_STATE_KEY : FRAME_STATE_PAYLOAD) : FRAME_STATE_COMPLETED;
                        }
                    } else {
                        $this->framestate = FRAME_STATE_ERROR;
                        $this->errcode = FRAME_ERROR_INTERNAL;
                    }
                    $this->framepos++;
                    break;
                case FRAME_STATE_LEN:
                    if (($this->framepos >=2) && ($this->framepos < (2 + $this->lenlen))) {
                        $this->lenbuf[] = $byte;
                        if (count($this->lenbuf) == $this->lenlen) {
                            for ($i = $this->lenlen - 1; $i >= 0; $i--) {
                                //TODO: Add overflow handling for <64 bit OS
                                $this->length |= $this->lenbuf[$i] << (8 * ($this->lenlen - $i - 1));
                            }
                            $this->framestate = $this->masked ? FRAME_STATE_KEY : FRAME_STATE_PAYLOAD;
                        }
                    } else {
                        $this->framestate = FRAME_STATE_ERROR;
                        $this->errcode = FRAME_ERROR_INTERNAL;
                    }
                    $this->framepos++;
                    break;
                case FRAME_STATE_KEY:
                    if (($this->framepos >= (2 + $this->lenlen)) && ($this->framepos < (6 + $this->lenlen))) {
                        $this->masking_key[] = $byte;
                        if (count($this->masking_key) == 4) {
                            $this->framestate = $this->length ? FRAME_STATE_PAYLOAD : FRAME_STATE_COMPLETED; // empty masked frame?
                        }
                    } else {
                        $this->framestate = FRAME_STATE_ERROR;
                        $this->errcode = FRAME_ERROR_INTERNAL;
                    }
                    break;
                case FRAME_STATE_PAYLOAD:
                    $pl = strlen($this->payload);
                    if ($pl < $this->length) {
                        if ($this->masked) {
                            $this->payload .= chr($byte ^ $this->masking_key[$pl % 4]);
                        } else {
                            $this->payload .= chr($byte);
                        }
                        if (++$pl == $this->length) $this->framestate = FRAME_STATE_COMPLETED;
                    }
                    $this->framepos++;
                    break;
                case FRAME_STATE_COMPLETED:
                    return $datapos;
                default:
            }
            $datapos++;
        }
        return $datapos;
    }

    /*
     * Is frame completed?
     * name: WSFrame::completed
     * @return boolean
     *
     */
    function completed()
    {
        return ($this->framestate == FRAME_STATE_COMPLETED);
    }

    /*
     * Encode frame
     * name: WSFrame::encode
     * @return string
     *
     */
    function encode()
    {
        $retval = chr((($this->fin & 0x1) << 7) | (($this->rsv & 0x7) << 4) | ($this->opcode & 0xF));

        $pl = strlen($this->payload);
        if ($pl < 126) {
            $retval .= chr(($pl & 0x7F) | (($this->masked & 0x1) << 7));
        } elseif ($pl <= 0xFFFF) {
            $retval .= chr(126 | (($this->masked & 0x1) << 7));
            $retval .= pack('n', $pl);
        } else {
            $retval .= chr(127 | (($this->masked & 0x1) << 7));
            for ($i = 7; $i >=0; $i--) {
                $retval .= chr(($pl >> (8 * $i)) & 0xFF);
            }
        }

        if ($this->masked) {
            $retval .= chr($this->masking_key[0]) . chr($this->masking_key[1]) .
                chr($this->masking_key[2]) . chr($this->masking_key[3]);
            for ($i = 0; $i < $pl; $i++) {
                $retval .= chr(ord($this->payload[$i]) ^ $this->masking_key[$i % 4]);
            }
        } else {
            $retval .= $this->payload;
        }

        return $retval;
    }
}

使用示例:

require __DIR__.'/common/widgets/WSClient.php';
require __DIR__.'/common/widgets/WSFrame.php';

$socket = new common\widgets\WSClient(
    '127.0.0.1',
    '/',
    false,
    9501
);

if (!$socket->connect(true)) {
    die('connect failed');
}

$msg = "abc123";

if (!$socket->send(WS_FRAME_TEXT, $msg, 1)) {
    echo $socket->errstr . "\n";
    die;
}






参考资料

位运算 https://blog.csdn.net/xiaopihaierletian/article/details/78162863

位运算 https://www.imooc.com/article/21962

位运算 https://www.cnblogs.com/joahyau/p/6420619.html

chr http://www.php.net/manual/zh/function.chr.php

chr http://www.w3school.com.cn/php/func_string_chr.asp

chr http://www.runoob.com/php/func-string-chr.html

ord http://www.php.net/manual/zh/function.ord.php

ord http://www.w3school.com.cn/php/func_string_ord.asp

ord http://www.runoob.com/php/func-string-ord.html

ASCII,Unicode和UTF-8终于找到一个能完全搞清楚的文章了 https://blog.csdn.net/Deft_MKJing/article/details/79460485

在线进制转换工具 https://tool.lu/hexconvert/

ASCII 码表对照 http://www.cnblogs.com/Frank99/p/5951204.html

swoole/framework提供的 PHP WebSocket客户端 https://github.com/matyhtf/framework/blob/master/libs/Swoole/Client/WebSocket.php

WebSocket客户端 实现 https://github.com/matyhtf/framework/tree/master/libs/Swoole/Client


返回