티스토리 뷰

Code

WebSocket for php test

풀짱 2020. 12. 14. 09:45

# client.html

<html>
<head>
    <title>WebSocket</title>

    <style>
        html, body {
            font: normal 0.9em arial, helvetica;
        }

        #log {
            width: 440px;
            height: 200px;
            border: 1px solid #7F9DB9;
            overflow: auto;
        }

        #msg {
            width: 330px;
        }
    </style>

    <script>
        var socket;

        function init() {
            var host = "ws://192.168.0.103:65001/test/server.php";
            try {
                socket = new WebSocket(host);
                log('WebSocket - status ' + socket.readyState);
                socket.onopen = function (msg) {
                    log("Welcome - status " + this.readyState);
                };
                socket.onmessage = function (msg) {
                    log("Received: " + msg.data);
                };
                socket.onclose = function (msg) {
                    log("Disconnected - status " + this.readyState);
                };
            } catch (ex) {
                log(ex);
            }
            $("msg").focus();
        }

        function send() {
            var txt, msg;
            txt = $("msg");
            msg = txt.value;
            if (!msg) {
                alert("Message can not be empty");
                return;
            }
            txt.value = "";
            txt.focus();
            try {
                socket.send(msg);
                log('Sent: ' + msg);
            } catch (ex) {
                log(ex);
            }
        }

        function quit() {
            log("Goodbye!");
            socket.close();
            socket = null;
        }

        // Utilities
        function $(id) {
            return document.getElementById(id);
        }

        function log(msg) {
            $("log").innerHTML += "<br>" + msg;
        }

        function onkey(event) {
            if (event.keyCode == 13) {
                send();
            }
        }
    </script>

</head>
<body onload="init()">
<h3>WebSocket v2.00</h3>
<div id="log"></div>
<input id="msg" type="textbox" onkeypress="onkey(event)"/>
<button onclick="send()">Send</button>
<button onclick="quit()">Quit</button>
<div>Commands: hello, hi, name, age, date, time, thanks, bye</div>
</body>
</html>

 

 

# php -f server.php

<?php
error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();


class SocketService
{
    private $address = '192.168.0.103';
    private $port = 65001;
    private $_sockets;

    public function __construct($address = '', $port = '')
    {
        if (!empty($address)) {
            $this->address = $address;
        }
        if (!empty($port)) {
            $this->port = $port;
        }
    }

    public function service()
    {
        //Get the tcp protocol number.
        $tcp = getprotobyname("tcp");
        $sock = socket_create(AF_INET, SOCK_STREAM, $tcp);
        //socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);
        if ($sock < 0) {
            throw new Exception("failed to create socket: " . socket_strerror($sock) . "\n");
        }
        socket_bind($sock, $this->address, $this->port);
        socket_listen($sock, $this->port);
        echo "listen on $this->address $this->port ... \n";
        $this->_sockets = $sock;
    }

    public function run()
    {
        $this->service();
        $clients[] = $this->_sockets;
        while (true) {
            $changes = $clients;
            $write = NULL;
            $except = NULL;
//When select is waiting, a of the two clients sends the data first, then socket [Select] will keep the socket of a in $changes and run down, and the socket of the other client will be discarded, so when looping again, it will only listen to A. This can add all the linked client sockets into $changes again in the new loop, so this logic error of this program can be avoided
            /** socket_select It's blocking. Only when there is a data request can it be processed. Otherwise, it will be blocked all the time
             * Here $changes will read to the currently active connection
             * For example, the data before socket select is as follows (describe the resource ID of socket):
             * $socket = Resource id #4
             * $changes = Array
             *       (
             *           [0] => Resource id #5 //Client 1
             *           [1] => Resource id #4 //server socket resource of bound port
             *       )
             * After calling socket select, there are two situations:
             * Case 1: if it is a new client 2 connection, $changes = array ([1] = > resource ID × 4), which is now used to receive the new client 2 connection
             * Case 2: if client 1 (resource ID ා) sends a message, then $changes = array ([1] = > resource ID ා), and the user receives the data of client 1
             *
             * As can be seen from the above description, socket [Select] has two functions, which also realizes IO multiplexing
             * 1,Here comes the new client. Introduce the new connection through resource ID 4, as shown in case 1
             * 2,If there is a connection to send data, switch to the current connection in real time and receive data, as in case 2*/
            socket_select($changes, $write, $except, NULL);
            foreach ($changes as $key => $_sock) {
                if ($this->_sockets == $_sock) { //Judge whether it is a new socket
                    if (($newClient = socket_accept($_sock)) === false) {
                        die('failed to accept socket: ' . socket_strerror($_sock) . "\n");
                    }
                    $line = trim(socket_read($newClient, 1024));
                    if ($line === false) {
                        socket_shutdown($newClient);
                        socket_close($newClient);
                        continue;
                    }
                    $this->handshaking($newClient, $line);
                    //Get client ip
                    socket_getpeername($newClient, $ip);
                    $clients[] = $newClient;
                    echo "Client ip:{$ip}  \n";
                    echo "Client msg:{$line} \n";
                } else {
                    $byte = socket_recv($_sock, $buffer, 2048, 0);

                    if ($byte < 7) continue;

                    $msg = $this->message($buffer);
                    //Business code here
                    echo "{$key} clinet msg:", $msg, "\n";
                    //fwrite(STDOUT, 'Please input a argument:');
                    //$response = trim(fgets(STDIN));

                    foreach ($clients as $key2 => $_sock2) {

                        if ($key2 === 0) {
                            continue;
                        }
                        if ($_sock != $_sock2) {
                            if ($this->send($_sock2, $msg) === false) {
                                socket_close($_sock2);
                                unset($clients[$key2]);
                            }
                            echo "{$key2} response to Client:" . $msg, "\n";
                        }
                    }
                }
            }
        }
    }

    /**
     * handshake processing
     * @param $newClient socket
     * @return int Information received
     */
    public function handshaking($newClient, $line)
    {

        $headers = array();
        $lines = preg_split("/\r\n/", $line);
        foreach ($lines as $line) {
            $line = rtrim($line);
            if (preg_match('/^(\S+): (.*)$/', $line, $matches)) {
                $headers[$matches[1]] = $matches[2];
            }
        }
        $secKey = $headers['Sec-WebSocket-Key'];
        $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
        $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "WebSocket-Origin: $this->address\r\n" .
            "WebSocket-Location: ws://$this->address:$this->port/websocket/websocket\r\n" .
            "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
        return socket_write($newClient, $upgrade, strlen($upgrade));
    }

    /**
     * Parsing received data
     * @param $buffer
     * @return null|string
     */
    public function message($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;
    }

    /**
     * send data
     * @param $newClinet New socket
     * @param $msg  Data to send
     * @return int|string
     */
    public function send($newClinet, $msg)
    {
        $msg = $this->frame($msg);
        return socket_write($newClinet, $msg, strlen($msg));
    }

    public function frame($s)
    {
        $a = str_split_unicode($s, 125);
        if (count($a) == 1) {
            return "\x81" . chr(strlen($a[0])) . $a[0];
        }
        $ns = "";
        foreach ($a as $o) {
            $ns .= "\x81" . chr(strlen($o)) . $o;
        }
        return $ns;
    }

    public function str_split_unicode($str, $l = 0)
    {
        if ($l > 0) {
            $ret = array();
            $len = mb_strlen($str, "UTF-8");
            for ($i = 0; $i < $len; $i += $l) {
                $ret[] = mb_substr($str, $i, $l, "UTF-8");
            }
            return $ret;
        }
        return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY);
    }

    /**
     * Close socket
     */
    public function close()
    {
        return socket_close($this->_sockets);
    }
}

$sock = new SocketService();
$sock->run();