WebSocket服务端开发(五)-WebSocketServer类帧解析整体流程介绍

本文介绍WebSocketServer类的parseFrame函数流程。

parseFrame函数:

function parseFrame($data, $socketId) {
  //判断该帧是否是经过掩码处理
  $isMasked = $this->isMasked($data[1]);

  //如果未经掩码处理,则根据协议规定,需要断开连接
  if (!$isMasked) {
    //此处使用1002状态码,表示协议错误,发送关闭帧
    $this->closeFrame($socketId, 1002, 'There is no mask!');

    //断开连接
    $this->disconnect($socketId);
    return false;
  }
  //获取负载的长度字节数
  $payloadLen = $this->getPayloadLen(substr($data, 1, 9));

  //根据负载长度获取负载的全部数据
  $payload = $this->getPayload($data, $payloadLen);

  //获取掩码值
  $mask = $this->getMask($data, $payloadLen);

  //获取帧的类型
  $frameType = $this->getFrameType($data[0]);

  //处理帧
  switch ($frameType) {
  case self::FRAME_CONTINUE:
    //后续帧,需要拼接buffer
    $this->socketListMap[$socketId]['buffer'] .= $this->parseRawFrame($payload, $mask);
    break;
  case self::FRAME_TEXT:
    //文本帧,处理方式默认保持一致,均使用parseRawFrame处理,如果由特殊需求可以重写parseTextFrame函数
    $this->socketListMap[$socketId]['buffer'] = $this->parseTextFrame($payload, $mask);
    break;
  case self::FRAME_BIN:
    //二进制帧,处理方式默认保持一致,均使用parseRawFrame处理,如果由特殊需求可以重写parseBinaryFrame函数
    $this->socketListMap[$socketId]['buffer'] = $this->parseBinaryFrame($payload, $mask);
    break;
  case self::FRAME_CLOSE:
    //发送关闭帧(应答帧)
    $this->closeFrame($socketId);
    break;
  case self::FRAME_PING:
    //发送pong帧响应,浏览器目前不提供ping、pong帧的API,此处逻辑基本不会走到,只为实现协议内容
    $this->sendPong($socketId, $this->parseRawFrame($payload, $mask));
    break;
  case self::FRAME_PONG:
    //收到pong帧不进行任何处理(正常情况下不会收到,浏览器不会主动发送pong帧)
    break;
  default:
    //其他帧类型无法处理,直接断开连接,根据协议,此处使用1003状态码关闭连接更好
    $this->disconnect($socketId);
    break;
  }
  if ($this->debug) {
    //输出调试信息
    echo "isFin:" . ((ord($data[0]) & 0x80) >> 7) . "\n";
    echo "opCode:$frameType\n";
    echo "payLoad Length:$payloadLen\n";
    echo "Mask:$mask\n\n";
  }

  //如果是结束的数据帧,返回true,否则均为false
  //当返回true时,外层调用函数会继续将执行核心业务逻辑,读取缓冲区中的数据进行处理
  //如果是false,则不进行进一步的处理(控制帧及非结束帧都不会提交到业务层处理)
  return $this->isFin($data[0]) && !$this->isControlFrame($frameType);
}

WebSocket服务端开发(四)-WebSocketServer类socket系列封装函数介绍

本文介绍WebSocketServer类中处理socket的封装函数实现。

getSocketId函数:

function getSocketId($socket) {
  //socketId由socket中的地址和端口号拼接,这样可以保证socket的唯一性,又可以通过id快速读取保存socket的信息以及其附加的其他相关信息
  if (socket_getpeername($socket, $address, $port) === FALSE) {
    return false;
  }
  return $address . '_' . $port;
}

addSocket函数:

function addSocket($socket) {
  //将socket添加到已接受的socket连接池中
  array_push($this->socketList, $socket);
  $socketId = $this->getSocketId($socket);

  //以socketId为索引建立socket映射表,便于后续快速处理socket相关信息
  $this->socketListMap[$socketId] = array(
    //读取缓冲区,由于可能存在分帧的情况,此处统一先保存到缓冲区中,带收到结束帧后统一处理缓冲区
    'buffer' => '',
    //握手成功标识,addSocket在接受连接后调用,故此时并未进行握手,初始化为false
    'handshake' => false,
    //最后通信时间,用于判断超时断开操作
    'lastCommuicate' => time(),
    //socket实例
    'socket' => $socket,
    //错误计数
    'errorCnt' => 0
  );
}

removeSocket函数:

function removeSocket($socketId) {
  $socket = $this->socketListMap[$socketId]['socket'];

  //找出socket在socketList中的索引
  $socketIndex = array_search($socket, $this->socketList);
  if ($this->debug) {
    echo "RemoveSocket at $socketIndex\n";
  }

  //移除socketList中的socket
  array_splice($this->socketList, $socketIndex, 1);

  //移除socketListMap中的相关信息
  unset($this->socketListMap[$socketId]);

  //回调事件
  $this->onAfterRemoveSocket($socketId);
}

socketAccept函数:

function socketAccept() {
  //接受socket
  $socket = socket_accept($this->serverSocket);
  if ($socket !== FALSE) {
    return $socket;
  } else if ($this->debug) {
    echo $this->getLastErrMsg();
  }
}

socketRecv函数:

//从指定socket中读取数据
function socketRecv($socketId) {
  $socket = $this->socketListMap[$socketId]['socket'];
  $bufferLen = socket_get_option($socket, SOL_SOCKET, SO_RCVBUF);
  $recv = socket_recv($socket, $buffer, $bufferLen, 0);
  if ($recv === FALSE) {
    $errCode = $this->getLastErrCode($socketId);
    $this->onerror($errCode, $socketId);
    if ($this->debug) {
      echo $this->getLastErrMsg(null, $errCode);
    }
    return NULL;
  } else if ($recv > 0) {
    if ($this->debug) {
      echo "Recv:\n";
      $this->showData($buffer);
    }
    $this->socketListMap[$socketId]['lastCommuicate'] = time();
  }
  return $buffer;
}

socketSend函数:

function socketSend($socketId, $data) {
  $socket = $this->socketListMap[$socketId]['socket'];
  if ($this->debug) {
    echo "Send:\n";
    $this->showData($data);
  }
  if (socket_write($socket, $data, strlen($data)) > 0) {
    $this->socketListMap[$socketId]['lastCommuicate'] = time();
  }
}

socketClose函数:

function socketClose($socketId) {
  $socket = $this->socketListMap[$socketId]['socket'];
  socket_close($socket);
}

connect函数:

function connect($socket) {
  $this->addSocket($socket);
  $this->onconnected($socket);
}

disconnectBySocket函数:

//此函数通过遍历查找socket信息,性能较低,不建议使用,通常使用disconnect直接根据socketId断开连接
//此函数通常用于一些异常socket的处理
function disconnectBySocket($socket) {
  $socketIndex = array_search($socket, $this->socketList);
  foreach ($this->socketListMap as $socketId => $session) {
    if ($session['socket'] == $socket) {
      $this->disconnect($socketId);
      return;
    }
  }
}

disconnect函数:

function disconnect($socketId, $silent = false) {
  $this->socketClose($socketId);
  if (!$silent) {
    $this->ondisconnected($socketId);
  }
  $this->removeSocket($socketId);
}