websocket初探

背景:

  前段时间有个小需求:进入页面后需要根据数据库状态来自行跳转。去查了查,大部分都是用轮询做。自己觉得其实轮询也不是不可以,但是总觉比较尴尬。

因为本身的模式应该是以状态为主体,页面部分为客体,当状态发生变化的时候,主动把信号传给页面,然后页面跟着做跳转。如果用轮询,就变成了以页面为

主体,状态为客体,页面不断的给请求,如果状态变了,然后自己做跳转。然后看到了websocket,不过说实话不懂这东西。socket编程一直都有点模糊。于是

稍微看看这到底是神马。

主题:

  去github上查了一波,找到了一个小实例很适合初学。

  地址:https://github.com/ghedipunk/PHP-Websockets

  主要步骤:

  建立socket并监听

    服务器端 :

$this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Failed: socket_create()");socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("Failed: socket_option()");
socket_bind($this->master, $addr, $port)                      or die("Failed: socket_bind()");
socket_listen($this->master,20)                               or die("Failed: socket_listen()");

    客户端:

var host = "ws://127.0.0.1:9000"; // SET THIS TO YOUR SERVER
   socket = new WebSocket(host);

  websocket协议的三次握手:

    服务端

      主要是对头部信息的拆分,拿到其中的sec-websocket-key,进行拼接后加密再编码返回给客户端。(PS对这块真不太熟,具体可参见下边的链接,很详细)

      提取代码如下

protected function doHandshake($user, $buffer) {
    $magicGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    $headers = array();
    $lines = explode("\n",$buffer);
    foreach ($lines as $line) {
      if (strpos($line,":") !== false) {
        $header = explode(":",$line,2);
        $headers[strtolower(trim($header[0]))] = trim($header[1]);
      }
      elseif (stripos($line,"get ") !== false) {
        preg_match("/GET (.*) HTTP/i", $buffer, $reqResource);
        $headers[‘get‘] = trim($reqResource[1]);
      }
    }
    if (isset($headers[‘get‘])) {
      $user->requestedResource = $headers[‘get‘];
    }
    else {
      // todo: fail the connection
      $handshakeResponse = "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
    }
    if (!isset($headers[‘host‘]) || !$this->checkHost($headers[‘host‘])) {
      $handshakeResponse = "HTTP/1.1 400 Bad Request";
    }
    if (!isset($headers[‘upgrade‘]) || strtolower($headers[‘upgrade‘]) != ‘websocket‘) {
      $handshakeResponse = "HTTP/1.1 400 Bad Request";
    }
    if (!isset($headers[‘connection‘]) || strpos(strtolower($headers[‘connection‘]), ‘upgrade‘) === FALSE) {
      $handshakeResponse = "HTTP/1.1 400 Bad Request";
    }
    if (!isset($headers[‘sec-websocket-key‘])) {
      $handshakeResponse = "HTTP/1.1 400 Bad Request";
    }
    else {

    }
    if (!isset($headers[‘sec-websocket-version‘]) || strtolower($headers[‘sec-websocket-version‘]) != 13) {
      $handshakeResponse = "HTTP/1.1 426 Upgrade Required\r\nSec-WebSocketVersion: 13";
    }
    if (($this->headerOriginRequired && !isset($headers[‘origin‘]) ) || ($this->headerOriginRequired && !$this->checkOrigin($headers[‘origin‘]))) {
      $handshakeResponse = "HTTP/1.1 403 Forbidden";
    }
    if (($this->headerSecWebSocketProtocolRequired && !isset($headers[‘sec-websocket-protocol‘])) || ($this->headerSecWebSocketProtocolRequired && !$this->checkWebsocProtocol($headers[‘sec-websocket-protocol‘]))) {
      $handshakeResponse = "HTTP/1.1 400 Bad Request";
    }
    if (($this->headerSecWebSocketExtensionsRequired && !isset($headers[‘sec-websocket-extensions‘])) || ($this->headerSecWebSocketExtensionsRequired && !$this->checkWebsocExtensions($headers[‘sec-websocket-extensions‘]))) {
      $handshakeResponse = "HTTP/1.1 400 Bad Request";
    }

    // Done verifying the _required_ headers and optionally required headers.

    if (isset($handshakeResponse)) {
      socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
      $this->disconnect($user->socket);
      return;
    }

    $user->headers = $headers;
    $user->handshake = $buffer;

    $webSocketKeyHash = sha1($headers[‘sec-websocket-key‘] . $magicGUID);

    $rawToken = "";
    for ($i = 0; $i < 20; $i++) {
      $rawToken .= chr(hexdec(substr($webSocketKeyHash,$i*2, 2)));
    }
    $handshakeToken = base64_encode($rawToken) . "\r\n";

    $subProtocol = (isset($headers[‘sec-websocket-protocol‘])) ? $this->processProtocol($headers[‘sec-websocket-protocol‘]) : "";
    $extensions = (isset($headers[‘sec-websocket-extensions‘])) ? $this->processExtensions($headers[‘sec-websocket-extensions‘]) : "";

    $handshakeResponse = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $handshakeToken$subProtocol$extensions\r\n";
    socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
    $this->connected($user);
  }

    客户端  

socket.onopen    = function(msg) { }

  接受和发送信息:

    服务器端

      在进行通信之前要对数据帧解码/编码才能获取/发送客户端信息。

      握手过程代码都集中在 function doHandshake ()中。

      数据侦编码代码如下:

/*数据帧编码

@param string 原始信息
@param obj 用户对象
@param string 消息类型
@param boolean 是否为附加数据
return string 经过编码的数据
*/
protected function frame($message, $user, $messageType=‘text‘, $messageContinues=false) {
    switch ($messageType) {
      case ‘continuous‘:              //附加数据帧
        $b1 = 0;
        break;
      case ‘text‘:                         //文本数据帧
        $b1 = ($user->sendingContinuous) ? 0 : 1;
        break;
      case ‘binary‘:                     //二进制数据帧
        $b1 = ($user->sendingContinuous) ? 0 : 2;
        break;
      case ‘close‘:                      //连接关闭
        $b1 = 8;
        break;
      case ‘ping‘:
        $b1 = 9;
        break;
      case ‘pong‘:
        $b1 = 10;
        break;
    }
    if ($messageContinues) {
      $user->sendingContinuous = true;
    }
    else {
      $b1 += 128;
      $user->sendingContinuous = false;
    }

    $length = strlen($message);
    $lengthField = "";
    if ($length < 126) {
      $b2 = $length;
    }
    elseif ($length < 65536) {
      $b2 = 126;
      $hexLength = dechex($length);
      //$this->stdout("Hex Length: $hexLength");
      if (strlen($hexLength)%2 == 1) {
        $hexLength = ‘0‘ . $hexLength;
      }
      $n = strlen($hexLength) - 2;

      for ($i = $n; $i >= 0; $i=$i-2) {
        $lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
      }
      while (strlen($lengthField) < 2) {
        $lengthField = chr(0) . $lengthField;
      }
    }
    else {
      $b2 = 127;
      $hexLength = dechex($length);
      if (strlen($hexLength)%2 == 1) {
        $hexLength = ‘0‘ . $hexLength;
      }
      $n = strlen($hexLength) - 2;

      for ($i = $n; $i >= 0; $i=$i-2) {
        $lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
      }
      while (strlen($lengthField) < 8) {
        $lengthField = chr(0) . $lengthField;
      }
    }

    return chr($b1) . chr($b2) . $lengthField . $message;
  }                

      数据帧解码如下:

/*数据帧解码
@param string 原始数据帧
@param obj 用户实体

return string 用户端返回的数据
*/
 protected function deframe($message, &$user) {
    //echo $this->strtohex($message);
    $headers = $this->extractHeaders($message);
    $pongReply = false;
    $willClose = false;
    switch($headers[‘opcode‘]) {
      case 0:
      case 1:
      case 2:
        break;
      case 8:
        // todo: close the connection
        $user->hasSentClose = true;
        return "";
      case 9:
        $pongReply = true;
      case 10:
        break;
      default:
        //$this->disconnect($user); // todo: fail connection
        $willClose = true;
        break;
    }

    /* Deal by split_packet() as now deframe() do only one frame at a time.
    if ($user->handlingPartialPacket) {
      $message = $user->partialBuffer . $message;
      $user->handlingPartialPacket = false;
      return $this->deframe($message, $user);
    }
    */

    if ($this->checkRSVBits($headers,$user)) {
      return false;
    }

    if ($willClose) {
      // todo: fail the connection
      return false;
    }

    $payload = $user->partialMessage . $this->extractPayload($message,$headers);

    if ($pongReply) {
      $reply = $this->frame($payload,$user,‘pong‘);
      socket_write($user->socket,$reply,strlen($reply));
      return false;
    }
    if ($headers[‘length‘] > strlen($this->applyMask($headers,$payload))) {
        $user->handlingPartialPacket = true;
        $user->partialBuffer = $message;
        return false;
    }

    $payload = $this->applyMask($headers,$payload);

    if ($headers[‘fin‘]) {
      $user->partialMessage = "";
      return $payload;
    }
    $user->partialMessage = $payload;
    return false;
  }

  针对上述过程可以参考参考http://www.qixing318.com/article/643129914.html这篇文章,很详细。

  客户端:

    客户端相对来说简单一些,接受信息方法为 socket.onmessage,发送消息为 socket.send 方法。

总结:以原来那个需求来说,websocket是有点大材小用了,还是老老实实轮询来的方便和实际。。。。。。。

  

时间: 2024-12-06 09:07:52

websocket初探的相关文章

Spring WebSocket初探2 (Spring WebSocket入门教程)&lt;转&gt;

See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的demo出来. 接上一篇:Spring WebSocket初探1 (Spring WebSocket入门教程) WebSocket前端准备 前端我们需要用到两个js文件:sockjs.js和stomp.js SockJS:SockJS 是一个浏览器上运行的 JavaScript 库,如果浏览器不支持 W

【WebSocket初探 】

众所周知,socket是编写网络通信应用的基本技术,网络数据交换大多直接或间接通过socket进行.对于直接使用socket的client与服务端,一旦连接被建立则均可主动向对方传送数据,而对于使用更上层的HTTP/HTTPS协议的应用,因为它们是非连接协议,所以通常仅仅能由client主动向服务端发送请求才干获得服务端的响应并取得相关的数据.而当前越来越多的应用希望可以及时获取服务端提供的数据,甚至希望可以达到接近实时的数据交换(比如非常多站点提供的在线客户系统).为达到此目的,通常採用的技术

Spring WebSocket初探1 (Spring WebSocket入门教程)&lt;转&gt;

See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的demo出来. WebSocket是html5带来的一项重大的特性,使得浏览器与服务端之间真正长连接交互成为了可能,这篇文章会带领大家窥探一下Spring 对WebSocket的支持及使用. 1. 基础环境 快速搭建Spring框架,我们使用Spring boot,这里先不讨论SpringBoot,只知

初探和实现websocket心跳重连

心跳重连缘由 在使用websocket过程中,可能会出现网络断开的情况,比如信号不好,或者网络临时性关闭,这时候websocket的连接已经断开, 而浏览器不会执行websocket 的 onclose方法,我们无法知道是否断开连接,也就无法进行重连操作. 如果当前发送websocket数据到后端,一旦请求超时,onclose便会执行,这时候便可进行绑定好的重连操作. 因此websocket心跳重连就应运而生. 如何实现 在websocket实例化的时候,我们会绑定一些事件: var ws =

WebSocket集成XMPP网页即时通讯1:Java Web Project服务端/客户端Jetty9开发初探

Web 应用的信息交互过程通常是客户端通过浏览器发出一个请求,服务器端接收和审核完请求后进行处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种机制对于信息变化不是特别频繁的应用尚能相安无事,但是对于那些实时要求比较高的应用来说,比如说在线游戏.在线证券.设备监控.新闻在线播报.RSS 订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了.所以保持客户端和服务器端的信息同步是实时 Web 应用的关键要素,对 Web 开发人员来说也是一个难题.在 WebSo

初探websocket

WebSocket是 HTML5开始提供的一种在单个 TCP连接上进行全双工通讯的协议. WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据. 在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道.两者之间就直接可以数据互相传送. WebSocket协议本质上是一个基于 TCP的应用层协议. 现在,很多网站为了实现推送技术,所用的技术都是 Ajax轮询.轮询是在特定的的时间间隔(如每1秒

国外物联网平台初探(五):Exosite Murano

国外物联网平台初探(五)--Exosite Murano 马智 ? 定位 Murano是一个基于云的IoT软件平台,提供安全.可扩展的基础设施,支持端到端的生态系统,帮助客户安全.可扩展地开发.部署和管理应用.服务以及联网产品. ? 功能 Murano平台简化了整个IoT技术栈,可视为集成在一起的多个云软件层. Murano提供IoT基础设施.开发环境和功能集成,包括设备连接.产品管理.数据路由.服务集成(如data store/告警/第三方分析平台).应用开放API.用户认证/角色/权限和应用

初探oVirt-重构-Self_Hosted_Engine

日期:2015/9/25 - 2015/9/29 time 19:07 主机:n72, n73, n86, n93, vm220 目的:初探oVirt-重构-Self_Hosted_Engine 操作内容: 一.基础环境 1.本次测试环境使用4台物理机来测试:node72, node73, n86, n93 CPU:    Intel(R) Xeon(R) CPU E5-2603 v2 @ 1.80GHz 内存:   32G     硬盘:   系统盘sda + 数据盘sdb ovirt-eng

国内物联网平台初探(五):机智云IoT物联网云服务平台及智能硬件自助开发平台

国内物联网平台初探(五)——机智云IoT物联网云服务平台及智能硬件自助开发平台 马智 平台定位 机智云平台是致力于物联网.智能硬件云服务的开放平台.平台提供了从定义产品.设备端开发调试.应用开发.产测.运营管理等覆盖智能硬件接入到运营管理全生命周期服务的能力. 机智云平台为开发者提供了自助式智能硬件开发工具与开放的云端服务.通过傻瓜化的工具.不断增强的SDK与API服务能力最大限度降低了物联网硬件开发的技术门槛,降低研发成本,提升开发者的产品投产速度,帮助开发者进行硬件智能化升级,更好的连接.服