PHP原生Socket实现WebSocket全双工通信详解
在PHP中实现WebSocket需要手动处理TCP连接、HTTP握手升级及数据帧编解码。本文将通过分析WebSocke协议原理,结合PHP原生Socket API,展示如何从零搭建一个支持多连接的WebSocket服务器,并以一个简易聊天室为例,完整演示握手、心跳、消息分发与断连处理的实际编码过程。
结果或目标
掌握在PHP环境中使用原生 socket_*() 函数集建立持久化长连接,理解WebSocket协议的握手流程(Upgrade Header交换)及数据帧格式,能够独立编写具备多用户管理能力的Socket服务端脚本。
准备条件
- 安装了
sockets扩展的PHP环境(通常Linux默认安装,Windows需开启extension=php_sockets.dll)。 - 具备基础的TCP/IP网络知识,理解C/S架构与服务端/客户端角色。
- 本地运行终端用于执行PHP后台脚本,以及现代浏览器用于前端测试。
真实操作顺序
第1步:理解WebSocket连接原理
HTTP是请求-响应模式,由客户端发起;而WebSocket允许服务端主动推送数据。建立WebSocket连接前,需完成一次HTTP握手:
- 客户端请求:发送带有
Upgrade: websocket和Sec-WebSocket-Key的请求头。 - 服务端响应:收到请求后,将
Sec-WebSocket-Key进行特定算法加密(SHA1 + Magic String),生成Sec-WebSocket-Accept返回给客户端,状态码为101 Switching Protocols。
第2步:PHP服务端Socket初始化
服务端需创建一个流式Socket(SOCK_STREAM),绑定IP和端口并进入监听状态。
function WebSocket($address, $port) {
// 创建IPv4 TCP Socket
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 设置允许地址重用,避免重启时报错
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
// 绑定地址与端口
socket_bind($server, $address, $port);
// 开始监听,最大挂起连接数为10
socket_listen($server, 10);
return $server;
}
第3步:实现主循环与连接管理
PHP脚本需保持运行状态,通过 socket_select() 阻塞等待I/O事件。需维护两个数组:$sockets(所有活跃fd)和 $users(记录连接状态、是否握手等信息)。
function run() {
while (true) {
$changes = $this->sockets;
$write = NULL;
$except = NULL;
// 阻塞直到有连接建立或数据到达
socket_select($changes, $write, $except, NULL);
foreach ($changes as $sock) {
// 新连接接入
if ($sock == $this->master) {
$client = socket_accept($this->master);
$key = uniqid();
$this->sockets[] = $client;
$this->users[$key] = [
'socket' => $client,
'shou' => false // 标记未握手
];
} else {
// 接收数据
$len = 0;
$buffer = '';
do {
$l = socket_recv($sock, $buf, 1000, 0);
$len += $l;
$buffer .= $buf;
} while ($l == 1000);
// 查找对应用户
$k = $this->search($sock);
// 长度小于7视为断开或无效数据
if ($len < 7) {
$this->send2($k);
continue;
}
// 未握手则执行握手逻辑
if (!$this->users[$k]['shou']) {
$this->woshou($k, $buffer);
} else {
// 已握手,解析数据帧
$decodedData = $this->uncode($buffer, $k);
if ($decodedData !== false) {
// 业务处理与推送
$this->handleMessage($k, $decodedData);
}
}
}
}
}
}
第4步:完成握手验证
核心在于处理 Sec-WebSocket-Key。必须严格遵循RFC 6455规范进行计算。
function woshou($k, $buffer) {
// 提取Key
$buf = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
$key = trim(substr($buf, 0, strpos($buf, "\r\n")));
// RFC 6455 规定的Magic String
$magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
// 计算Accept Key
$newKey = base64_encode(sha1($key . $magicString, true));
// 构造响应头
$response = "HTTP/1.1 101 Switching Protocols\r\n";
$response .= "Upgrade: websocket\r\n";
$response .= "Connection: Upgrade\r\n";
$response .= "Sec-WebSocket-Accept: " . $newKey . "\r\n\r\n";
socket_write($this->users[$k]['socket'], $response, strlen($response));
$this->users[$k]['shou'] = true;
}
第5步:数据帧解码(Masking Removal)
WebSocket客户端发往服务器的数据必须进行掩码(Mask)处理,服务端接收时需去除掩码还原原始Payload。
function uncode($str, $key) {
$msg = unpack('H*', $str);
$head = substr($msg[1], 0, 2);
// FIN bit + Opcode (0x81 for text)
if (hexdec($head[1]) < 8 || hexdec($head[1]) > 15) {
return false; // 未知类型
}
// 解析长度与掩码
$maskLen = substr($msg[1], 2, 2);
$len = hexdec($maskLen);
$isMasked = $len >= 126;
if ($len == 126) {
// 16位长度
$len = hexdec(substr($msg[1], 4, 4));
$maskOffset = 10;
} elseif ($len == 127) {
// 64位长度
$len = hexdec(substr($msg[1], 4, 16));
$maskOffset = 22;
} else {
$maskOffset = 8;
}
if ($isMasked) {
// 获取Masking-Key
$mKey = [$maskOffset+1, $maskOffset+2, $maskOffset+3, $maskOffset+4];
$dataStart = $maskOffset + 5;
$rawData = substr($msg[1], $dataStart);
$decoded = '';
$r = 0;
// XOR去掩码
for ($i = 0; $i < strlen($rawData); $i++) {
$decoded .= chr(hexdec(substr($rawData, $i*2, 2)) ^ hexdec(substr($msg[1], $mKey[($i % 4)]*2, 2)));
$i++; // consume next byte of data
}
return $decoded;
}
return false;
}
完成判断
当PHP脚本启动后,若能成功绑定端口且无报错输出,并在浏览器控制台看到WebSocket ReadyState变为1,即表示底层通道已连通。
原文有依据的排错
- 端口被占用:确保
socket_set_option中SO_REUSEADDR设为1,否则脚本重启时可能因TIME_WAIT状态失败。 - Huge Data:原生Socket处理大文本需注意内存泄漏,生产环境建议引入Redis作为发布订阅中间件解耦。
- 中文乱码:确保JSON序列化及传输过程中统一使用UTF-8,前端JS需正确解码URL参数。
简要总结
通过上述步骤,我们使用PHP原生API实现了WebSocket的核心握手与数据收发。此方法适合轻量级内部系统或学习原理,对于高并发场景建议转向Swoole等异步框架。以上就是PHP中使用原生Socket实现WebSocket通讯的详细教程,更多关于后端实时通信技术的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









