• PHP实现WebSocket示例


    WebSocket 是什么?

    摘抄网上的一些解释:

    WebSocket 协议是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

    WebSocket 通信协议于2011年被 IETF 定为标准 RFC 6455,并被 RFC7936 所补充规范。

    —— 百度百科

    WebSocket 是一个持久化的协议,这是相对于 http 非持久化来说的。

    举个简单的例子,http1.0 的生命周期是以 request 作为界定的,也就是一个 request,一个 response,对于 http 来说,本次 client 与 server 的会话到此结束;而在 http1.1 中,稍微有所改进,即添加了 keep-alive,也就是在一个 http 连接中可以进行多个 request 请求和多个 response 接受操作。然而在实时通信中,并没有多大的作用,http 只能由 client 发起请求,server 才能返回信息,即 server 不能主动向 client 推送信息,无法满足实时通信的要求。而 WebSocket 可以进行持久化连接,即 client 只需进行一次握手,成功后即可持续进行数据通信,值得关注的是 WebSocket 实现 client 与 server 之间全双工通信,即 server 端有数据更新时可以主动推送给 client 端。

    上图是一个演示client和server之间建立WebSocket连接时握手部分

    client 建立 WebSocket 时向服务器端请求的信息

      GET /chat HTTP/1.1 
      Host: server.example.com 
      Upgrade: websocket //告诉服务器现在发送的是WebSocket协议
      Connection: Upgrade 
      Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一个Base64 encode的值,这个是浏览器随机生成的,用于验证服务器端返回数据是否是WebSocket助理
      Sec-WebSocket-Protocol: chat, superchat 
      Sec-WebSocket-Version: 13 
      Origin: http://example.com

    服务器获取到 client 请求的信息后,根据 WebSocket 协议对数据进行处理并返回,其中要对 Sec-WebSocket-Key 进行加密等操作

       HTTP/1.1 101 Switching Protocols 
      Upgrade: websocket //依然是固定的,告诉客户端即将升级的是Websocket协议,而不是mozillasocket,lurnarsocket或者shitsocket
      Connection: Upgrade 
      Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key,也就是client要求建立WebSocket验证的凭证
      Sec-WebSocket-Protocol: chat

    PHP 服务端

    <?php
    
    if(($socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) {
        echo "socket_create() 失败的原因是:".socket_strerror($sock)."
    ";
    }
    if(($ret = socket_bind($socket,'127.0.0.1','9090')) < 0) {
        echo "socket_bind() 失败的原因是:".socket_strerror($ret)."
    ";
    }
    if(($ret = socket_listen($socket,3)) < 0) {
        echo "socket_listen() 失败的原因是:".socket_strerror($ret)."
    ";
    }
    
    $all_sockets = [$socket];    // socket 集合
    
    do {
        $copy_sockets = $all_sockets;   // 单独拷贝一份
    
        // 因为客户端是长连接,如果客户端非正常断开,服务端会在 socket_accept 阻塞,现在使用 select 非阻塞模式 socket
        if(socket_select($copy_sockets, $write, $except, 0) === false)
            exit('sosket_select error!');
    
        // 接收第一次 socket 连入,连入后移除服务端 socket
        if(in_array($socket, $copy_sockets)) {
            $client = socket_accept($socket);
            if($client) {
                $buf = socket_read($client, 1024);
                echo $buf;
    
                // 匹配 Sec-Websocket-Key 标识
                if (preg_match("/Sec-WebSocket-Key: (.*)
    /i",$buf,$match)) {
                    // 需要将 Sec-WebSocket-Key 值累加字符串,并依次进行 SHA-1 加密和 base64 加密
                    $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
                    // 拼凑响应内容
                    $res= "HTTP/1.1 101 Switching Protocol".PHP_EOL
                        ."Upgrade: WebSocket".PHP_EOL
                        ."Connection: Upgrade".PHP_EOL
                        ."WebSocket-Location: ws://127.0.0.1:9090".PHP_EOL
                        ."Sec-WebSocket-Accept: " . $key .PHP_EOL.PHP_EOL;  // 注意这里,需要两个换行
                    // 向客户端应答 Sec-WebSocket-Accept
                    socket_write($client, $res, strlen($res));
                    // 向客户端发送消息
                    socket_write($client, buildMsg('socket ok'), 1024);
    
                    // 加入客户端 socket
                    $all_sockets[] = $client;
                }
                // 移除服务端 socket
                $key = array_search($socket, $copy_sockets);
                unset($copy_sockets[$key]);
    
                // socket_close($client);
            }
        }
    
        // 循环所有客户端 sockets
        foreach ($copy_sockets as $s) {
            // 获取客户端发给服务端的内容
            $buf = socket_read($s, 8024);
            echo strlen($buf).'---'.PHP_EOL;
            // 代表客户端主动关闭
            if(strlen($buf) < 9) {
                $key = array_search($s, $all_sockets);
                unset($all_sockets[$key]);
                socket_close($s);
                continue;
            }
            // 输出
            echo getMsg($buf).PHP_EOL;
        }
    
    }while(true);
    socket_close($socket);
    
    // 编码服务端向客户端发送的内容
    function buildMsg($msg) {
        $frame = [];
        $frame[0] = '81';
        $len = strlen($msg);
        if ($len < 126) {
            $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        } else if ($len < 65025) {
            $s = dechex($len);
            $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
        } else {
            $s = dechex($len);
            $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
        }
        $data = '';
        $l = strlen($msg);
        for ($i = 0; $i < $l; $i++) {
            $data .= dechex(ord($msg{$i}));
        }
        $frame[2] = $data;
        $data = implode('', $frame);
        return pack("H*", $data);
    }
    
    // 解析客户端向服务端发送的内容
    function getMsg($buffer) {
        $res = '';
        $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++) {
            $res .= $data[$index] ^ $masks[$index % 4];
        }
        return $res;
    }

    客户端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script>
            // 创建一个Socket实例
            var socket = new WebSocket('ws://localhost:9090');
    
            // 打开Socket
            socket.onopen = function(event) {
                // 发送一个初始化消息
                socket.send("init msg");
    
            };
            socket.onmessage = function(event) {
                console.log('收到消息',event);
    
            };
    
            // 监听Socket的关闭
            socket.onclose = function(event) {
                console.log('关闭监听',event);
            };
    
            function  send()
            {
                socket.send("client msg");
            }
        </script>
    </head>
    <body>
    <button onclick="send()">发送消息</button>
    </body>
    </html>

    运行测试:

    Client

    Server

  • 相关阅读:
    终于看到费德勒在法网如愿!
    o(∩_∩)o...,今天去博客园了!
    条款4:使用Conditional特性代替#if条件编译
    MSDTC无法启动的解决方法
    2009 很有意义的一天
    从现在开始,争取记录每天所学到的、所感受到的、所遇见到的点点滴滴!
    了解MOSS2007 内容类型ID(Content Type IDs)命名规则
    CreateSpecificCulture('zhcn')和new CultureInfo('zhcn')的区别
    金华大显数码科技有限公司诚聘
    使用SQL Server中按位于来表示组合状态
  • 原文地址:https://www.cnblogs.com/tangxuliang/p/9713614.html
Copyright © 2020-2023  润新知