php websocket实践

2019-06-09  本文已影响0人  10xjzheng

前言

按理说,作为一个电子信息工程毕业的毕业生,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之前,大家是如何获取服务端最新的数据呢。

  1. Ajax轮询,想必大家都用过的,没什么好说的,就是定时请求罢了。


    image.png
  2. long poll,这种虽然减少了多次请求的消耗,但却阻塞了连接。


    image.png

    作为对比,websocket是这样的:


    image.png
    上面三个图来源于网络,侵删。

也就是说,无论是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请求:


image.png

可以看到请求头如下:

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传输数据了:


image.png

PHP实践

编程之前,先说一下websocket对象的基本事件:


image.png

然后php我用的是Hack book of Hoa\Websocket(点击可查看英文文档,好像只有英文的,我的渣渣英文水平看点文档还是勉强够用的)。

github给出了快速用法

composer require hoa/websocket '~3.0'
$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();
<input type="text" id="input" placeholder="Message…" />
<hr />
<pre id="output"></pre>

<script>
  var host   = 'ws://127.0.0.1:8889';
  var socket = null;
  var input  = document.getElementById('input');
  var output = document.getElementById('output');
  var print  = function (message) {
      var samp       = document.createElement('samp');
      samp.innerHTML = message + '\n';
      output.appendChild(samp);

      return;
  };

  input.addEventListener('keyup', function (evt) {
      if (13 === evt.keyCode) {
          var msg = input.value;

          if (!msg) {
              return;
          }

          try {
              socket.send(msg);
              input.value = '';
              input.focus();
          } catch (e) {
              console.log(e);
          }

          return;
      }
  });

  try {
      socket = new WebSocket(host);
      socket.onopen = function () {
          print('connection is opened');
          input.focus();

          return;
      };
      socket.onmessage = function (msg) {
          print(msg.data);

          return;
      };
      socket.onclose = function () {
          print('connection is closed');

          return;
      };
  } catch (e) {
      console.log(e);
  }
</script>

完。

上一篇下一篇

猜你喜欢

热点阅读