前言
首先要知道websocket的概念,我只说下我的简单理解
- 基于http协议,在http基础上升级为ws协议,所以会携带一个Upgrade,websocket-key用于握手过程中安全验证,握手完成后就建立起了长久连接。
- 因为连接断了不会重连,而且不止一个客户端,所以也有心跳机制和消息群发
直接上代码
- 这里不知为什么onConnect用不了
后台
composer require workerman/workerman
安装完后,把vendor丢到linux服务器即可使用- 似乎只能用命令行运行,所以还用
shell_exec
去执行命了了,此处不赘述 - 因为需求比较特殊仅做参考,只是用于持续发送消息
更新一下
php,狗看了都摇头
<?php
use Workerman\Worker;
use Workerman\Lib\Timer;
require_once __DIR__ . '/vendor/autoload.php';
function sendSocket($socket)
{
// global $socket;
try {
// 创建套接字
// $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if ($socket) {
// (不知道有没有用,因为不知道这里是客户端还是服务端) ['sec' => 1, 'usec' => 0]是一个关联数组,用于设置超时时间的值。其中,sec表示秒,usec表示微秒
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 1, 'usec' => 0]);
// 可以立即重连,不然断开再连接会报错
// socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
// if (socket_bind($socket, '0.0.0.0', 7111)) {
// 连接服务端
if (socket_connect($socket, '0.0.0.0', 7110)) {
while ($buf = socket_read($socket, 2048)) {
// 当套接字中没有更多数据可读取时,socket_read() 返回一个长度为 0 的空字符串("")
if ($buf != '') {
return $buf;
}
}
} else {
echo 'false3';
}
// } else {
// echo "socket_bind() ";
// }
// echo 'false2';
} else {
echo 'false';
}
} catch (Exception $e) {
echo 'no' . $e->getMessage() . '';
} finally {
// socket_close($socket);
// return 'false';
}
}
// 注意有时没有清理干净进程,需要手动处理
global $connectAr; //连接池
global $timer_id; //连接池
$connectArr = [];
$ws = new Worker('websocket://0.0.0.0:9501');
$ws->count = 2; //提供2个进程
$ws->onWorkerStart = function () {
global $timer_id;
global $socket;
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
// print_r($socket);
// bind只能用一次
socket_bind($socket, '0.0.0.0', 7111);
$timer_id = Timer::add(1, function () {
global $socket;
global $connectArr; // 声明为全局变量
$res = sendSocket($socket);
foreach ($connectArr as $key => $connection) {
$hex = bin2hex($res);//这里不转换会无法被游览器解析报错
$response = json_encode([
"data" => $hex,
"receipt_time" => date("Y-m-d H:i:s"),
]);
$connection->send($response);
// 将连接对象转换为数组
$connections = [];
foreach ($connectArr as $conn) {
$connections[] = [
'id' => $conn->id,
'remoteAddress' => $conn->getRemoteIp(),
'remotePort' => $conn->getRemotePort(),
];
}
$connection->send(json_encode($connections));
}
});
};
$ws->onMessage = function ($connection) {
global $connectArr; // 声明为全局变量
// global $timer_id; // 声明为全局变量
// 新的连接
if (!in_array($connection, $connectArr)) {
$connectArr[] = $connection;
$connection->send('hello'); // 向服务端发送欢迎消息
}
};
$ws->onClose = function ($connection) {
$connection->send('bye');
global $connectArr; // 声明为全局变量
global $timer_id;
// 从连接池清除
$index = array_search($connection, $connectArr);
if ($index !== false) {
unset($connectArr[$index]);
}
// 连接池空了就关闭
if (empty($connectArr)) {
Timer::del($timer_id);
$timer_id = null;
Worker::stopAll();
$command = 'php websocket.php stop';
shell_exec($command);
};
};
Worker::runAll();
旧版本
<?php
use Workerman\Worker;
use Workerman\Lib\Timer;
require_once __DIR__ . '/vendor/autoload.php';
function sendSocket($connection)
{
// 创建套接字
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
// while (true) {
if ($socket) {
if (socket_bind($socket, '0.0.0.0', 7111)) { //必须要有
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 1, 'usec' => 0]);
// 连接客户端
if (socket_connect($socket, '0.0.0.0', 7110)) {
// 读取客户端发送的消息
$buf = @socket_read($socket, 2048);
// 当套接字中没有更多数据可读取时,socket_read() 返回一个长度为 0 的空字符串("")
if ($buf != "") {
$hex = bin2hex($buf);
$response = json_encode([
"data" => $hex,
"receipt_time" => date("Y-m-d H:i:s"),
]);
$connection->send($response);
// sendSocket($connection);
}
}
}
}
socket_close($socket);
}
// 注意有时没有清理干净进程,需要手动处理
$connectArr = []; //连接池
$ws = new Worker('websocket://0.0.0.0:9501');
$ws->count = 1; //提供2个进程
$ws->onMessage = function ($connection) use ($connectArr) {
// 新的连接
if (!in_array($connection, $connectArr)) {
$connectArr[] = $connection;
$connection->send('{"hello":Welcome to the server!}'); // 向客户端发送欢迎消息
// 每个连接都持续发送消息
// 添加定时器,每隔 1 秒触发一次 sendSocket 函数
sendSocket($connection);
$timer_id = Timer::add(1, function () use ($connection) {
sendSocket($connection);
});
// 将定时器 ID 存储到连接对象中
$connection->timer_id = $timer_id;
}
// $connection->send('hello' . $data);
// sendSocket($connectArr);
};
$ws->onClose = function ($connection) use ($connectArr) {
// 从连接池清除
$index = array_search($connection, $connectArr);
if ($index !== false) {
unset($connectArr[$index]);
}
// 连接池空了就关闭
if (empty($connectArr)) {
$connection->send('bye');
Worker::stopAll();
$command = 'php websocket.php stop';
shell_exec($command);
};
// 取消定时器
Timer::del($connection->timer_id);
};
Worker::runAll();
前台
// 创建一个Socket实例
this.websocket = new WebSocket(
"ws://" + window.location.hostname + ":9501"
); //居然不能写127.0.0.1
console.log("连接", this.websocket.readyState, this.websocket);
// 监听状态改变事件
this.websocket.onopen = () => {
// 0 CONNECTING 连接尚未建立
// 1 OPEN 已连接
// 2 CLOSING 正在关闭
// 3 CLOSED 已关闭
console.log("连接", this.websocket.readyState);
this.websocket.send('')
};
// 监听消息事件(服务器推送事件)
this.websocket.onmessage = (event) => {
console.log("接收", event, event.data);
};
// 监听连接关闭事件
this.websocket.onclose = () => {
console.log("this.websocket关闭", this.websocket.readyState);
};