NHN Cloud Meetup 編集部
TOAST Hasteの紹介
2016.12.26
322
はじめに
私たちは長い間、TCP(Transmission Control Protocol)を使って多くのアプリケーションを開発してきました。TCPはパケットの順序保証、フロー制御、混雑制御などの多くの役割を担い、開発者がより抽象的なレベルでネットワークを眺められるようにサポートします。これにより、開発者は端末間のネットワークをバイトストリームとみなして、簡単に開発することができます。
しかし、特定の分野(例えば、リアルタイム性が必要なもの)では、TCPの作業が負荷になって、パフォーマンスを発揮できないことがあります。最も代表的なものとして、Head of Line Blocking(以下HOL Blocking)がそうです。
では、TCPの代わりにUDPを使えば、どうでしょうか。UDPはベストエフォートで動作し、端末のアプリケーション間のポートを区別する役割を持ちます。見方を変えると、UDPは真っ白な画用紙のようなもので、その上にいくらでも機能を追加できます。実際にGoogleでも、ほとんどのサービスにUDP基盤のQUICプロトコルをオプションで使用しています。
UDPについては、UDPを使用するときに考慮すべきことが参考になるでしょう。
ゲームはどうしょうか?韓国のマルチプレイゲームはほとんどTCPを使って開発されています。先に説明したように、TCPはとても良いトランスポートプロトコルです。しかしUDPを使うと、マルチプレキシング、Wi-Fi Cellular handoverなどの点でメリットがあります。このようなメリットを得るために、UDPを使ってゲームを開発していくと、多くの課題に直面するでしょう。課題を解決するうちに、コンテンツ開発よりも、ネットワーク層の開発に多くの時間を費やすことになり、スケジュールに追われて、結局TCPに戻ることになります。UDPを使って早くゲームサーバーを開発することは不可能なのでしょうか。
私たちも同じような問題について、試行錯誤をしてきました。ネットワーク層の開発は毎回はできないので、再利用できるようにしておけば、次回はさらに簡単に作成できるのではないかと考えました。そして、そのように作られたフレームワークがTOAST Hasteです。
TOAST Haste
TOAST Hasteは非同期ゲームサーバーのフレームワークです。TOAST Hasteを使って開発すると、以下のようなメリットがあります。
- 様々なQoSを提供
- Multiplexing
- Wi-Fi Cellular handover
- オプションの暗号化(using Diffie-Hellman algorithm, AES)
1つずつ詳しく見ていきましょう。
TOAST Hasteのソースは、https://github.com/nhnent/toast-haste.frameworkから確認できます。
TOAST Haste .NET SDKは、https://github.com/nhnent/toast-haste.sdk.dotnetから確認できます。
様々なQoSを提供
UDPは基本的にベストエフォートで動作します。つまり、ポートでアプリケーションを分離する他は何もしません。したがって、パケットの順序保証、再送信を実装する必要があります。TOAST Hasteは、パケットの順序保証と再送信に対する実装がなされており、下記のようなQoSを提供しています。
- Reliable sequenced:パケット順序を保証し、失効時に再送信を行う。
- Unreliable sequenced:損失時の再送信は保証しないが、最新のパケット順序を保証する。パケット損失時の再送信は必要ないが、最新のデータが有効でなければならない場合に使用する。(例: プレイヤーの位置情報)
- Reliable fragmented:IP階層の分断化を防ぐために、あらかじめMTUサイズに切り取って送受信する。大規模なデータ送受信に役立てられる。
TOAST HasteのQoS実装は、ENet(MIT License)とTCP実装の一部をもとに開発されました。
Multiplexing
TOAST Hasteは、論理的なチャネルを指定して送受信できるように設計されました。チャネル別に処理が実行されるので、データを役割に応じて適切にチャネル別に分けて送受信できます。適切なチャネル分散は、HOL Blockingの問題を最小限に抑えることができます。
Reliable sequenced(あるいはReliable fragmented)QoSで送受信する場合には、パケット損失時、チャネルごとにHOL Blockingの問題が発生します。
したがって、ドメイン別に適切なチャネル分配が必要です。
Wi-Fi Cellular handover
TCPの場合、連結基盤であるため、Source IP、Source Port、Destination IP、Destination Portの4つの情報を使って1つの連結を識別します。したがって、Wi-FiとCellular移動時、ネットワークが変更されてIPが変わると、新しい接続を試みる必要があります。TOAST Hasteは、独自の識別番号で接続を管理しているので、IPが変更されても再接続なく、接続が維持されます。
オプションの暗号化
ゲームだけでなく、すべてのアプリケーションにおいて、重要データの暗号化は不可欠です。しかしゲームの場合は、すべてのパケットを暗号化すると、リアルタイム性を確保するのが難しく、必要に応じて暗号化を行う必要があります。TOAST Hasteは、接続が成立したとき、暗号化に対する事前情報を送受信します。その後は、ユーザーが必要に応じて暗号化を行うことができます。
参考までに、TOAST Hasteはキーの交換にはDiffie-Hellman Algorithmを、データの暗号化にはAES256を使用しています。
Let’s Haste
簡単なEchoサーバーとクライアントを作成して、TOAST Hasteの使い方を覚えましょう。
Echoサーバー
- TOAST Haste frameworkをチェックアウトすると、com.nhnent.haste.example.echoserver
パッケージのEchoServerクラスのmain関数からEchoサーバーを実行できます。
1. GameServerBootstrap
からサーバーを設定する
public class EchoServer { private static final int PORT = 5056; public static void main(String[] args) { GameServerBootstrap bootstrap = new GameServerBootstrap(); bootstrap.application(new EchoServerApplication()) .option(UDPOption.THREAD_COUNT, 2) .bind(PORT).start(); } }
2. EchoServerApplication
でClientPeer
の生成コードを追加する
ClientPeerは、クライアントが接続するときに生成されるPeerオブジェクトです。
public class EchoServerApplication extends ServerApplication { @Override protected void setup() { } @Override protected void tearDown() { } @Override protected ClientPeer createPeer(InitialRequest initialRequest, NetworkPeer networkPeer) { return new EchoPeer(initialRequest, networkPeer); } }
3.実際に送受信するデータのEchoMessage
クラスを実装する
- データはMessageBridge
を継承して実装すると、FieldParameterアノテーションを利用して簡単に実装できます。
public class EchoMessage extends MessageBridge { public static final short MESSAGE = 0; public EchoMessage(RequestMessage request) { super(request); } @FieldParameter(Code = MESSAGE) public String message; }
4.実際のクライアントとデータを送受信するEchoPeer
クラスを実装する
public class EchoPeer extends ClientPeer { private static final Logger logger = LoggerFactory.getLogger(EchoPeer.class); public EchoPeer(InitialRequest initialRequest, NetworkPeer networkPeer) { super(initialRequest, networkPeer); } @Override protected void onReceive(RequestMessage request, SendOptions options) { EchoMessage message = new EchoMessage(request); logger.info(MessageFormat.format("Client message is \"{0}\"", message.message)); ResponseMessage response = message.toResponse(); this.send(response, options); } @Override protected void onDisconnect(DisconnectReason reason, String detail) { } }
Echoクライアント
- TOAST Haste SDK for .NETとEchoクライアントの全体ソースコードは、こちらから確認できます。
1. NetworkConnection
オブジェクトと設定オブジェクトを作成する
_connection = new NetworkConnection(); _config = new ConnectionConfig { ChannelCount = 5, DisconnectionTimeout = 3000, IsCrcEnabled = false, MaxUnreliableCommands = 0, MTUSize = 1350, PingInterval = 1000, SentCountAllowance = 3, WarningQueueSize = 500, }; _connection.Configure(_config);
2. NetworkConnection
オブジェクトに応答イベントを登録する
ClientPeerは、クライアントが接続するときに生成されるPeerオブジェクトです。
_connection.ResponseReceived += OnResponseReceived; _connection.StatusChanged += OnStatusChanged; ... const int MESSAGE_CODE = 0; private static void OnResponseReceived(ResponseMessage response) { if (response.Code == MESSAGE_CODE) { string message = string.Empty; if (response.Data.GetValue(MESSAGE_PARAM_CODE, out message)) { Console.WriteLine("[OnResponseReceived] Server message is \"{0}\"", message); } } }
3.接続して応答スレッドを開始する
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5056); _connection.Connect(remoteEndPoint, new Version(0, 1, 0), null); Thread receiveThread = new Thread(() => { while (true) { _connection.NetworkUpdate(); } }); receiveThread.Start();
4.データ送信のメソッドを作成する
const int MESSAGE_PARAM_CODE = 0; public void Send(string input) { DataObject data = new DataObject(); data.SetString(MESSAGE_PARAM_CODE, input); _connection.SendRequestMessage(MESSAGE_CODE, data, SendOptions.ReliableSend); }
まとめ
TCPは現在においても優れたトランスポートプロトコルです。しかし、アプリケーションによっては、TCPのメリットが限界に達することもあります。代表的な例としてはリアルタイム性が必要なサービスで、そのうち代表的なものがゲームでしょう。
ゲームサーバーフレームワークが持つべき価値は、ネットワークがすべてではありません。TOAST Hasteは、様々なQoSのネットワークの実装だけでなく、最適なThreadモデルの選択とThread-safeなオブジェクトリサイクルなど、色々な技法を用いています。まだ道のりは長く、足りない部分が多いですが、より多く方に知識やコードを共有して向上させていきたいと考えています。
このようなことから、TOAST Hasteでは実装全体を公開しています。不便な部分や、素晴らしいアイデアがあれば、一緒に話し合いながら有用なフレームワークに発展させていきたいと思います。なおTOAST Hasteは、TOASTCloud Real-time multiplayerサービスの骨組みであり、今後もサービスで発生しているバグ修正や追加機能は、TOAST Haste
に着実に反映していく予定です。
興味のある方は、https://github.com/nhnent/toast-haste.frameworkからフレームワークのソースコードを、https://github.com/nhnent/toast-haste.sdk.dotnetから、.NET SDKのソースコードをご確認いただけます。