html5 WebSocket 与 PHP socket 聊天室原理

时间:2021-05-30 10:10:08

html js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>聊天室</title>
    <link rel="stylesheet" href="css/style.css">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
</head>
<body>
<div class="head"></div>
<div id="wrapper">
    <div id="message">
    
    </div>
    <div id="action">
        <textarea id="data"></textarea>
        <button id="send">发送</button>
    </div>
    
</div>

<script>
    (function() {

        var socket = new WebSocket('ws://127.0.0.1:8008');
        var send = document.getElementById('send');
        var data = document.getElementById('data');
        var message = document.getElementById('message');
        var wrapper = document.getElementById('wrapper');
        var height = (wrapper.offsetHeight) -270;

        message.style.height = height+'px';
        socket.onopen = function(event) {
            message.innerHTML = '<p><span>连接成功!</span></p>';
        }

        socket.onmessage = function(event) {
            var dl = document.createElement('dl');
            var jsonData = JSON.parse(event.data);
            dl.innerHTML =  "<dt><img src="+jsonData.avatar+"><dt><dd><span></span>"+jsonData.content+"</dd>";
            message.appendChild(dl);
            message.scrollTop = message.scrollHeight;
        }

        socket.onerror = function() {
            message.innerHTML = '<p><span>连接失败!</span></p>';
        }

        send.addEventListener('click', function() {

            var content = data.value;
            if(content.length <= 0) {
                alert('消息不能为空!');
                return false;
            }

            var avatar = Math.random();
            var message = {
                "avatar" : 'images/avatar.jpg',
                "content" : content
            }

            var json = JSON.stringify(message);
            socket.send(json);

            data.value = ''; data.focus();
            
        });
    })();
</script>
</body>
</html>

PHP

<?php

class WebSocket {

    private $socket;

    private $accept;

    private $isHand = array();

    public function __construct($host, $port, $max) {
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, TRUE);
        socket_bind($this->socket, $host, $port);
        socket_listen($this->socket, $max);
    }

    public function start() {
        while(true) {
            $cycle = $this->accept;
            $cycle[] = $this->socket;
            socket_select($cycle, $write, $except, null);

            foreach($cycle as $sock) {
                if($sock === $this->socket) {
                    $client = socket_accept($this->socket);
                    $this->accept[] = $client;
                    $key = array_keys($this->accept);
                    $key = end($key);
                    $this->isHand[$key] = false;
                } else {
                    $length = socket_recv($sock, $buffer, 204800, 0);
                    $key = array_search($sock, $this->accept);

                    if($length < 7) {
                        $this->close($sock);
                        continue;
                    }

                    if(!$this->isHand[$key]) {
                        $this->dohandshake($sock, $buffer, $key);
                    } else {
                        // 先解码,再编码
                        $data = $this->decode($buffer);
                        $data = $this->encode($data);

                        // 判断断开连接(断开连接时数据长度小于10)
                        if(strlen($data) > 10) {
                            foreach($this->accept as $client) {
                                socket_write($client, $data, strlen($data));
                            }
                        }
                    }
                }
                
            }

        }
        
    }

    /**
     * 首次与客户端握手
     */
    public function dohandshake($sock, $data, $key) {
        if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $data, $match)) {
            $response = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
            $upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
                    "Upgrade: websocket\r\n" .
                    "Connection: Upgrade\r\n" .
                    "Sec-WebSocket-Accept: " . $response . "\r\n\r\n";
            socket_write($sock, $upgrade, strlen($upgrade));
            $this->isHand[$key] = true;
        }
    }

    /**
     * 关闭一个客户端连接
     */
    public function close($sock) {
        $key = array_search($sock, $this->accept);
        socket_close($sock);
        unset($this->accept[$key]);
        unset($this->handshake[$key]);
    }

    /**
     * 解码过程
     */
    public 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;
    }

    /**
     * 编码过程
     */
    public function encode($buffer) {
        $length = strlen($buffer);
        if($length <= 125) {
            return "\x81".chr($length).$buffer;
        } else if($length <= 65535) {
            return "\x81".chr(126).pack("n", $length).$buffer;
        } else {
            return "\x81".char(127).pack("xxxxN", $length).$buffer;
        }
    }


}

$webSocket = new WebSocket('127.0.0.1', 8008, 100);
$webSocket->start();

?>