WebSocket介绍
WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2011年成为标准。
优点及缺点
WebSocket的优势包括:
实时性: 由于WebSocket的持久化连接,它可以实现实时的数据传输,避免了Web应用程序需要不断地发送请求以获取最新数据的情况。
双向通信: WebSocket协议支持双向通信,这意味着服务器可以主动向客户端发送数据,而不需要客户端发送请求。
减少网络负载: 由于WebSocket的持久化连接,它可以减少HTTP请求的数量,从而减少了网络负载。
WebSocket的劣势包括:
需要浏览器和服务器都支持: WebSocket是一种相对新的技术,需要浏览器和服务器都支持。一些旧的浏览器和服务器可能不支持WebSocket。
需要额外的开销: WebSocket需要在服务器上维护长时间的连接,这需要额外的开销,包括内存和CPU。
安全问题: 由于WebSocket允许服务器主动向客户端发送数据,可能会存在安全问题。服务器必须保证只向合法的客户端发送数据。
基本概念
WebSocket 协议是一种基于TCP的协议,用于在客户端和服务器之间建立持久连接,并且可以在这个连接上实时地交换数据。WebSocket协议有自己的握手协议,用于建立连接,也有自己的数据传输格式。
当客户端发送一个 WebSocket 请求时,服务器将发送一个协议响应以确认请求。在握手期间,客户端和服务器将协商使用的协议版本、支持的子协议、支持的扩展选项等。一旦握手完成,连接将保持打开状态,客户端和服务器就可以在连接上实时地传递数据。
WebSocket 协议使用的是双向数据传输,即客户端和服务器都可以在任意时间向对方发送数据,而不需要等待对方的请求。它支持二进制数据和文本数据,可以自由地在它们之间进行转换。
生命周期
WebSocket 生命周期描述了 WebSocket 连接从创建到关闭的过程。一个 WebSocket 连接包含以下四个主要阶段:
- 连接建立阶段(Connection Establishment): 在这个阶段,客户端和服务器之间的 WebSocket 连接被建立。客户端发送一个 WebSocket 握手请求,服务器响应一个握手响应,然后连接就被建立了。
- 连接开放阶段(Connection Open): 在这个阶段,WebSocket 连接已经建立并开放,客户端和服务器可以在连接上互相发送数据。
- 连接关闭阶段(Connection Closing): 在这个阶段,一个 WebSocket 连接即将被关闭。它可以被客户端或服务器发起,通过发送一个关闭帧来关闭连接。
- 连接关闭完成阶段(Connection Closed): 在这个阶段,WebSocket 连接已经完全关闭。客户端和服务器之间的任何交互都将无效。
连接原理
客户端发起 WebSocket 连接请求,请求头中包含 Upgrade 和 Connection 两个字段。Upgrade 字段指明协议升级,Connection 字段指明协议连接类型,如下所示:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服务端接收到请求后,进行协议升级确认。如果服务端支持 WebSocket 协议,则返回状态码 101 Switching。Protocols 响应,表明接受协议升级请求,同时也会发送服务端的 Sec-WebSocket-Accept 头信息加密结果,如下所示:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
客户端收到服务端响应后,进行协议升级确认,验证服务端的 Sec-WebSocket-Accept 头信息加密结果是否正确。如果正确,表明连接已经升级成功,可以进行数据传输。
数据传输过程中,客户端和服务端可以双向发送或接收数据,数据格式为帧(Frame),帧是 WebSocket 传输的最小单位,包含了真实数据的二进制流以及控制信息,数据传输完毕后,可以关闭连接。
数据格式
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
头部信息(Header)
- FIN(1位):表示这个数据帧是否是消息的最后一帧。如果是最后一帧,该值为1;否则为0。
- RSV1, RSV2, RSV3(各1位):预留字段,暂时没有使用,值一般为0。
Opcode(4位):指定操作代码。它可以有以下值:
- 0x0:表示数据帧是一个连续帧
- 0x1:表示数据帧是一个文本帧
- 0x2:表示数据帧是一个二进制帧
- 0x8:表示连接断开
- 0x9:表示ping
- 0xA:表示pong
- Mask(1位):如果设置为1,则需要一个掩码键(Masking Key),用于数据的安全传输。
- Payload length(7位或7+16位或7+64位):指定有效负载数据的长度(注意,这里指的是原始长度,没有应用掩码的长度)。如果它的值小于或等于125,则该字段就是有效负载的长度。如果它的值为126,则紧随其后的2个字节(16个比特位)用于指定长度。如果它的值为127,则紧随其后的8个字节(64个比特位)用于指定长度。
- 掩码键(Masking key) 掩码键是4个字节长的随机数,用于安全传输数据。
- 负载数据(Payload data) 负载数据是真正需要传输的消息数据。如果Mask标志为1,则需要对负载数据进行逐比特的异或操作(XOR)以加密数据。
代码实例
讲解完WebSocket的基本概念后下面使用PHP语言搭建一个简单的WebSocket后端,当有WebSocket连接时打印已获取连接,后面每隔3s打印一次当前时间,当后端接收到你好时,会触发回复你好,已接收到请求。
<?php
/* 阻塞式、单进程 PHP WebSocket 服务器
* 运行:php server.php
* 监听:0.0.0.0:9501
*/
$host = '0.0.0.0';
$port = 9501;
@ini_set('display_errors', '1');
error_reporting(E_ALL);
$listen = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$listen) die("socket_create 失败\n");
socket_set_option($listen, SOL_SOCKET, SO_REUSEADDR, 1);
if (!socket_bind($listen, $host, $port)) die("bind 失败:{$host}:{$port}\n");
if (!socket_listen($listen, 128)) die("listen 失败\n");
socket_set_nonblock($listen);
$clients = [$listen];
$handshake= [];
$lastTime = [];
echo "WebSocket 服务器启动,监听 ws://{$host}:{$port}\n";
while (true) {
$read = $clients;
$write = $except = null;
@socket_select($read, $write, $except, 0, 200000);
if (in_array($listen, $read)) {
$sock = socket_accept($listen);
if ($sock) {
socket_set_nonblock($sock);
$clients[(int)$sock] = $sock;
echo "新连接: " . (int)$sock . "\n";
}
}
foreach ($clients as $k => $sock) {
if ($sock === $listen) continue;
if (in_array($sock, $read)) {
$buf = socket_read($sock, 2048, PHP_BINARY_READ);
if ($buf === '' || $buf === false) { // 断开
socket_close($sock);
unset($clients[$k], $handshake[$k], $lastTime[$k]);
echo "连接 $k 断开\n";
continue;
}
if (!isset($handshake[$k])) {
if (handshake($sock, $buf)) $handshake[$k] = true;
continue;
}
$data = decodeFrame($buf);
if ($data === false) continue;
echo "收到 $k 消息: $data\n";
if (trim($data) === '你好') {
$reply = encodeFrame('你好,已接收到请求');
@socket_write($sock, $reply, strlen($reply));
}
}
// 每 3 秒广播时间
if (isset($handshake[$k]) && (time() - ($lastTime[$k] ?? 0)) >= 3) {
$msg = encodeFrame('当前时间: ' . date('Y-m-d H:i:s'));
@socket_write($sock, $msg, strlen($msg));
$lastTime[$k] = time();
}
}
}
/* ---------- 协议函数 ---------- */
function handshake($sock, $buf)
{
if (!preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buf, $m)) return false;
$key = trim($m[1]);
$accept= base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
$reply = "HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: $accept\r\n\r\n";
return socket_write($sock, $reply, strlen($reply)) !== false;
}
function decodeFrame($data)
{
if (strlen($data) < 2) return false;
$len = ord($data[1]) & 127;
if ($len === 126) {
$mask = substr($data, 4, 4); $payload = substr($data, 8);
} elseif ($len === 127) {
$mask = substr($data, 10, 4); $payload = substr($data, 14);
} else {
$mask = substr($data, 2, 4); $payload = substr($data, 6);
}
return $len > 0 ? $payload ^ str_repeat($mask, ceil($len / 4)) : '';
}
function encodeFrame($msg)
{
$len = strlen($msg);
if ($len < 126) return chr(0x81) . chr($len) . $msg;
if ($len < 65536) return chr(0x81) . chr(126) . pack('n', $len) . $msg;
return chr(0x81) . chr(127) . pack('J', $len) . $msg;
}
![图片[1]-【技术教程】WebSocket使用](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/10/image-1024x570.png)
![图片[2]-【技术教程】WebSocket使用](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/10/image-1-1024x178.png)










暂无评论内容