• php 实现websocket服务


    前言

    之前使用的都是封装好的websocket,现在使用php提供的相关函数实现一个websocket服务

    流程
    1. 通过 socket_create 创建服务端
    2. socket_bind 绑定服务端地址
    3. socket_listen监听服务端
    4. socket_select 剔除不是正在接受消息的客户端或者不是正常状态的服务端
    5. 判断是不是正在和服务端进行握手,是的将客户端维护
    6. 遍历正在接受消息的客户端,对其接受到的内容进行处理(是断开,还是单纯的传输信息)
    握手 socket_read 读到的报文内容
    GET / HTTP/1.1
    Host: localhost:8084
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:104.0) Gecko/20100101 Firefox/104.0
    Accept: */*
    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, br
    Sec-WebSocket-Version: 13
    Origin: http://localhost:8081
    Sec-WebSocket-Extensions: permessage-deflate
    Sec-WebSocket-Key: hJno9ufjz22HoskdJC9wMw==
    Connection: keep-alive, Upgrade
    Sec-Fetch-Dest: websocket
    Sec-Fetch-Mode: websocket
    Sec-Fetch-Site: same-site
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade: websocket
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    socket_select

    socket_select( r e a d ) 能将当前没有正在接受消息的客户端或者不是正常状态的服务端从 read) 能将当前没有正在接受消息的客户端或者不是正常状态的服务端从 read)能将当前没有正在接受消息的客户端或者不是正常状态的服务端从read中剔除

    解析客户端发送的数据

    网上拷过来的代码

    function parseMessage($data)
    {
        $res = "";
        $len = ord($data[1]) & 127;
        if ($len === 126) {
            $make = substr($data, 4, 4);
            $result = substr($data, 8);
        } elseif ($len === 127) {
            $make = substr($data, 10, 4);
            $result = substr($data, 14);
        } else {
            $make = substr($data, 2, 4);
            $result = substr($data, 6);
        }
    
        for ($i = 0; $i < strlen($result); $i++) {
            $res .= $result[$i] ^ $make[$i % 4];
        }
    
        return $res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    客户端发送消息处理

    网上拷过来的代码

    function createMessage($data)
    {
        $result = [0 => '81'];
        $len = strlen($data);
        if ($len < 126) {
            $result[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        } elseif ($len < 65025) {
            $str = dechex($len);
            $result[1] = '7e' . str_repeat('0', 4 - strlen($str)) . $str;
        } else {
            $str = dechex($len);
            $result[1] = '7f' . str_repeat('0', 16 - strlen($str)) . $str;
        }
    
        $result2 = "";
        for ($i = 0; $i < $len; $i++) {
            $result2 .= dechex(ord($data[$i]));
        }
        $result[2] = $result2;
        $response = implode('', $result);
        return pack("H*", $response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    前端连接示例

    菜鸟教程拷过来的

    DOCTYPE HTML>
    <html>
    <head>
        <meta charset="utf-8">
        <title>菜鸟教程(runoob.com)title>
    
        <script type="text/javascript">
            function WebSocketTest() {
                if ("WebSocket" in window) {
                    alert("您的浏览器支持 WebSocket!");
    
                    let socketUrl = "ws://localhost:8084";
                    // 打开一个 web socket
                    var ws = new WebSocket(socketUrl);
    
                    ws.onopen = function () {
                        // Web Socket 已连接上,使用 send() 方法发送数据
                        ws.send("发送数据");
                        alert("数据发送中...");
                    };
    
                    ws.onmessage = function (evt) {
                        var received_msg = evt.data;
                        alert("数据已接收...");
                    };
    
                    ws.onclose = function () {
                        // 关闭 websocket
                        alert("连接已关闭...");
                    };
                } else {
                    // 浏览器不支持 WebSocket
                    alert("您的浏览器不支持 WebSocket!");
                }
            }
        script>
    
    head>
    <body>
    
    <div id="sse">
        <a href="javascript:WebSocketTest()">运行 WebSocketa>
    div>
    
    body>
    html>
    
    
    • 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
    完整示例
    
    # https://www.php.net/manual/zh/function.socket-create.php
    # 基于tcp协议创建socket
    # AF_INET ipv4网络协议
    # SOCK_STREAM 可靠字节流
    # SOL_TCP tcp传输控制协议
    # 创建套接字
    $service = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    
    # 绑定地址
    socket_bind($service, "127.0.0.1", "8084") or die("不能绑定socket地址");
    
    # 监听
    socket_listen($service, 6);
    
    $services = [$service];
    
    while (true) {
    
        $copyServices = $services;
    
        if (socket_select($copyServices, $write, $except, 0) === false)
            exit("error");
    
        # 判断服务端是否还在
        # 如果服务端不是正常的状态,也会被socket_select剔除
        if (in_array($service, $copyServices)) {
            $client = socket_accept($service);
            $body = socket_read($client, 8096);
    
            # 判断是否是第一次建立连接
            # 是的话将客户端保存
            if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i", $body, $match)) {
                # 按照固定算法和字符串对key进行加密
                $key = base64_encode(sha1($match[1] . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
    
                # 拼接响应 head 头
                $header = "HTTP/1.1 101 Switching Protocol" . PHP_EOL
                    . "Upgrade: WebSocket" . PHP_EOL
                    . "Connection: Upgrade" . PHP_EOL;
                $header .= "WebSocket-Location: ws://127.0.0.1:8084" . PHP_EOL;
                $header .= "Sec-WebSocket-Accept: " . $key . PHP_EOL . PHP_EOL;
    
                socket_write($client, $header);
                socket_write($client, createMessage("hello websocket"));
    
                echo "建立连接" . PHP_EOL;
    
                $services[] = $client;
            }
    
            # 剔除服务端
            $key = array_search($service, $copyServices);
            unset($copyServices[$key]);
        }
    
        if ($copyServices) {
            echo "服务端数量" . count($services);
        }
    
        # $copyServices剔除了服务端
        # 还被socket_select剔除了没在接受消息的客户端
        foreach ($copyServices as $c) {
            $buf = socket_read($c, 8096);
            # 消息可能是断开连接的信息,需要关闭并剔除客户端
            if (strlen($buf) < 9) {
                $key = array_search($c, $services);
                unset($services[$key]);
                socket_close($c);
                continue;
            }
    
            #有消息的就输出消息
            echo parseMessage($buf);
            echo PHP_EOL;
        }
    }
    
    //网上拷的服务端向客户端发送信息处理
    function createMessage($data)
    {
        $result = [0 => '81'];
        $len = strlen($data);
        if ($len < 126) {
            $result[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        } elseif ($len < 65025) {
            $str = dechex($len);
            $result[1] = '7e' . str_repeat('0', 4 - strlen($str)) . $str;
        } else {
            $str = dechex($len);
            $result[1] = '7f' . str_repeat('0', 16 - strlen($str)) . $str;
        }
    
        $result2 = "";
        for ($i = 0; $i < $len; $i++) {
            $result2 .= dechex(ord($data[$i]));
        }
        $result[2] = $result2;
        $response = implode('', $result);
        return pack("H*", $response);
    }
    
    //网上拷的解析客户端发送给服务端的消息
    function parseMessage($data)
    {
        $res = "";
        $len = ord($data[1]) & 127;
        if ($len === 126) {
            $make = substr($data, 4, 4);
            $result = substr($data, 8);
        } elseif ($len === 127) {
            $make = substr($data, 10, 4);
            $result = substr($data, 14);
        } else {
            $make = substr($data, 2, 4);
            $result = substr($data, 6);
        }
    
        for ($i = 0; $i < strlen($result); $i++) {
            $res .= $result[$i] ^ $make[$i % 4];
        }
    
        return $res;
    }
    
    socket_close($service);
    
    
    
    • 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
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
  • 相关阅读:
    你可以在 2024 年构建的 7 个 Micro SaaS 创意
    聊城办理ISO认证企业须知
    服务端技术方案应该具有哪些章节
    【贪心算法题记录】1005. K 次取反后最大化的数组和
    Mybatis---CRUD案例
    目标检测论文解读复现之三:基于改进YOLOv7的X光图像旋转目标检测
    win11 笔记本在移动硬盘中安装linux(ubuntu22),安装NVIDIA驱动,安装anaconda,进行深度学习
    Android 屏幕适配
    KS数据采集代码示例
    (学习日记)2022.8.5
  • 原文地址:https://blog.csdn.net/qq_29744347/article/details/126919873