WebSocket服务端开发(七)-WebSocketServer类健康检查函数

本文介绍WebSocketServer类健康检查函数。

函数实现如下:

  function healthCheck() {
    //获取当前时间
    $now = time();

    //记录最后健康检查时间
    $this->lastHealthCheck = $now;

    //初始化不健康的连接列表
    $unhealthyList = array();

    //循环连接池
    foreach ($this->socketListMap as $socketId => $session) {
      //找出最后通信时间超过超时时间(目前超时时间与健康检查时间相同)
      if ($now - $session['lastCommuicate'] > $this->healthCheckInterval) {
        array_push($unhealthyList, $socketId);
      }
    }
    if ($this->debug) {
      echo 'Unhealthy socket:' . implode(',', $unhealthyList) . "\n";
    }

    //健康检查回调,默认的行为是直接断开连接
    //可以根据自己的需求改进,如发送ping帧探测等,如果仍无响应再断开连接
    $this->onafterhealthcheck($unhealthyList);
  }

注意:由于该类只是简单的实现了WebSocket协议,没有使用多线程处理,故健康检查只有在处理完一次连接后才有可能执行。如果设置健康检查时间间隔为10分钟,10分钟内收到任何数据,那么也不会进行健康检查,直到收到一个连接后才会进行健康检查。
收发数据都会更新最后通信时间的值,如果希望只在服务器收到信息时更新最后通信时间,可以在recv和send函数中修改。
建议定时从服务器发送心跳包以维持连接。

WebSocket服务端开发(六)-WebSocketServer类帧解析相关函数介绍

本文介绍WebSocketServer类解析帧相关的函数。

isMasked函数:

  //判断是否是经过掩码处理的帧
  function isMasked($byte) {
    return (ord($byte) & 0x80) > 0;
  }

getPayloadLen函数:

  //获取负载的长度
  //首字节如果小于126,则长度为首字节的值
  //首字节如果等于126,则长度为后面紧跟的两个字节表示的字节数
  //如果首字节是127,则长度为后面紧跟8字节表示的字节数
  function getPayloadLen($data) {
    $first = ord($data[0]) & 0x7F;
    $second = (ord($data[1]) << 8) + ord($data[2]);
    $third = (ord($data[3]) << 40) + (ord($data[4]) << 32) + (ord($data[5]) << 24) + (ord($data[6]) << 16) + (ord($data[7]) << 8) + ord($data[8]);
    if ($first < 126) {
      return $first;
    } else if ($first === 126) {
      return $second;
    } else {
      return ($second << 48) + $third;
    }
  }

getPayLoad函数:

  //获取负载的内容,根据帧的结构
  //第0字节为结束标记及帧类型
  //第1字节是否掩码标识位及负载长度的首字节
  //根据协议要求,浏览器发送的数据必须经过掩码处理,所以偏移量至少是1字节的帧首字节+4字节的掩码长度
  //根据帧长度的不同,表示长度的字节数可能为1、3或9
  //所以根据不同的情况截取不同长度的数据即为负载内容
  function getPayload($data, $len) {
    $offset = 5;
    if ($len < 126) {
      return substr($data, $offset + 1, $len);
    } else if ($len < 65536) {
      return substr($data, $offset + 3, $len);
    } else {
      return substr($data, $offset + 9, $len);
    }
  }

getMask函数:

  //获取掩码的值
  //仍然是根据不同的偏移位置截取
  function getMask($data, $len) {
    $offset = 1;
    if ($len < 126) {
      return substr($data, $offset + 1, 4);
    } else if ($len < 65536) {
      return substr($data, $offset + 3, 4);
    } else {
      return substr($data, $offset + 9, 4);
    }
  }

getFrameType函数:

  //获取帧类型
  function getFrameType($byte) {
    return ord($byte) & 0x0F;
  }

isFin函数:

  //判断是否为结束帧
  function isFin($byte) {
    return (ord($byte[0]) & 0x80) > 0;
  }

isControlFrame函数:

  //判断是否为控制帧,控制帧包含关闭帧、PING帧和PONG帧
  function isControlFrame($frameType) {
    return $frameType === self::FRAME_CLOSE || $frameType === self::FRAME_PING || $frameType === self::FRAME_PONG;
  }

parseBinaryFrame、parseTextFrame、parseRawFrame函数:

  //处理负载的掩码,将其还原
  function parseRawFrame($payload, $mask) {
    $payloadLen = strlen($payload);
    $dest = '';
    $maskArr = array();
    for ($i = 0; $i < 4; $i++) {
      $maskArr[$i] = ord($mask[$i]);
    }
    for ($i = 0; $i < $payloadLen; $i++) {
      $dest .= chr(ord($payload[$i]) ^ $maskArr[$i % 4]);
    }
    return $dest;
  }

  function parseTextFrame($payload, $mask) {
    return $this->parseRawFrame($payload, $mask);
  }

  function parseBinaryFrame($payload, $mask) {
    return $this->parseRawFrame($payload, $mask);
  }

closeFrame函数:

  //创建并发送关闭帧
  function closeFrame($socketId, $closeCode = 1000, $closeMsg = 'goodbye') {
    $closeCode = chr(intval($closeCode / 256)) . chr($closeCode % 256);
    $frame = $this->createFrame($closeCode . $closeMsg, self::FRAME_CLOSE);
    $this->socketSend($socketId, $frame);
    $this->disconnect($socketId);
  }

sendPing、sendPong函数:

  function sendPing($socketId, $data = 'ping') {
    $frame = $this->createFrame($data, self::FRAME_PING);
    $this->socketSend($socketId, $frame);
  }

  function sendPong($socketId, $data = 'pong') {
    $frame = $this->createFrame($data, self::FRAME_PONG);
    $this->socketSend($socketId, $frame);
  }

createFrame函数:

  //封装帧头的相关标识位、长度等信息
  function createFrame($data, $type, $fin = 0x01) {
    $dataLen = strlen($data);
    $frame = chr(($fin << 7) + $type);
    if ($dataLen < 126) {
      $frame .= chr($dataLen);
    } else if ($dataLen < 65536) {
      $frame .= chr(126);
      $frame .= chr(intval($dataLen / 256));
      $frame .= chr(intval($dataLen % 256));
    } else {
      $frame .= chr(127);
      $hexLen = str_pad(base_convert($dataLen, 10, 16), 16, '0', STR_PAD_LEFT);
      for ($i = 0; $i < 15; $i += 2) {
        $frame .= chr((intval($hexLen[$i], 16) << 8) + intval($hexLen[$i + 1], 16));
      }
    }
    $frame .= $data;
    return $frame;
  }