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
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;
}
客户端发送消息处理

网上拷过来的代码

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);
}
前端连接示例

从菜鸟教程拷过来的

<!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()">运行 WebSocket</a>
</div>

</body>
</html>

完整示例

# 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);


  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值