PHP websocket 應用

無知的小王發表於2021-11-20

因為公司有這個需求,記錄一下

建立兩個陣列來記錄連線的客戶端以及握手狀態

    private $master = null; //服務端
    private $connectPool = []; //客戶端連線池
    private $handShakePool = []; //握手連線池

執行服務的方法

public function startServer($ip, $port) {
        $this->connectPool[] = $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);//建立服務端
        socket_bind($this->master, $ip, $port);//繫結服務端ip,埠
        socket_listen($this->master, 1000);//監聽,最多同時監聽1000個客戶端
        while(true) {
            $sockets = $this->connectPool;
            $write = $excpet = null;
            socket_select($sockets, $write, $excpet, 60);//阻塞模式

            foreach($sockets as $socket) {//監聽訊息
                if($socket == $this->master) {//新增新客戶端
                    $this->connectPool[] = $client = socket_accept($this->master);
                    $keyArr = array_keys($this->connectPool, $client);
                    $key = end($keyArr);
                    $this->handShakePool[$key] = false;
                } else {
                    $length = socket_recv($socket, $buffer, 1024, 0);
                    if($length<1) {//當長度小於7,說明當前客戶端連線已斷開
                        $this->close($socket);//服務端關閉斷開連線的客戶端
                    } else {
                        $key = array_search($this->master, $this->connectPool);
                        if($this->handShakePool[$key] == false) {//進行握手
                            $this->handShake($socket, $buffer, $key);
                        } else {//傳送訊息,可根據具體業務進行調整
                            $msg = $this->deFrame($buffer);
                            $msg = $this->enFrame($msg);
                            $this->send($socket, $msg);
                        }
                    }
                }
            }
        }
    }

以下是某些必需的操作

關閉連線

// 客戶端斷開連線
    public function close($socket) {
        $key = array_search($socket, $this->connectPool);
        unset($this->connectPool[$key]);
        unset($this->handShakePool[$key]);
        socket_close($socket);
        echo("有客戶端斷開連線\n");
    }

握手操作

//握手
    public function handShake($socket ,$buffer, $key) {
        if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $buffer, $match)) {
            $responseKey = 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: ".$responseKey."\r\n\r\n";
            socket_write($socket, $upgrade, strlen($upgrade));
            $this->handShakePool[$key] = true;
        }
        echo("新建握手,當前客戶端數量=>".(sizeof($this->handShakePool)-1)."\n");
    }

資料的加密與解密,以及傳送資訊

//資料封幀
    public function enFrame($msg) {
        $len = strlen($msg);
        if($len<=125) {
            return "\x81".chr($len).$msg;
        } else if($len <= 65535) {
            return "\x81".chr(126).pack("n",$len).$msg;
        } else {
            return "\x81".chr(127).pack("xxxxN",$len).$msg;
        }
    }

    //資料解幀
    public function deFrame($buffer) {
        $len = $masks = $data = $decode = 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++) {
            $decode .= $data[$index]^$masks[$index*4];
        }
        return $decode;
    }

    //傳送資訊
    public function send($socket, $msg) {
        socket_write($socket, $msg);
        echo("給{$socket}傳送{$msg}\n");
    }

寫完以上程式碼,執行 startServer 方法即可

本作品採用《CC 協議》,轉載必須註明作者和本文連結
如有錯誤,歡迎大佬指點

相關文章