前言
按理说,作为一个电子信息工程毕业的毕业生,websocket这种通信范畴的东西,即使是应用层的内容,我应该早早就应该研究一下的。很惭愧,不怎么用到便没去学习,之前公司用了workerman做了一个聊天工具,但由于不是我负责,就没去细细把玩。
通信这种东西,我们大学都是做过实验的。一个模拟信号,如何通过调制转成数字信号,无线还要通过载波传输,接收端如何将一个信号接收,经过滤波,再译码的一系列过程,一切皆0-1,这个过程耗费了多少人的心血,香农定理、傅里叶变换,要学太多的东西。很遗憾,大学没有好好学,如果我能将那些学科《模拟电路》、《数字电路》、《DSP信号处理》、《通信信号处理》、《电磁波》一一融汇贯通的话,那肯定是不一样的自己。
说到底还是俗人一个,耐不下性子做学问,人世间有太多诱惑了,看小说、打篮球、踢足球、弹吉他、弹电子琴、看电影,太多太多,可能大多时候是精神无所寄托罢了。只有从内心征服自己,才是真正的强者。
websocket之前要先了解http协议,在http协议之前,又需要了解tcp/ip四层协议,协议是一层一层的,虽然这里websocket和http协议却不是分层的。
说到分层,我们知道,操作系统是分层的,OSI七层模型自不必说,代码设计大抵也是MVC分层的,这世间大到宇宙天体,小到尘埃原子,都是分层的。
很多东西都可以用到类比思维。
再比如模块化,自己拆过笔记本或者组装过台式机就知道,内存、硬盘、主板、光驱等等都是模块化的设计,到架构设计,nginx+php+mysql/mongo+kafka+redis+rocketMQ+es,到我们写代码,也都是模块化设计,再到最近几年流行的微服务,不也是一个一个服务的划分吗?
很多时候,其实写代码和其它行业相比,从方法论的角度来说,并无不同,总有一些小诀窍,提炼出来,在任何行业都适用的。如果从这种普遍适用的角度,那你提炼的东西很可能就是一种哲学了。我对哲学浅薄的理解是:哲学是对过去的总结,并对未来具有指导意义。不同之处在于你在某一行业数年乃至数十年对知识和经验的积累,这种是无法一朝一夕就能赶上的。
但是有用的是经验,不是经历。
还是说到柴静在《看见》这本书,陈虻对她说的那句话:“痛苦是财富?这是扯淡!痛苦就是痛苦,对痛苦的思考才是财富”。
经历就是经历,对经历的思考和总结,这种提炼的过程,才是财富。
websocket 理论知识
先说下http协议,http协议的固定的一个request对应一个response,即使出错了,不管是4XX客户端错误还是5XX服务器错误,服务端都得给你来一个响应。说到服务端错误,之前公司的服务器常常报502 bad gateway,但一重启php-fpm就可以,后来说的php的opcache缓存太小了,顺手记录一下。
https只是在http的基础上加了ssl握手以保证其安全性,之前有写过文章《细说HTTPS》介绍,就不再赘述。
相应的,ws(websocket)也有其加密协议wss。
首先,websocket之前,大家是如何获取服务端最新的数据呢。
-
Ajax轮询,想必大家都用过的,没什么好说的,就是定时请求罢了。
-
long poll,这种虽然减少了多次请求的消耗,但却阻塞了连接。
作为对比,websocket是这样的:
上面三个图来源于网络,侵删。
也就是说,无论是Ajax轮询,还是long poll,本质上都是对服务端的轮询,只是后者减少了请求次数,而websocket则不同,它是真正的在client和server之间建立了一个长连接,服务端可以主动给客户端推送数据。但是server主进程主要是一个对端口listenning操作,虽然注册了一些事件,比如open,message,close,但是你想要给客户端主动推,必须要有一个触发事件。
举个例子,server进程在跑,client1和server已经建立了通道,你想要给client1发一个信息,怎么发?你需要一个触发,比如client1给server发一个我要数据的请求,server响应它。那还不是跟http一个鸟样,要一个请求才一个响应?
no no no, 道路已经铺成,并非要你client1发起请求,我server才会给你响应。我只要有其它的客户端给server端一个message事件作为触发,我就能给你client1推送,我还可以给全部客户端广播。这种套路是不是很眼熟,嗯,websocket聊天室就是这样子。
好了,言归正传,websocket是如何建立起来的呢?
我写了一个demo,我们来看看这个ws请求:
可以看到请求头如下:
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: Upgrade
Host: 127.0.0.1:8889
Origin: http://127.0.0.1
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: hGdbmHGP6TOBETbfDVpBaw==
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0
熟悉http请求的同学可以注意到多了几行内容:
Connection: Upgrade
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: hGdbmHGP6TOBETbfDVpBaw==
Sec-WebSocket-Version: 13
Upgrade: websocket
Connection: Upgrade和Upgrade: websocket就是重点了,告诉server我要的是一个升级版连接,这个升级版连接就是websocket。
Sec-WebSocket-Key 是一个Base64 encode的值,这个是浏览器随机生成的,用于验证,另外一个是Extensions 所用的扩展,还有Version即版本。
然后我们来看看response:
Connection: Upgrade
Sec-WebSocket-Accept: VR0OMQ/dhxkaZ/GcdrN3K+74JUk=
Sec-WebSocket-Version: 13
Upgrade: websocket
没什么特别的内容,确认了这个请求已经是websocket连接。
接下来就可以用websocket传输数据了:
PHP实践
编程之前,先说一下websocket对象的基本事件:
然后php我用的是Hack book of Hoa\Websocket(点击可查看英文文档,好像只有英文的,我的渣渣英文水平看点文档还是勉强够用的)。
github给出了快速用法
- 1.安装一下:
composer require hoa/websocket '~3.0'
- 2.然后用github上的server端测试代码,文件名为server.php:
$websocket = new Hoa\Websocket\Server(
new Hoa\Socket\Server('ws://127.0.0.1:8889')
);
$websocket->on('open', function (Hoa\Event\Bucket $bucket) {
echo 'new connection', "\n";
return;
});
$websocket->on('message', function (Hoa\Event\Bucket $bucket) {
$data = $bucket->getData();
echo '> message ', $data['message'], "\n";
$bucket->getSource()->send($data['message']);
echo '< echo', "\n";
return;
});
$websocket->on('close', function (Hoa\Event\Bucket $bucket) {
echo 'connection closed', "\n";
return;
});
$websocket->run();
-
- cli模式运行此进程。
-
- 客户端js
connection is closed
-
效果
客户端:
服务端:
完。