之前为了学习 go 写了两个小项目,fastcgi-go 和 mysql-proxy 都是用了 go 来处理二进制包,从而实现一些协议(fastcgi 协议 和 mysql 协议)
之后感觉各种应用协议并不是高深复杂的东西,只要跟着协议走就能跑通它。这次有点好奇 websocket 是什么协议,和 socket 是什么关系,有空用 php 写一个小框架就明白了
这里先贴出 github 地址 websocket-php

首先搜索 websocket 协议,在 Laravel China 找到了 老司机带你用 PHP 实现 Websocket 协议 这篇文章,里面介绍了很多基础知识,对于第一次实现 websocket 有很大帮助,因此就不在这赘述了
而我想做一个稍微 “能用” 的东西。在实现这些功能的过程中,遇到了一些与 websocket 相关或不相关的问题,要好好记录一下

目标
跟 Workerman 一样启动一个服务,同时管理多个连接,为各个事件设置回调,就像这样

$server = new Server();
$server->on('message', function(Connection $client, Message $message) use ($server) {
    $server->sendMessage($client, Packet::MSG_TYPE_TXT, 'get ' . $message->content());
});
$server->start();


socket 连接的非阻塞模式
在调用 socket_accept 和 socket_read 时,程序完全停止住了,这时如果第二个连接请求进来,则会超时。而 socket_set_nonblock 可以将这个连接设置为非阻塞模式。
这时与一般的阻塞模式有些许不同,在等待输入时,这两个函数不是阻塞住,而是直接返回 false ,需要做为 false 时的处理

$client = socket_accept($this->connection);
if ($client) {
    ...
}


Chrome 浏览器的包长限制
写完后在 index.html 测试,发现包长超过 255 字节就会报错


我猜测 Chrome 限制了 websocket 协议包的长度,用 go 做客户端测试了一下,发现能正常传输,这也证实了猜想。所以要考虑分包的问题了

消息抽象 和 websocket 协议分包
使用 websocket 通信应该专注与传输的消息而忽略底层的分包操作,因此需要先抽象出一个 Message 类,一个消息可以由一个或多个包组成
无论是发送还是接受消息时,只需要遵守 websocket 协议分包即可,协议中有包有两个部分用于分包。第一个是第一位(FIN),1 表示结束,0 表示未结束。第二个是第 5 到 8 位(opcode),如果是 0 的话表示这个包是上个包的延续

输入抽象以方便测试
在编写的过程中总是要不断测试,开启服务,再开启客户端发送消息的测试方式太过麻烦。如果可以不开启服务,直接读取包的内容就好了
于是包的读取通过 Reader 类完成,把输入的内容存到文件以后,Server 可以通过 startReadFile 来完成一次文件读取,来模拟接收一个或多个消息

运行截图
测试过程:

发送长度达到需要分包的消息
同时打开多个连接发送消息
关闭一个连接
读取本地文件
网页截图

 

php 输出截图

读取本地文件

$server = new Server();
$server->on('message', function($conn, Message $msg) {
    echo $msg->length(), PHP_EOL, substr($msg->content(), $msg->length()-10, 10);
});
$server->startReadFile('test_bin_file');