內容目录

上一个主题

中文分词

下一个主题

CDN

Channel

服务概述

channel是新浪云提供的实时消息推送服务。通过在浏览器和新浪云服务端之间建立长连接,使得应用可以方便的向javascript客户端实时的推送消息。

下图为channel服务的大致使用流程:

../../_images/channel-overview.png

channel服务的使用主要包含两个部分:JS客户端,服务端处理程序。

对于JS客户端,其需要完成:

  • 使用应用服务端创建的channel url连接上channel的服务器。
  • 设置下行消息的处理函数。
  • 需要发送消息时,调用send发送消息给channel服务器(或者直接使用XMLHttpRequest直接发送给应用服务端也可)。

对于应用的服务端:

  • 调用create_channel为每个客户端创建一个channel,并将channel的url返回给客户端。
  • 处理channel server发过来的客户端上行消息(如果有的话)。
  • 有消息要发送时,调用send_message来向客户端推送消息。

当JS客户端和channel服务端连接上/断开或者当JS客户端有发送消息给channel服务器时,channel服务器会使用http回调的方式通知应用。

回调地址 事件说明
/_sae/channel/connected 客户端连接上channel服务器
/_sae/channel/disconnected 客户端和channel服务器断开
/_sae/channel/message 客户端有上行消息,POST内容的message字段为JS客户端发送的内容

所有的http回调都使用POST方法。所有http回调的POST内容中的from字段为客户端对应channel的名称。其余字段(如果有)见具体回调说明。

注解

每个html页面最多可以建立1个channel连接。

API使用手册

Server端API:

点击查看

Javascript客户端API:

在html页面中使用以下代码引用Channel服务的js库。

<script type="text/javascript" src="http://channel.sinaapp.com/api.js"></script>
class sae.Channel(url)
参数:
  • url (string) – 服务端create_channel()返回的url地址
onopen

设置客户端连接上服务端时的回调函数。

onmessage

设置客户端收到消息时的回调函数。该函数接受一个参数:一个messagae对象,其中的data字段为服务端send_message接口发送的消息内容。

onerror

设置客户端和服务端连接出现错误时的回调函数。

onclose

设置客户端关闭和服务端的连接时的回调函数。

使用示例

下面我们使用 TicTacToe(井字棋) 游戏来示范Channel服务的使用方法:

完整代码:https://github.com/sinacloud/sae-channel-examples/tree/master/php

channel的创建和连接

首先,当用户A打开TicTacToe游戏的主页时,TicTacToe服务端的程序会:

  • 调用 createChannel 创建为用户A创建一个channel,并将该channel的url嵌入到返回给用户的html页面代码中。
  • 生成一个加入游戏的连接,用户通过将此连接发送给其它用户B,其它用户B可以通过此连接加入用户A创建的游戏。

每个页面对应的channel的name应该是独一无二的,比如可以使用用户id的字符串作为channel的name。

游戏的主页的html代码模板大致如下所示,其中 <?=$url ?><?=$game_link?> 分别为上面生成的channel url和游戏加入连接。

<head>
...
<script src="http://channel.sinaapp.com/api.js"></script>
</head>
<body>
  <script>
    socket = new sae.Channel('<?=$url?>');
    socket.onopen = onOpened;
    socket.onmessage = onMessage;
    socket.onerror = onError;
    socket.onclose = onClose;
  </script>

  ...

  <div id='other-player' style='display:none'>
    Waiting for another player to join.<br>
    Send them this link to play:<br>
    <div id='game-link'><a href='<?=$game_link?>'><?=$game_link?></a></div>
  </div>

</body>

游戏的js客户端使用 sae.Channel 来创建一条channel连接,并且设置channel的onopen/onmessage/onerror/onclose的callback函数。

使用channel来推送游戏状态信息

当用户B点击用户A发过来的连接打开了游戏页面时,游戏的javascript客户端通过 sendMessage 函数通知服务端。

onOpened = function() {
  connected = true;
  sendMessage('opened');
  updateBoard();
};

sendMessage = function(path, opt_param) {
  path += '?g=' + state.game_key;
  if (opt_param) {
    path += '&' + opt_param;
  }
  var xhr = new XMLHttpRequest();
  xhr.open('POST', path, true);
  xhr.send();
};

服务端更新当前游戏的状态,并且通过channel的 sendMessage 将游戏的新的状态发送给用户A和用户B的channel客户端。客户端接受到消息后更新游戏页面。此后用户A和用户B交替走棋,客户端通过 sendMessage 将用户的走法发送给服务端。

moveInSquare = function(id) {
  if (isMyMove() && state.board[id] == ' ') {
    sendMessage('/move', 'i=' + id);
  }
}

服务收到消息后更新游戏的状态,再通过 sendMessage 将更新后的状态发送给用户A和B,如此往复直到游戏结束为止。

<?php

switch ($action) {
  case 'opened':
    $game_key = $_REQUEST['g'];
    $game = Game::get_by_key_name($game_key);
    $game_updater_instance = new GameUpdater($game);
    $game_updater_instance->send_update();
    break;
  case 'move':
    $game_key = $_REQUEST['g'];
    $game = Game::get_by_key_name($game_key);
    $user = $_COOKIE['u'];
    if ($game and $user) {
      $id = $_REQUEST['i'];
      $game_updater_instance = new GameUpdater($game);
      $game_updater_instance->make_move($id,$user);
    }
    break;
  default:
    die('Illegal request');
}

class GameUpdater
{
    public $game;
    function __construct($game)
    {
      $this->game = $game;
      $this->channel = new SaeChannel();
    }

    function get_game_message()
    {
      $GameUpdater = array(
        'board' => $this->game->board,
        'userX' => $this->game->userX,
        'userO' => $this->game->userO,
        'moveX' => $this->game->moveX,
        'winner' => $this->game->winner,
        'winningBoard' => $this->game->winning_board,
        );
      return json_encode($GameUpdater);
    }

    function send_update()
    {
      $message = $this->get_game_message();
      $this->channel->sendMessage($this->game->userX.$this->game->key_name,$message);
      if ($this->game->userO) {
        $this->channel->sendMessage($this->game->userO.$this->game->key_name,$message);
      }
    }

    function check_win()
    {
      if ($this->game->moveX) {
          # O just moved, check for O wins
          $potential_winner = $this->game->userO;
          $wins = array_map(function($a){return str_replace('X','O',$a);},Wins::$x_win_patterns);
          //$wins = Wins::$o_win_patterns;
      } else {
          $potential_winner = $this->game->userX;
          $wins = Wins::$x_win_patterns;
      }
      foreach ($wins as $small) {
        if ( $this->check_regual( $this->game->board, $small) ) {
          $this->game->winner = $potential_winner;
          $this->game->winning_board = $small;
          return true;
        }
      }
      return false;
    }

    ...

    function make_move( $position, $user )
    {
      if ($user>0 and $user == $this->game->userX or $user == $this->game->userO) {
        if($this->game->moveX == ($user == $this->game->moveX)) {
          $boardList = $this->str2array($this->game->board);
          if ($boardList[$position] == ' ') {
            $boardList[$position] = $this->game->moveX ? 'X' : 'O';
            $this->game->board = implode("", $boardList);
            $this->game->moveX = !$this->game->moveX;
            $this->check_win();
            $this->game->put();
            $this->send_update();
            return true;
          }
        }
      }
      return false;
    }

    ...
}

GameUpdater类检查move的请求是否合法,如果合法则更新游戏的状态并且通知游戏双方新的游戏状态。