首页 > 代码库 > websocket初探

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是有点大材小用了,还是老老实实轮询来的方便和实际。。。。。。。

  

 

websocket初探