PHP实现WebSocket实例详解
作者:Azure沫 发布时间:2023-06-11 23:08:01
WebSocket 是什么?
摘抄网上的一些解释:
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 时向服务器端请求的信息
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 进行加密等操作
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 服务端
<?php
if(($socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) {
echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
}
if(($ret = socket_bind($socket,'127.0.0.1','9090')) < 0) {
echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
}
if(($ret = socket_listen($socket,3)) < 0) {
echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
}
$all_sockets = [$socket]; // socket 集合
do {
$copy_sockets = $all_sockets; // 单独拷贝一份
// 因为客户端是长连接,如果客户端非正常断开,服务端会在 socket_accept 阻塞,现在使用 select 非阻塞模式 socket
if(socket_select($copy_sockets, $write, $except, 0) === false)
exit('sosket_select error!');
// 接收第一次 socket 连入,连入后移除服务端 socket
if(in_array($socket, $copy_sockets)) {
$client = socket_accept($socket);
if($client) {
$buf = socket_read($client, 1024);
echo $buf;
// 匹配 Sec-Websocket-Key 标识
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$match)) {
// 需要将 Sec-WebSocket-Key 值累加字符串,并依次进行 SHA-1 加密和 base64 加密
$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; // 注意这里,需要两个换行
// 向客户端应答 Sec-WebSocket-Accept
socket_write($client, $res, strlen($res));
// 向客户端发送消息
socket_write($client, buildMsg('socket ok'), 1024);
// 加入客户端 socket
$all_sockets[] = $client;
}
// 移除服务端 socket
$key = array_search($socket, $copy_sockets);
unset($copy_sockets[$key]);
// socket_close($client);
}
}
// 循环所有客户端 sockets
foreach ($copy_sockets as $s) {
// 获取客户端发给服务端的内容
$buf = socket_read($s, 8024);
echo strlen($buf).'---'.PHP_EOL;
// 代表客户端主动关闭
if(strlen($buf) < 9) {
$key = array_search($s, $all_sockets);
unset($all_sockets[$key]);
socket_close($s);
continue;
}
// 输出
echo getMsg($buf).PHP_EOL;
}
}while(true);
socket_close($socket);
// 编码服务端向客户端发送的内容
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);
}
// 解析客户端向服务端发送的内容
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;
}
客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
// 创建一个Socket实例
var socket = new WebSocket('ws://localhost: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("client msg");
}
</script>
</head>
<body>
<button onclick="send()">发送消息</button>
</body>
</html>
运行测试:
Client
Server
来源:https://www.cnblogs.com/tangxuliang/p/9713614.html


猜你喜欢
- 游戏玩法游戏玩法: 该游戏由 2 到 6 个人玩,使用除大小王之外的 52 张牌,游戏者的目标是使手中的牌的点数之和不超过 21 点且尽量大
- 0. 本文借助django-debug-toolbar来展现效果django-debug-toolbar的安装1. 介绍select_rel
- 在利用opencv进行图片处理时,经常需要查看图片关心区域或位置的像素数值,苦于没有应手的小软件,我用python3.6+opencv3.4
- 线程锁相当于同时只能有一个线程申请锁,有的场景无数据修改互斥要求可以同时让多个线程同时运行,且需要限制并发线程数量时可以使用信号量impor
- 项目信号处理和提取部分用到了matlab,需要应用到工程中方便研究。用具有万能粘合剂之称的“Python”。具体方法如下:1.python中
- 本文实例为大家分享了vue+echarts封装气泡图的具体代码,供大家参考,具体内容如下前端可视化封装气泡图1. html<templ
- 设计是简单的如果你知道要放的东西该放到哪。曾经在某个电子杂志里看到一篇关于如何在平面设计中偷懒的文章,引发了我的一些思考,在平面设计中有这么
- 假如你用SQL2005做一个数据库备份,然后把这个备份到装有SQL2000的服务器去恢复,是恢复不了,同样,你把SQL2005数据库附加到S
- 闭包的定义非常晦涩——闭包,是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落。
- 我们通常可以使用os模块的命令进行执行cmd方法一:os.systemos.system(执行的命令)# 源码def system(*arg
- map是key-value数据结构,又称为字段或者关联数组。类似其他编程语言的集合一、基本语法var 变量名 map[keyty
- Python 想必大家都已经很熟悉了,甚至关于它有用或者无用的论点大家可能也已经看腻了。但是无论如何,它作为一个广受关注的语言还是有它独到之
- 你同样可以使用cache标签来缓存模板片段。 在模板的顶端附近加入{% load cache %}以通知模板存取缓存标签。模板标签{% ca
- Python的五个标准数据类型数字字符串列表元组字典一、数字不可变数据类型,存储值为数值1.创建对象,分配数值例:>>>
- 函数的作用域python中的作用域分4种情况:L:local,局部作用域,即函数中定义的变量;E:enclosing,嵌套的父级函数的局部作
- // 自动转换字符集 支持数组转换 function auto_charset($fContents, $from='gbk'
- 思路:使用socket传输文件过程中,如果单次传输每次只能发送一部分数据,如果针对大文件,一次传输肯定是不行的,所以需要我们在传输的时候提前
- 要将深度学习更快且更便捷地应用于新的问题中,选择一款深度学习工具是必不可少的步骤。 TensorFlow是谷歌于2015年11月9日正式开源
- //重新封装document对象 var Console={ Write:function(msg){alert(msg);} }; //P
- js中变量的特征js的变量是松散类型的。变量可以用于保存任何类型的数据。所以js也被称为弱类型语言。变量的定义与访问简单说下作用域什么是作用