美文网首页
第二课时:websocket

第二课时:websocket

作者: liamu | 来源:发表于2018-06-22 10:45 被阅读13次

通过原生php实现websocket

  • 新建静态页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        // 创建一个Socket实例
        var socket = new WebSocket('ws://172.16.130.74: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("test");
    }
    </script>
</head>
<body>
  <button onclick="send()">发送消息</button>
</body>
</html>
  • php的实现
<?php
$server=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);

socket_bind($server,'0.0.0.0',9090) or die("error");

socket_listen($server,5);

$allsockets=[$server];
while(true)
{
    $copySockets=$allsockets;
    if(socket_select($copySockets,$write ,$except,0)===false) exit("error");

    if(in_array($server,$copySockets)){
        $client=socket_accept($server);
        $buf=socket_read($client,8024);
        print_r($buf).PHP_EOL;// 客户端请求头
        if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$match)) {
            $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  ;//注意这里,需要两个换行

            socket_write($client, $res, strlen($res));//握手成功
            socket_write($client,buildMsg("hello websocket"));
            $allsockets[]=$client;//把 websocket客户端的socket保存起来
        }
        $key = array_search($server, $copySockets);
        unset($copySockets[$key]);
    }

    foreach($copySockets as $s)
    {
        $buf=socket_read($s,8024);
        if(strlen($buf)<9) //代表客户端主动关闭
        {
            $key = array_search($s, $allsockets);
            unset($allsockets[$key]);//在数组中删除 该socket
            socket_close($s);//服务端一定要主动关掉
            continue;
        }
        echo getMsg($buf);//获取客户端发送消息,并转码
        echo PHP_EOL;
    }
    //socket_close($client);//关掉 客户端socket
}
socket_close($server);//关机
//解析客户端发送给服务端的消息
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;
}

//编码发送给客户端的数据
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);
}

  • 请求头
GET / HTTP/1.1
Host: 172.16.130.74:9090
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: file://
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.90 Safari/537.36 2345Explorer/9.3.2.17331
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Sec-WebSocket-Key: UuvGbo3XuryZ4r167PmL3w==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

必须是有效的http request 格式;
HTTP request method 必须是GET,协议应不小于1.1 如: Get / HTTP/1.1;
必须包括Upgrade头域,并且其值为”websocket”;
必须包括”Connection” 头域,并且其值为”Upgrade”;
必须包括”Sec-WebSocket-Key”头域,其值采用base64编码的随机16字节长的字符序列;
如果请求来自浏览器客户端,还必须包括Origin头域 。 该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接;
必须包括”Sec-webSocket-Version” 头域,当前值必须是13;
可能包括”Sec-WebSocket-Protocol”,表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之;
可能包括”Sec-WebSocket-Extensions”, 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强;
可能包括任意其他域,如cookie.

  • 响应头
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: Y+Te7S7wQJC0FwXumEdGbv9/Mek=

必须包括Upgrade头域,并且其值为”websocket”;
必须包括Connection头域,并且其值为”Upgrade”;
必须包括Sec-WebSocket-Accept头域,其值是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5- E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码,就是 “Sec-WebSocket-Accept”的值;
应答包中冒号后面有一个空格;
最后需要两个空行作为应答包结束。
请注意:258EAFA5- E914-47DA-95CA-C5AB0DC85B11 这一串加密的字串是固定的,不可更改,否则会握手失败。客户端的请求头实际上是一个http请求,我们只要从头部匹配出 Sec-WebSocket-Key 并且按照固定加密返回即可。

三次握手示意图 抓包分析
  • 端口占用查看命令
    • lsof -i:port
    • netstat -anpt | grep port

相关文章

网友评论

      本文标题:第二课时:websocket

      本文链接:https://www.haomeiwen.com/subject/iizhyftx.html