RTMFPはFlashPlayer10で使えるP2Pプロトコルです。RTMFPを使用することでブラウザ間で直接通信することが出来るため、スケーラブルなシステムを構築出来ます。P2Pの最大の問題はNAT越えですが、RTMFPはUDPを使用することでNATを超えられるようになっています。

Another advantage of UDP over TCP that it enables end-to-end peering―that is, direct data transmission between two clients located behind network address translators (NATs).

しかし、全てのネットワークで繋がるわけではなく、

RTMFP is built on top of UDP, which enables direct connection between clients even if they are located behind NATs or firewalls. In order for RTMFP to work, your firewall must be configured to allow outgoing UDP traffic. While this is the case with most consumer or small office/home office (SOHO) firewalls, many corporate firewalls block UDP traffic altogether.

にて述べられているように、FirewallでUDPを通さないように設定している企業やネットワークも多いのが実情です。実際にmixiアプリでRTMFPを使用してみたところ、繋がる環境は大体50%程度でした。DirectPlayで接続する場合とあまり変わらない接続率ですね。(Ruinaterraでの経験的な数値です。)

ということで、商用サービスとしてRTMFPだけというのは厳しく、何かしらトンネリングするオプションを用意しておく方が良さそうです。RTMFPを使用すればラグが少なく快適なプレイが、使用できない場合は中継サーバを経由することで繋がるけれどラグは大きい、みたいな構成ですね。

ということでさっそく昨日一日費やして、GAE上にトンネリングAPIを構築してみました。流石に、RTMFPをエミュレートするわけにはいかないので、POSTとポーリングによるメッセージパッシングをする仕組みを構築してみました。

RTMFPで接続する場合、次のようなモデルを使っています。

クライアント1->sendStream->サーバプレイヤ<-sendStream<-クライアント2
クライアント1<-recvStream<-サーバプレイヤ->recvStream->クライアント2

つまり、クライアントの情報をサーバプレイヤが集約して、全員に配信していると。このモデルでトンネリングするには、間に中継サーバを挟めばよいことになります。ここではGAEを使いました。

クライアント->POST->中継サーバ(GAE)<-GET<-サーバプレイヤ->(省略)
クライアント->GET->中継サーバ(GAE)<-POST<-サーバプレイヤ->(省略)

GAEからは、sendStreamと違って、メッセージを送りつけることは出来ないので、定期的にGETすることでポーリングすることにしました。データ構造的には次のようなものを使っています。

class Tonnering(db.Model):
id = db.StringProperty()
client_request_n = db.IntegerProperty()
client_request_1 = db.TextProperty()
client_request_2 = db.TextProperty()
client_request_3 = db.TextProperty()
client_request_4 = db.TextProperty()
client_request_5 = db.TextProperty()
client_request_6 = db.TextProperty()
client_request_7 = db.TextProperty()
client_request_8 = db.TextProperty()
client_request_9 = db.TextProperty()
client_request_10= db.TextProperty()
client_request_11= db.TextProperty()
client_request_12= db.TextProperty()
client_request_13= db.TextProperty()
client_request_14= db.TextProperty()
client_request_15= db.TextProperty()
client_request_16= db.TextProperty()
server_response = db.TextProperty()
server_date = db.IntegerProperty()

クライアントからのメッセージはclient_requestにqueueとして格納されていきます。サーバは、client_request_nを見て、処理をしていないメッセージを判別し、処理をしていないメッセージだけ処理をします。サーバからは全員の情報をマージして処理した結果がserver_responseに返ってくるのでクライアントはこれをgetします。server_dateに最後にサーバからのデータが更新された日付を格納しておくことで、サーバプレイヤがコネクションを終了したかが分かります。

そんな感じでGAE上でトンネリングが走るようになりました。今まで繋がらなかった方もぜひぜひお試し下さい。問題はサーバ負荷で、結構、CPU資源を食いそうだなぁと思っています。ということでCPU予算を少し増強しておきました。

本当はそろそろmixiアドプログラムでGAEがペイするかどうかの記事も書こうと思っていたのですが、セーブデータのGAE化とトンネリングでまた維持費が上がるのでまだ書けそうにないです。速報的には意外と割は良くて1万ユーザを超えて1PVが0.02円になったらペイしそうかなぁという感じです。このあたりはもう少し安定してきたらまとめます。