美文网首页
php websocket实践

php websocket实践

作者: 10xjzheng | 来源:发表于2019-06-09 11:34 被阅读0次

    前言

    按理说,作为一个电子信息工程毕业的毕业生,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给出了快速用法

    • 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();
    
      1. cli模式运行此进程。
      1. 客户端js
    <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>
    
    • 效果
      客户端:


      image.png

      服务端:


      image.png

    完。

    相关文章

      网友评论

          本文标题:php websocket实践

          本文链接:https://www.haomeiwen.com/subject/adxxxctx.html