Workerman 是一款纯 PHP 开发的开源高性能 PHP 应用容器,它突破了传统 PHP 应用的限制,能够开发高性能的实时网络应用。它不是传统的 MVC 框架,而是一个底层通用的服务框架,支持 TCP、UDP、HTTP、WebSocket 等多种协议,适用于开发即时通讯、物联网、游戏服务器、高性能 HTTP 服务等多种应用。
Workerman 的设计理念是极简、稳定和高性能,适合希望突破传统 PHP 开发限制的开发者。
composer require workerman/workerman
start.php
文件
<?php
useWorkerman\Worker;
useWorkerman\Connection\TcpConnection;
useWorkerman\Timer;
require_once__DIR__ . '/vendor/autoload.php';
// 心跳间隔55秒
define('HEARTBEAT_TIME', 55);
$worker = new Worker('websocket://0.0.0.0:8484');
$worker->count = 1;
// $worker->transport = 'ssl'; // 强制使用 SSL 传输层
$worker->connectionList = [];
// 进程启动后设置一个每10秒运行一次的定时器
$worker->onWorkerStart = function ($worker){
Timer::add(10, function() use ($worker){
$time_now = time();
foreach($worker->connections as $connection) {
// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
$connection->send('您长时间未请求服务器,链接已经断开');
$connection->close();
}
}
});
// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$textWorker = new Worker('tcp://0.0.0.0:5678');
$textWorker->onMessage = function($connection, $data) use ($worker)
{
// $data数组格式,里面有uid,表示向那个uid的页面推送数据
$data = json_decode($data, true);
//聊天
if ($data['type'] == 'text')
{
if (isset($worker->connectionList[$data['userToId']]))
{
$conns = $worker->connectionList[$data['userToId']];
foreach ($conns as $conn){
$conn->send(json_encode([
'status' => 1,
'message' => $data['message'],
]));
}
$connection->send(json_encode([
'status' => 1,
'message' => '消息发送成功',
]));
}else{
$connection->send(json_encode([
'status' => 0,
'message' => '消息发送失败,对方下线了哈',
]));
}
}
};
$textWorker->listen();
// $output->writeln('用户onWorkerStart' );
};
$worker->onConnect = function(TcpConnection $connection) use ($worker)
{
// 指令输出
// echo('用户:'.$worker->id.'-'.$connection->id.'链接成功');
echo($connection->id.'ok');
};
$worker->onMessage = function ($connection, $data) use ($worker) {
// 给connection临时设置一个lastMessageTime属性,用来记录上次收到消息的时间
$connection->lastMessageTime = time();
$data = json_decode($data, true);
//绑定用户ID
if ($data['type'] == 'bind' && isset($data['userId']) && !isset($connection->userId))
{
$connection->userId = $data['userId'];
$worker->connectionList[$connection->userId][$connection->id] = $connection;
}
//聊天
if ($data['type'] == 'text')
{
if (isset($worker->connectionList[$data['userToId']]))
{
$conns = $worker->connectionList[$data['userToId']];
foreach ($conns as $conn){
$conn->send('用户'.$data['userToId'].':'.$data['message']);
}
}else{
$connection->send('我:对方下线了');
}
}
//心跳接收并回应
if ($data['type'] == 'ping')
{
$connection->send('peng');
}
//向客户端自己发送数据
if (!empty($data['message'])){
$connection->send('我:'.$data['message']);
}
// // 指令输出
// $output->writeln('用户:'.$connection->id.',客户端接收到数据:'.json_encode($data) );
};
// 客户端连接断开时,断开对应的链接连接
$worker->onClose = function(TcpConnection $connection) use ($worker)
{
if(isset($connection->userId) && isset($worker->connectionList[$connection->userId]))
{
// 连接断开时删除映射
if (count($worker->connectionList[$connection->userId]) == 1){
unset($worker->connectionList[$connection->userId]);
}else{
unset($worker->connectionList[$connection->userId][$connection->id]);
}
}
// 指令输出
// echo('用户:'.$connection->id.'已经断开链接。'.$connection->userId);
};
// 运行worker
Worker::runAll();
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Example</title>
</head>
<body>
<p>用户:<input type="text" id="userToId" value="1000"></p>
<p>内容:<input type="text" id="msg"></p>
<p><button id="sendMessageButton">发送消息</button></p>
<script>
function getUrlParams() {
const url = window.location.search
const paramsRegex = /[?&]+([^=&]+)=([^&]*)/gi;
const params = {};
let match;
while (match = paramsRegex.exec(url)) {
params[match[1]] = match[2];
}
return params;
}
// 创建 WebSocket 连接
const ws = new WebSocket('wss://hospital.s.cntmt.cn/websocket');
let data = getUrlParams()
// 连接打开时触发
ws.onopen = function() {
console.log('已连接到服务器.',data.userid);
ws.send(JSON.stringify({
type: 'bind',
userId: data.userid,
}));
// 间隔30秒通知一次服务器我还在线
setInterval(() => {
ws.send(JSON.stringify({
type: 'ping'
}));
},50000);
};
// 收到服务器消息时触发
ws.onmessage = function(event) {
console.log(event.data);
// 可以在这里添加更多的处理逻辑,比如更新页面内容
// alert('Received from server: ' + event.data);
};
// 连接关闭时触发
ws.onclose = function() {
console.log('已断开与服务器的连接');
};
// 连接出错时触发
ws.onerror = function(error) {
console.error('WebSocket Error:', error);
};
// 获取按钮元素
const sendMessageButton = document.getElementById('sendMessageButton');
const msg = document.getElementById('msg');
const userToId = document.getElementById('userToId');
// 为按钮添加点击事件监听器
sendMessageButton.addEventListener('click', function() {
// 检查 WebSocket 连接是否已打开
if (ws.readyState === WebSocket.OPEN) {
// 发送消息给服务器'发送:' + msg.value
ws.send(JSON.stringify({
type: 'text',
userToId: userToId.value,
message: msg.value,
}));
// console.log('Message sent to server: Hello Server!');
} else {
console.error('WebSocket未打开。就绪状态:', ws.readyState);
}
});
</script>
</body>
</html>
send.php
文件
<?php
// 建立socket连接到内部推送端口
//134.175.18.35:5678这个是workerman服务器地址
$client = stream_socket_client('tcp://134.175.18.35:5678', $errno, $errmsg, 1);
// 推送的数据,包含uid字段,表示是给这个uid推送
$data = [
'type' => 'text',
'userToId' => '1000',
'message' => [
'title'=>'baiti',
'content'=>'neirong',
'time'=>date('Y-m-d H:i:s')
]
];
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data)."\n");
// 读取推送结果
echo fread($client, 8192);
在www.test.com
站点配置可以跨服务器,配置下面代理地址
location /websocket
{
proxy_pass http://134.175.18.35:8484;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
break;
}