WebSocket Back

We use WebSocket to create a connection between Client and Server, to send or receive data.

Client (JavaScript)

1. new an object

/**
 * [WebSocket]
 * @param {[string]} url      [url to connect]
 * @param {[string]} protocol [protocol designed by servers, which will be a empty string by default]
 */
var webSocket = new WebSocket('http://www.example.com/socketserver' 'socket');

2. available methods

/**
 * [close description]
 * @param  {[number]} code   [status code, which is 1000 by default, meaning normal closing]
 * @param  {[string]} reason [the reason why]
 */
webSocket.close('300', 'Personal Reson');

webSocket.send('data');

A list of Close Status Code:

Status Name Description
0–999 Reserved and not used.
1000 CLOSE_NORMAL Normal closure; the connection successfully completed whatever purpose for which it was created.
1001 CLOSE_GOING_AWAY The endpoint is going away, either because of a server failure or because the browser is navigating away from the page that opened the connection.
1002 CLOSE_PROTOCOL_ERROR The endpoint is terminating the connection due to a protocol error.
1003 CLOSE_UNSUPPORTED The connection is being terminated because the endpoint received data of a type it cannot accept (for example, a text-only endpoint received binary data).
1004 Reserved. A meaning might be defined in the future.
1005 CLOSE_NO_STATUS Reserved. Indicates that no status code was provided even though one was expected.
1006 CLOSE_ABNORMAL Reserved. Used to indicate that a connection was closed abnormally (that is, with no close frame being sent) when a status code is expected.
1007 Unsupported Data The endpoint is terminating the connection because a message was received that contained inconsistent data (e.g., non-UTF-8 data within a text message).
1008 Policy Violation The endpoint is terminating the connection because it received a message that violates its policy. This is a generic status code, used when codes 1003 and 1009 are not suitable.
1009 CLOSE_TOO_LARGE The endpoint is terminating the connection because a data frame was received that is too large.
1010 Missing Extension The client is terminating the connection because it expected the server to negotiate one or more extension, but the server didn't.
1011 Internal Error The server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
1012 Service Restart The server is terminating the connection because it is restarting. [Ref]
1013 Try Again Later The server is terminating the connection due to a temporary condition, e.g. it is overloaded and is casting off some of its clients. [Ref]
1014 Reserved for future use by the WebSocket standard.
1015 TLS Handshake Reserved. Indicates that the connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified).
1016–1999 Reserved for future use by the WebSocket standard.
2000–2999 Reserved for use by WebSocket extensions.
3000–3999 Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve.
4000–4999 Available for use by applications.

3. available attributes

Name Type Description
binaryType String Type of transferring data, which should be 'blob' or 'arraybuffer'
bufferAmount Number Number of sendding data (read-only)
extensions String Extensions the Server chooses
url String Constructor url
protocol String Constructor protocol
readyState Number Connection state
onclose Event Close listener
onerror Event Error listener
onmessage Event Message Listener
onopen Event Open listener

A list of Ready State Code

Constant Value Description
CONNECTING 0 The connection is setting up
OPEN 1 The connection has been built up
CLOSING 2 The connection is closing
CLOSED 3 The connection has been closed, or failed to build up

4. Compatibility Problems

  • Desktop
Features Chrome Firefox (Gecko) Internet Explorer Opera Safari
Basic (Yes) 4.0 (2.0) (Yes) (Yes) ?
Words Protocol ? 6.0 (6.0) ? ? ?

Notice that: After Gecko 6.0, constructor has been changed to MozWebSocket.

Notice that: extensions attribute is only supported by Gecko 8.0 or more.

  • Mobile
Features Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Basic ? 7.0 (7.0) ? ? ?
Words Protocol ? 7.0 (7.0) ? ? ?

Server (PHP)

Even though you're building a server, a client still has to start the WebSocket handshake process. So you must know how to interpret the client's request. The client will send a pretty standard HTTP request that looks like this (the HTTP version must be 1.1 or greater, and the method must be GET):

GET /socketserver HTTP/1.1
Host: www.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: socket
Sec-WebSocket-Version: 13

1. create a class

<?php
class WebSocket
{
    /** the client */
    private $master;

    /** sockets with different state */
    private $sockets = [];

    /** whether has a handShake */
    private $handShake = false;

    /** extract key from the request header */
    private function getKey($req)
    {
        $key = null;

        if (preg_match('/Sec-WebSocket-Key: (.*)\r\n/'), $req, $match) {
            $key = $match[1];
        }

        return $key;
    }

    /** encrypt key */
    private function encry($req)
    {
        $key = $this->getKey($req);
        $mask = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

        return base64_encode(sha1($key, $mask, true));
    }

    /** under Version 13 */
    private function encryOld($key1, $key2, $l8b)
    {
        $key1_num = implode($key1_num[0]);
        $key2_num = implode($key2_num[0]);

        /** count space */
        preg_match_all('/([ ]+)/', $key1, $key1_spc);
        preg_match_all('/([ ]+)/', $key2, $key2_spc);

        if ($key1_spc === 0 || $key2_spc === 0) { 
            $this->log("Invalid key");
            return;
        }

        /** some math */
        $key1_sec = pack('N', $key1_num / $key1_spc);
        $key2_sec = pack('N', $key2_num / $key2_spc);

        return md5($key1_sec . $key2_sec . $l8b, 1);
    }

    /** hand shake */
    private function doHandShake($socket, $req)
    {
        $acceptKey = $this->encry($req);

        $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
           "Upgrade: websocket\r\n" .
           "Connection: Upgrade\r\n" .
           "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" .
           "\r\n";

        socket_write($socket, $upgrade.chr(0), strlen($upgrade.chr(0)));

        $this->handShake = true;
    }

    /** parse data frames */
    private function decode($buffer)
    {
        $len = $masks = $data = $decoded = null;
        $len = ord($buffer[1]) & 127;

        if ($len === 126)  {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127)  {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } else  {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }

        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }

        return $decoded;
    }

    /** generate data frames */
    private function encode($msg)
    {
        $str = str_split($msg, 125);

        if (count($str) === 1) {
            return "\x81" . chr(strlen($str[0])) . $str[0];
        }

        $ns = '';

        foreach ($str as $o) {
            $ns .= "\x81" . chr(strlen($o)) . $o;
        }

        return $ns;
    }

    /** send messages to the client */
    private function send($client, $msg)
    {
        $msg = $this->encode($msg);
        socket_write($client, $msg, strlen($msg));
    }

    public function __construct($address, $port = 80)
    {
        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) || die('socket_create() failed');

        socket_set_option($this->master, SQL_SOCKET, SO_REUSEADR, 1) || die('socket_option() failed');

        socket_bind($this->master, $address, $port) || die('socket_bind() failed');

        socket_listen($this->master, 2) || die('socket_listen() failed');

        $this->sockets[] = $this->master;

        /** loop for listen */
        for (;;) {
            $write = null;
            $except = null;

            socket_select($this->sockets, $write, $except, null);

            foreach ($this->sockets as $socket) {
                if ($socket === $this->master) {
                    $client = socket_accept($this->master);

                    if ($client < 0) {
                        echo 'socket_accept() failed';
                        continue;
                    } else {
                        /** connect($client); */
                        array_push($this->sockets, $client);
                        echo 'connect client';
                    }
                } else {
                    $bytes = @socket_recv($socket, $buffer, 2048, 0);

                    if ($bytes == 0) {
                        return;
                    }

                    if (!$this->handshake) {
                        /** echo 'shakeHands'; */
                        $this->doHandShake($socket, $buffer);
                    } else {
                        /** echo 'send file'; */
                        $buffer = $this->decode($buffer);
                        /** process($socket, $buffer); */
                    }
                }
            }
        }
    }
}
?>

The format of a Received Data Frame

 0               1               2               3              
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 4               5               6               7              
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
 8               9               10              11             
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
 12              13              14              15
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

2. new an object

<?php
$ws = new WebSocket('localhost');
?>
Empty Comments
Sign in GitHub

As the plugin is integrated with a code management system like GitLab or GitHub, you may have to auth with your account before leaving comments around this article.

Notice: This plugin has used Cookie to store your token with an expiration.