文章目录
- 前言
- 流程
- 握手 socket_read 读到的报文内容
- socket_select
- 解析客户端发送的数据
- 客户端发送消息处理
- 前端连接示例
- 完整示例
前言
之前使用的都是封装好的websocket,现在使用php提供的相关函数实现一个websocket服务
流程
- 通过 socket_create 创建服务端
- socket_bind 绑定服务端地址
- socket_listen监听服务端
- socket_select 剔除不是正在接受消息的客户端或者不是正常状态的服务端
- 判断是不是正在和服务端进行握手,是的将客户端维护
- 遍历正在接受消息的客户端,对其接受到的内容进行处理(是断开,还是单纯的传输信息)
握手 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)</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/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);
更多推荐
php 实现websocket服务
发布评论