Building Real-Time WebSocket Services with Workerman: A PHP High‑Performance Guide
This article introduces Workerman, an open‑source pure‑PHP high‑performance application container, outlines its key features and typical use cases, and provides step‑by‑step installation, server‑side and client‑side code examples, as well as Nginx reverse‑proxy configuration for secure WebSocket deployment.
Overview
Workerman is an open‑source, pure‑PHP high‑performance application container that breaks the limits of traditional PHP applications, enabling the development of real‑time network services. It is not a conventional MVC framework but a low‑level, general‑purpose service framework supporting TCP, UDP, HTTP, WebSocket and other protocols, suitable for instant messaging, IoT, game servers, high‑performance HTTP services, and more.
Main Features
High performance : runs in resident memory, eliminating per‑request initialization overhead and supporting massive concurrent connections.
Multi‑process support : fully utilizes multi‑core CPUs by handling requests in multiple processes.
Long‑connection support : ideal for applications that require persistent connections such as chat rooms and games.
Rich protocol support : handles standard and custom protocols.
Distributed deployment : can be deployed at large scale across multiple machines.
Smooth restart : allows seamless service upgrades without affecting clients.
Application Scenarios
Instant messaging : real‑time chat rooms, push notifications.
Internet of Things : communication with smart devices.
Game servers : supports board games, MMORPGs, etc.
High‑performance HTTP services : builds fast websites or APIs.
Data monitoring : real‑time data change push.
The design philosophy of Workerman is simplicity, stability, and high performance, making it suitable for developers who want to overcome traditional PHP limitations.
Installation
composer require workerman/workermanServer‑Side Code (start.php)
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Timer;
require_once __DIR__ . '/vendor/autoload.php';
// Heartbeat interval 55 seconds
define('HEARTBEAT_TIME', 55);
$worker = new Worker('websocket://0.0.0.0:8484');
$worker->count = 1;
$worker->connectionList = [];
// Periodic timer (every 10 seconds)
$worker->onWorkerStart = function($worker) {
Timer::add(10, function() use ($worker) {
$time_now = time();
foreach ($worker->connections as $connection) {
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
$connection->send('You have been idle for a long time, the connection is closed');
$connection->close();
}
}
});
// Internal TCP port for data push (Text protocol)
$textWorker = new Worker('tcp://0.0.0.0:5678');
$textWorker->onMessage = function($connection, $data) use ($worker) {
$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'=>'Message sent successfully']));
} else {
$connection->send(json_encode(['status'=>0,'message'=>'Message failed, the peer is offline']));
}
}
};
$textWorker->listen();
};
$worker->onConnect = function(TcpConnection $connection) use ($worker) {
echo $connection->id . 'ok';
};
$worker->onMessage = function($connection, $data) use ($worker) {
$connection->lastMessageTime = time();
$data = json_decode($data, true);
// Bind user ID
if ($data['type'] == 'bind' && isset($data['userId']) && !isset($connection->userId)) {
$connection->userId = $data['userId'];
$worker->connectionList[$connection->userId][$connection->id] = $connection;
}
// Chat message
if ($data['type'] == 'text') {
if (isset($worker->connectionList[$data['userToId']])) {
foreach ($worker->connectionList[$data['userToId']] as $conn) {
$conn->send('User' . $data['userToId'] . ':' . $data['message']);
}
} else {
$connection->send('I: The other side is offline');
}
}
// Ping
if ($data['type'] == 'ping') {
$connection->send('peng');
}
// Echo non‑empty messages
if (!empty($data['message'])) {
$connection->send('Me:' . $data['message']);
}
};
$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]);
}
}
};
Worker::runAll();Client Test Code (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>User: <input type="text" id="userToId" value="1000"></p>
<p>Message: <input type="text" id="msg"></p>
<p><button id="sendMessageButton">Send Message</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;
}
const ws = new WebSocket('wss://hospital.s.cntmt.cn/websocket');
const data = getUrlParams();
ws.onopen = function() {
console.log('Connected to server.', data.userid);
ws.send(JSON.stringify({type:'bind', userId: data.userid}));
setInterval(() => {
ws.send(JSON.stringify({type:'ping'}));
}, 50000);
};
ws.onmessage = function(event) {
console.log(event.data);
};
ws.onclose = function() {
console.log('Connection closed');
};
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() {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({type:'text', userToId: userToId.value, message: msg.value}));
} else {
console.error('WebSocket not open. Ready state:', ws.readyState);
}
});
</script>
</body>
</html>Server‑Side Notification Test (send.php)
<?php
// Connect to the internal push port (Text protocol)
$client = stream_socket_client('tcp://134.175.18.35:5678', $errno, $errmsg, 1);
// Data to push, includes uid indicating the target user
$data = [
'type' => 'text',
'userToId' => '1000',
'message' => [
'title' => 'baiti',
'content' => 'neirong',
'time' => date('Y-m-d H:i:s')
]
];
// Send data (Text protocol requires a newline at the end)
fwrite($client, json_encode($data) . "
");
// Read push result
echo fread($client, 8192);Nginx Reverse‑Proxy Configuration for HTTPS
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;
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
