PHP WebSocket

强调一下:websocket 是一个应用层协议,协议标识符为ws 即websocket的缩写,wss 为 websocket security的缩写

websocket步骤

来自客户端的握手,在服务端看来,就是客户端发过来一段HTTP报头

GET / HTTP/1.1
Host: 101.200.142.148:8880
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1

这时,服务端需要响应如下信息给客户端,这样握手就成功了。

#一个demo

客户端:

<!--
 * @Author: your name
 * @Date: 2020-09-08 16:06:27
 * @LastEditTime: 2020-09-08 16:34:47
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /undefined/Users/mac/Desktop/index.html
-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <p>这是一个websocket的demo</p>
</body>
<script>
    socket = new WebSocket("ws://101.200.142.148:8880");
    console.log(socket);
    //握手成功后执行
    socket.onopen = function(){
        console.log("连接成功");
        //向服务器发送数据
        socket.send("hello server");
    }
    //当服务端有消息发来时执行
    socket.onmessage = function(e){
        console.log(e);
        console.log(e.data);
    }
</script>
</html>

服务端:

<?php
/*
 * @Author: your name
 * @Date: 2020-09-08 16:19:05
 * @LastEditTime: 2020-09-08 16:29:20
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /undefined/Users/mac/Desktop/websocket_demo/server.php
 */
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_set_option($socket,SOL_SOCKET,SO_REUSEADDR,true);
socket_bind($socket,0,8880);
socket_listen($socket);
while(true){
        $connSock = socket_accept($socket);
        $request = socket_read($connSock,1024);
        $response = handShake($request);
        echo $response;
        //响应客户端的握手请求
        socket_write($connSock,$response,strlen($response));
        //接收客户端传来的数据
        $res = socket_read($connSock,1024);
        //解码数据帧
        echo decode($res);
        //向客户端发送数据
        $str = "hello Client";
        //编码数据帧
        $str = frame($str);
        socket_write($connSock,$str,strlen($str));
}


function handShake($request){
        preg_match("/Sec-WebSocket-Key:(.*)\r\n/",$request,$match);
        $key = trim($match[1]);
        var_dump($match);
        $new_key =base64_encode(sha1($key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
        $response = "HTTP/1.1 101 Switching Protocols\r\n";
        $response.= "Upgrade: websocket\r\n";
        $response.= "Connection: Upgrade\r\n";
        $response.= "Sec-WebSocket-Accept: $new_key\r\n";
        $response.= "Sec-WebSocket-Protocol: chat\r\n\r\n";
        return $response;
}

//解析客户端发来的数据帧
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;
}                    
//编码数据帧
function frame($s) {
    $a = str_split($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;
} 

#Web群聊聊天室DEMO

大体思路:

使用IO多路复用 socket_select

如果当前活跃的是 套接字 就建立连接 ,并初始化用户列表

如果当前活跃的是 连接 ,就ws握手,如果已经握手,就解码客户端

传来的数据帧,然后调用send方法,将数据帧推送给在线的每个用户。

Ws.php

<?php
/*
 * @Author: your name
 * @Date: 2020-09-08 16:19:05
 * @LastEditTime: 2020-09-09 10:58:37
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /undefined/Users/mac/Desktop/websocket_demo/server.php
 */

class Ws
{
    public $socket = null;
    public $sockets = [];
    public $write = null;
    public $except = null;
    public $users = [];


    public function __construct($allow_ip, $port)
    {
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, true);
        socket_bind($this->socket, $allow_ip, $port);
        socket_listen($this->socket);
        $this->sockets[] = $this->socket;

        while (true) {
            $tmp_sockets = $this->sockets;
            socket_select($tmp_sockets, $this->write, $this->except, null);
            foreach ($tmp_sockets as $sock) {
                if ($sock == $this->socket) {
                    $connSock = socket_accept($this->socket);
                    $this->sockets[] = $connSock;
                    $this->users[] = [
                        'socket' => $connSock,
                        'hand_shake' => false,
                    ];
                } else {
                    $index = $this->getUserIndex($sock);
                    $request = socket_read($sock, 1024);
                    if(strlen($request) == 8){
                        $this->logout($index);
                    }else{
                        if ($this->users[$index]['hand_shake'] == false) {
                            $response = $this->handShake($request);
                            //响应客户端的握手请求
                            socket_write($sock, $response, strlen($response));
                            $this->users[$index]['hand_shake'] = true;
                        } else {
                            //解码数据帧
                            $msg = $this->decode($request);
                            $this->send($msg, $index);
                        }
                    }
                }
            }
        }
    }
    private function logout($user_index){
        socket_close($this->users[$user_index]['socket']);
        unset($this->users[$user_index]);
        $this->sockets = null;
        $this->sockets[] = $this->socket;
        foreach($this->users as $user){
            $this->sockets[] = $user['socket'];
        }
    }
    public function send($msg, $index)
    {
        $arr = explode('===', $msg);
        switch ($arr[0]) {
            case 'login':
                $this->users[$index]['name'] = $arr[1];
                $res['msg'] = $arr[1] . ':登录成功';
                $res['type'] = 'login';
                //获取所有用户名
                $names['userlist'] = $this->getUserNames();
                $names['type'] = 'user';
                $names = $this->frame(json_encode($names));
                //向每个用户推送
                foreach ($this->users as $user) {
                    socket_write($user['socket'], $names, strlen($names));
                }
                break;
            case  'content':
                $res['content'] = $arr[1];
                $res['name'] = $this->users[$index]['name'];
                $res['time'] = date('Y-m-d H:i:s',time());
                $res['type'] = 'content';
                break;
        }
        $res = $this->frame(json_encode($res));
        //向每个用户推送
        foreach ($this->users as $user) {
            socket_write($user['socket'], $res, strlen($res));
        }
    }
    private function getUserNames()
    {
        foreach ($this->users as $user) {
            $names[] =  $user['name'];
        }
        return $names;
    }

    public function getUserIndex($sock)
    {
        foreach ($this->users as $key => $user) {
            if ($user['socket'] == $sock) {
                return $key;
            }
        }
    }
    public function handShake($request)
    {
        preg_match("/Sec-WebSocket-Key:(.*)\r\n/", $request, $match);
        $key = trim($match[1]);
        var_dump($match);
        $new_key = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        $response = "HTTP/1.1 101 Switching Protocols\r\n";
        $response .= "Upgrade: websocket\r\n";
        $response .= "Connection: Upgrade\r\n";
        $response .= "Sec-WebSocket-Accept: $new_key\r\n";
        $response .= "Sec-WebSocket-Protocol: chat\r\n\r\n";
        return $response;
    }

    //解析客户端发来的数据帧
    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 frame($s)
    {
        $a = str_split($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;
    }
}

server.php

<?php
/*
 * @Author: your name
 * @Date: 2020-09-08 16:59:14
 * @LastEditTime: 2020-09-08 17:00:00
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /websocket_demo/server.php
 */
require_once 'Ws.php';

$allow_ip = 0;
$port = 8880;
$ws = new Ws($allow_ip,$port);

index.html

<!--
 * @Author: your name
 * @Date: 2020-09-08 16:06:27
 * @LastEditTime: 2020-09-09 10:26:36
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /undefined/Users/mac/Desktop/index.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>西电221聊骚室</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
    <style>
        #content {
            height: 100px;
            resize: none;
        }

        #msg-box {
            height: 450px;
        }

        #user-box {
            height: 450px;
        }

        .title {
            padding: 20px;
            font-size: 20px;
        }
    </style>
</head>

<body>
    <div class='container'>
        <div class='text-center title'><strong>西电221聊骚室</strong></div>
        <div class='row'>
            <div class='col-sm-8 '>
                <div class='panel panel-default'>
                    <div class="panel-heading">消息框</div>
                    <div class='panel-body' id='msg-box'></div>
                </div>
            </div>
            <div class='col-sm-4'>
                <div class='panel panel-default'>
                    <div class='panel-heading'>用户列表</div>
                    <div class='panel-body' id='user-box'></div>
                </div>
            </div>

        </div>
        <div class='form-group'>
            <textarea class='form-control' name="" id="content" cols="30" rows="5"></textarea>
        </div>
        <button id='send' class='btn btn-primary'>发送消息</button>
    </div>

</body>
<script>
    var name = prompt("欢迎进入西电暗网,请输入用户名:")
    socket = new WebSocket("ws://101.200.142.148:8880");
    console.log(socket);
    //握手成功后执行
    socket.onopen = function () {
        console.log("连接成功");
        //向服务器发送数据
        socket.send("login===" + name);
    }
    //当服务端有消息发来时执行
    socket.onmessage = function (e) {
        var data = JSON.parse(e.data);
        console.log(e);
        console.log(e.data);
        let msg_box = document.getElementById('msg-box');
        switch (data.type) {
            case "login":
                msg_box.innerHTML += '<div>' + (new Date()).getTime() + ' ' + data.msg + '</div>';
                break;
            case "user":
                let user_box = document.getElementById('user-box');
                let str = '';
                for (let i = 0; i < data.userlist.length; i++) {
                    str += '<div>' + data.userlist[i] + '</div>';
                }
                user_box.innerHTML = str;
                break;
            case "content":
                msg_box.innerHTML += '<div>' + data.time + ' ' + data.name + ':' + data.content + '</div>';
        }
    }
    let btn = document.getElementById('send');
    function send(){
        let content = document.getElementById('content');
        let data = content.value;
        if(data=='') return;
        socket.send("content===" + data);
        content.value='';
    }
    btn.onclick = ()=>{
        send();
    };
    document.onkeydown = (e) => {
        switch (e.keyCode) {
            case 13:
                send();
                break;
        }
    }

</script>

</html>

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值