Ratchet
Ratchet 是一个用于异步服务WebSockets的PHP库。通过简单的接口构建应用程序,并通过组合不同的组件重用应用程序,而无需更改其任何代码。
什么是 WebSocket
WebSocket是一种通信协议,可在单个TCP连接上进行全双工通信。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。
通信流程
典型握手通信
客户端请求
代码语言:javascript复制GET /chat HTTP/1.1
Host: wss.tinywan.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
服务器响应
代码语言:javascript复制HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK xOo=
Sec-WebSocket-Protocol: chat
字段说明
Connection
必须设置Upgrade
,表示客户端希望连接升级。Upgrade
字段必须设置Websocket
,表示希望升级到Websocket
协议。Sec-WebSocket-Key
是随机的字符串,服务器端会用这些数据来构造出一个SHA-1
的信息摘要。把Sec-WebSocket-Key
加上一个特殊字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11
,然后计算SHA-1摘要,之后进行Base64编码,将结果做为Sec-WebSocket-Accept
头的值,返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。Sec-WebSocket-Version
表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当弃用。Origin
字段是可选的,通常用来表示在浏览器中发起此Websocket连接所在的页面,类似于Referer
。但是,与Referer
不同的是,Origin
只包含了协议和主机名称。其他一些定义在HTTP协议中的字段,如Cookie
等,也可以在Websocket中使用。
案例
服务端
新建目录
代码语言:javascript复制mkdir ratchet.websocket.tinywan.cn
安装依赖包
代码语言:javascript复制E:dnmpwwwratchet.websocket.tinywan.cn>composer require cboden/ratchet
./composer.json has been created
Running composer update cboden/ratchet
Loading composer repositories with package information
Updating dependencies
Lock file operations: 18 installs, 0 updates, 0 removals
- Locking cboden/ratchet (v0.4.4)
- Locking evenement/evenement (v3.0.2)
- Locking guzzlehttp/psr7 (2.6.1)
- Locking psr/http-factory (1.0.2)
- Locking psr/http-message (2.0)
- Locking ralouphie/getallheaders (3.0.3)
- Locking ratchet/rfc6455 (v0.3.1)
- Locking react/cache (v1.2.0)
- Locking react/dns (v1.12.0)
- Locking react/event-loop (v1.5.0)
- Locking react/promise (v3.0.0)
- Locking react/socket (v1.14.0)
- Locking react/stream (v1.3.0)
- Locking symfony/deprecation-contracts (v2.5.2)
- Locking symfony/http-foundation (v5.4.32)
- Locking symfony/polyfill-mbstring (v1.28.0)
- Locking symfony/polyfill-php80 (v1.28.0)
- Locking symfony/routing (v5.4.26)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 18 installs, 0 updates, 0 removals
- Downloading symfony/routing (v5.4.26)
- Downloading symfony/http-foundation (v5.4.32)
- Downloading react/event-loop (v1.5.0)
- Downloading evenement/evenement (v3.0.2)
- Downloading react/stream (v1.3.0)
- Downloading react/promise (v3.0.0)
- Downloading react/cache (v1.2.0)
- Downloading react/dns (v1.12.0)
- Downloading react/socket (v1.14.0)
- Downloading ratchet/rfc6455 (v0.3.1)
- Downloading cboden/ratchet (v0.4.4)
- Installing symfony/polyfill-php80 (v1.28.0): Extracting archive
- Installing symfony/deprecation-contracts (v2.5.2): Extracting archive
- Installing symfony/routing (v5.4.26): Extracting archive
- Installing symfony/polyfill-mbstring (v1.28.0): Extracting archive
- Installing symfony/http-foundation (v5.4.32): Extracting archive
- Installing react/event-loop (v1.5.0): Extracting archive
- Installing evenement/evenement (v3.0.2): Extracting archive
- Installing react/stream (v1.3.0): Extracting archive
- Installing react/promise (v3.0.0): Extracting archive
- Installing react/cache (v1.2.0): Extracting archive
- Installing react/dns (v1.12.0): Extracting archive
- Installing react/socket (v1.14.0): Extracting archive
- Installing ralouphie/getallheaders (3.0.3): Extracting archive
- Installing psr/http-message (2.0): Extracting archive
- Installing psr/http-factory (1.0.2): Extracting archive
- Installing guzzlehttp/psr7 (2.6.1): Extracting archive
- Installing ratchet/rfc6455 (v0.3.1): Extracting archive
- Installing cboden/ratchet (v0.4.4): Extracting archive
6 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
12 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found
Using version ^0.4.4 for cboden/ratchet
WebsocketServer.php 代码
代码语言:javascript复制<?php
/**
* @desc Websocket 服务端代码
* @author Tinywan(ShaoBo Wan)
* @date 2023/11/30 9:20
*/
require __DIR__ . '/vendor/autoload.php';
use RatchetConnectionInterface;
use RatchetRFC6455MessagingMessageInterface;
class WebsocketServer implements RatchetWebSocketMessageComponentInterface
{
/**
* @var SplObjectStorage
*/
protected $clients;
/**
* WebsocketServer constructor.
*/
public function __construct()
{
$this->clients = new SplObjectStorage;
}
/**
* @desc: onOpen 描述
* @param ConnectionInterface $conn
* @author Tinywan(ShaoBo Wan)
*/
public function onOpen(ConnectionInterface $conn)
{
// Store the new connection to send messages to later
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})n";
}
/**
* @desc: onMessage 描述
* @param ConnectionInterface $from
* @param MessageInterface $msg
* @author Tinywan(ShaoBo Wan)
*/
public function onMessage(ConnectionInterface $from, MessageInterface $msg)
{
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "n"
, $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
foreach ($this->clients as $client) {
$client->send($msg);
}
}
/**
* @desc: onClose 描述
* @param ConnectionInterface $conn
* @author Tinywan(ShaoBo Wan)
*/
public function onClose(ConnectionInterface $conn)
{
// The connection is closed, remove it, as we can no longer send it messages
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnectedn";
}
/**
* @desc: onError 描述
* @param ConnectionInterface $conn
* @param Exception $e
* @author Tinywan(ShaoBo Wan)
*/
public function onError(ConnectionInterface $conn, Exception $e)
{
echo "An error has occurred: {$e->getMessage()}n";
$conn->close();
}
}
$server = RatchetServerIoServer::factory(
new RatchetHttpHttpServer(
new RatchetWebSocketWsServer(
new WebsocketServer()
)
),
8585
);
$server->run();
运行 WebSocket 服务器
代码语言:javascript复制php WebsocketServer.php
客户端
chat.html
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Real-time WebSocket</title>
<script src="http://cdn.bootcss.com/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
</head>
<body>
<h2>开源技术小栈 WebSocket 演示</h2>
<h3>发送一个websocket数据:</h3>
<input type="text" style="height: 50px; width: 100%;" name="data" id="data"><p></p>
<button id="submit" onclick="sub()" style="height: 50px; width: 100%;" >提交</button> <p></p>
<div id="output"></div>
<div id="message"></div>
<script type="text/javascript">
var wsUri = "ws://127.0.0.1:8585";
var output;
var websocket;
function init() {
output = document.getElementById("output");
testWebSocket();
websocket.send("hello n");
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function (evt) {
onOpen(evt)
};
websocket.onclose = function (evt) {
onClose(evt)
};
websocket.onmessage = function (evt) {
onMessage(evt)
};
websocket.onerror = function (evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen("CONNECTED");
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style="color: blue;">RESPONSE: ' evt.data '</span>');
$("#message").append(evt.data)
}
function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' evt.data);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
function sub()
{
websocket.send($("#data").val());
}
window.addEventListener("load", init, false);
</script>
</body>
</html>