PHP实现WebSocket实例详解

2023-02-20 15:26:59 浏览数 (2)

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 时向服务器端请求的信息

1 2 3 4 5 6 7 8

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 进行加密等操作

1 2 3 4 5

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 服务端

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

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

客户端

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

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

运行测试:

Client

Server

到此这篇关于PHP实现WebSocket实例详解的文章就介绍到这了

未经允许不得转载:肥猫博客 » PHP实现WebSocket实例详解

0 人点赞