sifue's blog

プログラマな二児の父の日常 ポートフォリオは www.soichiro.org

Channel APIで他のユーザーにプッシュ通信でアラートメッセージを送るサンプル

今日は久しぶりの外出がない休日だったこともあって、GoogleAppEngineのChannel APIを使ったサンプルを作ってみました。GoogleAppEngine SDK Java 1.4.0ではChannel APIが標準搭載されて、プッシュ通信を使ってネトゲーっぽいことができるようになるらしいので、ちょっと調べてみようかなと思ったのが発端です。
ちなみに1.4.0は現在prerelease中で、まだローカル環境でしか実行できませんが、githubの方にソースは上げておきました。1.4.0が正式リリースされた暁には、ちゃんとサーバー上で動くはずです。
https://github.com/sifue/channelsample


仕様は、同じURLを開いている人にプッシュ通信で突然メッセージをアラートを使って送りつけるというサンプルです。Channel APIがどういう仕組なのかほんの少しだけ見てみたいという人には役に立つかもしれません。


実際に動かしているところはこんな感じです。

このURLを見ている全ての人にアラートを送信してくれます。


Slim3を使って、
◯GAEのChannel API で対戦ゲームの開発がどの程度難しいか
http://d.hatena.ne.jp/shuji_w6e/20101113/1289651033
を参考にさせてもらいながら実装してみました。
これはすごいです。さすがに休日の少しの時間でこんなのは作れません。


一応動きを端折って紹介します。
まずはコントローラーから。
ルートにアクセスしたらチャンネルを作って、リクエストにチャンネルのトークンを入れてindex.jspにリダイレクトします。


IndexController.java

public class IndexController extends Controller {

    private Channel channel = new Channel();
    
    @Override
    public Navigation run() throws Exception {
       
        String channelId = "testchannel"; 
        String channelToken = channel.getChannel(channelId); 
        
        requestScope("channelToken", channelToken);
        
        return forward("index.jsp");
    }
}


その後index.jspでは、グローバル変数にチャンネルのトークンだけ入れて、message.jsの実装にトス。このjspは、テキストエリアとボタンがあるだけの簡単なUIです。


index.jsp

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Channel API Sample</title>
<script type="text/javascript" src="/js/channel.js"></script>
<script type="text/javascript" src="/js/jquery-1.4.3.min.js"></script>
<script type="text/javascript">
	// Global
	channelToken = "${f:h(channelToken)}";
</script>
<script type="text/javascript" src="/js/message.js"></script>
</head>
<body>
<p>Channel Token is "${f:h(channelToken)}"</p>
<p>Please input message for other user</p>
<form>
<textarea id="textarea_value"></textarea><br />
<input type="button" value="Send message to other user" onclick="myapp.postMessage()"/>
</form>
</body>
</html>


message.jsでは、チャンネルのトークンを元にソケットを開いてメッセージがやってくるのを待ち受けておきます。あと、ボタンが押された際にメッセージをサーバーに送るpostMessage()も実装しておきます。


message.js

$(document).ready(function() {

    var channel = new goog.appengine.Channel(channelToken);
    var socket = channel.open();
    
    var updateView = function(messageContent) {
    	messageContent = decodeURI(messageContent);
    	window.alert(messageContent);
	}
    
    socket.onmessage = function(msg) {
        var data = $.parseJSON(msg.data);
        console.debug("content: " + data.content);
        updateView(data.content);
      };
      
    // Global
    myapp = {
	postMessage : function(){
	$.post(
		"message",
		{content:$('#textarea_value').val()},
		function(data,status){
			console.debug(data);
			$('#textarea_value').val('');
		},	
		'text'
		)
	}
    }

});



最後はメッセージの受け取り元のMessageController.javaです。ここでは、メッセージを受け取ったら、開いておいたチャンネルを使ってメッセージを各クライアントに送ります。


MessageController.java

public class MessageController extends Controller {

    private Channel channel = new Channel();
    
    @Override
    public Navigation run() throws Exception {
        
        String content = asString("content");
        String channelId = "testchannel"; 
        channel.sendMessage(channelId, content);
        
        response.setContentType("text/plain; charset=UTF-8");
        response.getWriter().print("post is success. content: " + content);
        response.flushBuffer();
        
        return null;
    }
}



最後に、チャンネルのサービスを担うクラスChannel.javaです。メソッドはチャンネルのトークンの取得とメッセージ送信の2つだけです。チャンネルの寿命がイマイチわからず...。どうやら一つのチャンネルIDに対して複数のチャンネルトークンが発行できて、オープンされたらそれを監視しているっぽい感じがします。(詳しい方、教えてください...)

Channel.java

public class Channel {
    
   private static ChannelService cs = ChannelServiceFactory.getChannelService();

   public String getChannel(String channelId){
       return  cs.createChannel(channelId);
   }
    
   public void sendMessage(String channelID, String content) {
       
       // URIEncode
        try {
            content = URLEncoder.encode(content, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
       
       ChannelMessage cm = 
           new ChannelMessage(channelID,
               "{ \"content\":\"" + content + "\"}");
       cs.sendMessage(cm);
   }
}


と、こんな感じです。

チャンネルを繋いでいる全てのユーザーにプッシュ通信をメッセージを送りつけられるのは、リアルタイム性の必要なアプリケーションでは結構役に立ちそうです。あと、Google App Engineという非常に安価なインフラで実現できるのも有り難いところ。今後、何かいい使い方が見つかればなと思います。


あと、Channel APIの公式サンプル、Dance Dance Robotもソースを見ると結構ガッツリ書いていてちょっと大変そうでした。やっぱりゲーム作りってしっかり腰を据えないと難しいですよね。
http://dance-dance-robot.appspot.com/
http://code.google.com/p/dance-dance-robot/


追伸
appengine-java-sdk-1.4.0_prerelease.zipには残念ながらChannelAPIのJavadocが付いていなかったので、手探り実装でした。早くJavadoc見たいですね。
あと、今回はじめてgithubを使いました。Google CodeやSorceForgeの方が簡単かもしれません。Eclipse使いとMac使いにちょっとやさしくない感じでした。もうちょっとegitがよくなれば良いのですが。