0

0

细说websocket

php中文网

php中文网

发布时间:2016-06-23 14:37:51

|

1059人浏览过

|

来源于php中文网

原创

下面我画了一个图演示 client 和 server 之间建立 websocket 连接时握手部分,这个部分在 node 中可以十分轻松的完成,因为 node 提供的 net 模块已经对 socket 套接字做了封装处理,开发者使用的时候只需要考虑数据的交互而不用处理连接的建立。而 php 没有,从 socket 的连接、建立、绑定、监听等,这些都需要我们自己去操作,所以有必要拿出来再说一说。

    +--------+    1.发送Sec-WebSocket-Key        +---------+    |        | --------------------------------> |        |    |        |    2.加密返回Sec-WebSocket-Accept  |        |    | client | <-------------------------------- | server |    |        |    3.本地校验                      |        |    |        | --------------------------------> |        |    +--------+                                   +--------+

看了我写的上一篇文章的同学应该是对上图有了比较全面的理解。① 和 ② 实际上就是一个 HTTP 的请求和响应,只不过我们在处理的过程中我们拿到的是没有经过解析的字符串。如:

GET /chat HTTP/1.1Host: server.example.comOrigin: http://example.com

我们往常看到的请求是这个样子,当这东西到了服务器端,我们可以通过一些代码库直接拿到这些信息。

一、php 中处理 websocket

WebSocket 连接是由客户端主动发起的,所以一切要从客户端出发。第一步是要解析拿到客户端发过来的 Sec-WebSocket-Key 字符串。

GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13

前文中也提到了 client 请求的格式(如上),首先 php 建立一个 socket 连接,监听端口的信息。

1. socket 连接的建立

关于 socket 套接字的建立,相信很多大学修过计算机网络的人都知道了,下面是一张连接建立的过程:

深山旅游网站管理系统
深山旅游网站管理系统

旅游网站管理系统是由深山工作室自主研发而成,具有以下相应的功能.1. 旅游新闻发布,站内新闻发布2. 发布旅游线路详细信息.包括:线路名称、线路类型、线路报价、游完整条线路所需时间、线路说明、线路途经景点、行程安排及其它备注。3. 旅游景点介绍推广4. 旅行社加盟介绍系统.包括:各个旅行社可以发布自己的旅游线路.管理留言,发布信息(vip,会员)5. 酒店宾馆加盟介绍系统,酒店宾馆发布客房信息(v

下载

// 建立一个 socket 套接字$master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1);socket_bind($master, $address, $port);socket_listen($master);

相比 node,这个地方的处理实在是太麻烦了,上面几行代码并未建立连接,只不过这些代码是建立一个 socket 套接字必须要写的东西。由于处理过程稍微有复杂,所以我把各种处理写进了一个类中,方便管理和调用。

//demo.phpClass WS {    var $master;  // 连接 server 的 client    var $sockets = array(); // 不同状态的 socket 管理    var $handshake = false; // 判断是否握手    function __construct($address, $port){        // 建立一个 socket 套接字        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)               or die("socket_create() failed");        socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)              or die("socket_option() failed");        socket_bind($this->master, $address, $port)                                or die("socket_bind() failed");        socket_listen($this->master, 2)                                           or die("socket_listen() failed");        $this->sockets[] = $this->master;        // debug        echo("Master socket  : ".$this->master."\n");        while(true) {            //自动选择来消息的 socket 如果是握手 自动选择主机            $write = NULL;            $except = NULL;            socket_select($this->sockets, $write, $except, NULL);            foreach ($this->sockets as $socket) {                //连接主机的 client                 if ($socket == $this->master){                    $client = socket_accept($this->master);                    if ($client < 0) {                        // debug                        echo "socket_accept() failed";                        continue;                    } else {                        //connect($client);                        array_push($this->sockets, $client);                        echo "connect client\n";                    }                } else {                    $bytes = @socket_recv($socket,$buffer,2048,0);                    if($bytes == 0) return;                    if (!$this->handshake) {                        // 如果没有握手,先握手回应                        //doHandShake($socket, $buffer);                        echo "shakeHands\n";                    } else {                        // 如果已经握手,直接接受数据,并处理                        $buffer = decode($buffer);                        //process($socket, $buffer);                         echo "send file\n";                    }                }            }        }    }}

demo.php 握手连接测试代码

 

上面这段代码是经过我调试了的,没太大的问题,如果想测试的话,可以在 cmd 命令行中键入 php /path/to/demo.php;当然,上面只是一个类,如果要测试的话,还得新建一个实例。

$ws = new WS('localhost', 4000);

客户端代码可以稍微简单点:

var ws = new WebSocket("ws://localhost:4000");ws.onopen = function(){    console.log("握手成功");};ws.onerror = function(){    console.log("error");};

运行服务器代码,当客户端连接的时候,我们可以看到:

通过上面的代码可以清晰的看到整个交流的过程。首先是建立连接,node 中这一步已经封装到了 net 和 http 模块,然后判断是否握手,如果没有的话,就 shakeHands。这里的握手我直接就 echo 了一个单词,表示进行了这个东西,前文我们提到过握手算法,这里就直接写了。

2. 提取 Sec-WebSocket-Key 信息

function getKey($req) {    $key = null;    if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match)) {         $key = $match[1];     }    return $key;}

这里比较简单,直接正则匹配,websocket 信息头一定包含 Sec-WebSocket-Key,所以我们匹配起来也比较快捷~

3. 加密 Sec-WebSocket-Key

function encry($req){    $key = $this->getKey($req);    $mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";    return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));}

将 SHA-1 加密后的字符串再进行一次 base64 加密。如果加密算法错误,客户端在进行校检的时候会直接报错:

4. 应答 Sec-WebSocket-Accept

function dohandshake($socket, $req){    // 获取加密key    $acceptKey = $this->encry($req);    $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .               "Upgrade: websocket\r\n" .               "Connection: Upgrade\r\n" .               "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" .               "\r\n";    // 写入socket    socket_write(socket,$upgrade.chr(0), strlen($upgrade.chr(0)));    // 标记握手已经成功,下次接受数据采用数据帧格式    $this->handshake = true;}

这里千万要注意,每一个请求和相应的格式,最后有一个空行,也就是 \r\n,开始测试的时候把这东西给弄丢了,纠结了半天。

当客户端成功校检key后,会触发 onopen 函数:

5. 数据帧处理

// 解析数据帧function decode($buffer)  {    $len = $masks = $data = $decoded = null;    $len = ord($buffer[1]) & 127;    if ($len === 126)  {        $masks = substr($buffer, 4, 4);        $data = substr($buffer, 8);    } else if ($len === 127)  {        $masks = substr($buffer, 10, 4);        $data = substr($buffer, 14);    } else  {        $masks = substr($buffer, 2, 4);        $data = substr($buffer, 6);    }    for ($index = 0; $index < strlen($data); $index++) {        $decoded .= $data[$index] ^ $masks[$index % 4];    }    return $decoded;}

这里涉及的编码问题在前文中已经提到过了,这里就不赘述,php 对字符处理的函数太多了,也记得不是特别清楚,这里就没有详细的介绍解码程序,直接把客户端发送的数据原样返回,可以算是一个聊天室的模式吧。

// 返回帧信息处理function frame($s) {    $a = str_split($s, 125);    if (count($a) == 1) {        return "\x81" . chr(strlen($a[0])) . $a[0];    }    $ns = "";    foreach ($a as $o) {        $ns .= "\x81" . chr(strlen($o)) . $o;    }    return $ns;}// 返回数据function send($client, $msg){    $msg = $this->frame($msg);    socket_write($client, $msg, strlen($msg));}

客户端代码:

var ws = new WebSocket("ws://localhost:4000");ws.onopen = function(){    console.log("握手成功");};ws.onmessage = function(e){    console.log("message:" + e.data);};ws.onerror = function(){    console.log("error");};ws.send("李靖");

在连通之后发送数据,服务器原样返回:

 

二、注意问题 1. websocket 版本问题

客户端在握手时的请求中有Sec-WebSocket-Version: 13,这样的版本标识,这个是一个升级版本,现在的浏览器都是使用的这个版本。而以前的版本在数据加密的部分更加麻烦,它会发送两个key:

GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeOrigin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Key1: xxxxSec-WebSocket-Key2: xxxx

如果是这种版本(比较老,已经没在使用了),需要通过下面的方式获取

function encry($key1,$key2,$l8b){ //Get the numbers preg_match_all('/([\d]+)/', $key1, $key1_num); preg_match_all('/([\d]+)/', $key2, $key2_num);    $key1_num = implode($key1_num[0]);    $key2_num = implode($key2_num[0]);    //Count spaces    preg_match_all('/([ ]+)/', $key1, $key1_spc);    preg_match_all('/([ ]+)/', $key2, $key2_spc);    if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; }    //Some math    $key1_sec = pack("N",$key1_num / $key1_spc);    $key2_sec = pack("N",$key2_num / $key2_spc);    return md5($key1_sec.$key2_sec.$l8b,1);}

只能无限吐槽这种验证方式!相比 nodeJs 的 websocket 操作方式:

//服务器程序var crypto = require('crypto');var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';require('net').createServer(function(o){    var key;    o.on('data',function(e){        if(!key){            //握手            key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];            key = crypto.createHash('sha1').update(key + WS).digest('base64');            o.write('HTTP/1.1 101 Switching Protocols\r\n');            o.write('Upgrade: websocket\r\n');            o.write('Connection: Upgrade\r\n');            o.write('Sec-WebSocket-Accept: ' + key + '\r\n');            o.write('\r\n');        }else{            console.log(e);        };    });}).listen(8000);

多么简洁,多么方便!有谁还愿意使用 php 呢。。。。

2. 数据帧解析代码

本文没有给出 decodeFrame 这样数据帧解析代码,前文中给出了数据帧的格式,解析纯属体力活。

3. 代码下载

对这部分感兴趣的同学,可以再去深究。提供了参考代码下载。

4. 相关开源库参考

http://socketo.me Ratchet 为 php 封装的一个 WebSockets 库。 ]

Google 上搜索 php+websoket+class,也能找到不少相关的资料。

三、参考资料 http://www.php.net/manual/zh/ref.sockets.php php manual http://www.rfc-editor.org/rfc/rfc6455.txt  [RFC6455] WebSocket

 

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 生态工具与框架:扩展开发能力
Golang 生态工具与框架:扩展开发能力

《Golang 生态工具与框架》系统梳理 Go 语言在实际工程中的主流工具链与框架选型思路,涵盖 Web 框架、RPC 通信、依赖管理、测试工具、代码生成与项目结构设计等内容。通过真实项目场景解析不同工具的适用边界与组合方式,帮助开发者构建高效、可维护的 Go 工程体系,并提升团队协作与交付效率。

1

2026.02.24

Golang 性能优化专题:提升应用效率
Golang 性能优化专题:提升应用效率

《Golang 性能优化专题》聚焦 Go 应用在高并发与大规模服务中的性能问题,从 profiling、内存分配、Goroutine 调度、GC 机制到 I/O 与锁竞争逐层分析。结合真实案例讲解定位瓶颈的方法与优化策略,帮助开发者建立系统化性能调优思维,在保证代码可维护性的同时显著提升服务吞吐与稳定性。

0

2026.02.24

Golang 面试题精选:高频问题与解答
Golang 面试题精选:高频问题与解答

Golang 面试题精选》系统整理企业常见 Go 技术面试问题,覆盖语言基础、并发模型、内存与调度机制、网络编程、工程实践与性能优化等核心知识点。每道题不仅给出答案,还拆解背后的设计原理与考察思路,帮助读者建立完整知识结构,在面试与实际开发中都能更从容应对复杂问题。

1

2026.02.24

Golang 运行与部署实战:从本地到云端
Golang 运行与部署实战:从本地到云端

《Golang 运行与部署实战》围绕 Go 应用从开发完成到稳定上线的完整流程展开,系统讲解编译构建、环境配置、日志与配置管理、容器化部署以及常见运维问题处理。结合真实项目场景,拆解自动化构建与持续部署思路,帮助开发者建立可靠的发布流程,提升服务稳定性与可维护性。

3

2026.02.24

Golang 疑难杂症解决指南:常见问题排查与优化
Golang 疑难杂症解决指南:常见问题排查与优化

《Golang 疑难杂症解决指南》聚焦开发过程中常见却棘手的问题,从并发模型、内存管理、性能瓶颈到工程化实践逐步拆解。通过真实案例与调试思路,帮助开发者定位问题根因,建立系统化排查方法。不只给出答案,更强调分析路径与工具使用,让你在复杂 Go 项目中具备持续解决问题的能力。

0

2026.02.24

Golang 入门学习路线:从零基础到上手开发
Golang 入门学习路线:从零基础到上手开发

Golang 入门路线涵盖从零到上手的核心路径:首先打牢基础语法与切片等底层机制;随后攻克 Go 的灵魂——接口设计与 Goroutine 并发模型;接着通过 Gin 框架与 GORM 深入 Web 开发实战;最后在微服务与云原生工具开发中进阶,旨在培养具备高性能并发处理能力的后端工程师。

0

2026.02.24

中国研究生招生信息网官方网站入口 研招网网页版在线入口
中国研究生招生信息网官方网站入口 研招网网页版在线入口

中国研究生招生信息网入口(https://yz.chsi.com.cn) 此网站是研究生报名入口的唯一官方网站

71

2026.02.24

苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法
苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法

本专题汇总苹果官网最新可用入口及中国站点访问方式,涵盖官网直达链接、iPhone官方页面查看方法与常见访问说明,帮助用户快速进入苹果官方网站,便捷了解产品信息与官方服务。

14

2026.02.24

Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址
Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址

本专题系统整理Asianfanfics(AFF)官方网站最新可用入口,涵盖官方平台最新直达地址、官网登录方式及中文访问指引,帮助用户快速、安全地进入AFF平台浏览与使用相关内容。

15

2026.02.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
高洛峰细说PHP视频教程
高洛峰细说PHP视频教程

共55课时 | 10.9万人学习

动力学院设计模式PHP视频教程
动力学院设计模式PHP视频教程

共15课时 | 3万人学习

细说PHP第三季
细说PHP第三季

共58课时 | 11.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号