Azureに基づいたゲームサーバーの作成

Windows RT用に1つのゲームを実装するプロセスでは、2人のプレーヤー用にマルチプレーヤーを作成する必要がありました。 同時に、WinRTとWindows Phone 7.5の間のクロスプラットフォームゲームのサポートが必要でした。 このような機会を提供するサービスが見つからなかったため、単純なサーバーを作成することにしました。このサーバーは、あるクライアントから別のクライアントにリアルタイムでメッセージを単純に転送します。 私はAzureアカウントしか持っていないので、そのための実装を行うことにしました。 同時に、Azureは、容易なスケーラビリティ、優れた管理コンソール(新しいインターフェイス)、および開発を促進する多くのサービスを提供します。 さて、Azureの下での開発の主な機能は、C#とVisual Studio 2012で開発できることです。



猫の下で、クラウドでのサーバーの開発と展開の説明。



そこで、2人でゲームを提供するゲームサーバーを作成します。 サーバーは、Worker Roleのデュプレックスチャネルを備えたテクノロジWCFに基づいて構築されています。 サーバーは、異なるOS間のリアルタイムゲームを提供します。



ワーカーロールの作成


Visual Studio 2012で新しいプロジェクトを作成します。

画像



働く役割を選択してください:



画像



作業ロールのプロパティで、2つのエンドポイントを作成します。



画像



インターフェイス用の1つのエンドポイントとメタデータ用の1つのエンドポイント。 クライアントコードを自動的に生成するには、メタデータのエンドポイントが必要です。



この役割は1つのコピーで機能します。 一般に、少なくとも2つのコピーをお勧めします。 これは、最小限のロール復元力と負荷分散を確保するために必要です。 1つの役割が該当する場合、2番目の役割は要求を受け入れます。 ただし、2つのロールの場合、それらの間で同期を行う必要があります。 このプロセスについては、次の記事で説明します。



次に、WCFサービスを開始するコードをWorkerRole.csファイルに記述します。



private void StartGameService(int retries) { if (retries == 0) { RoleEnvironment.RequestRecycle(); return; } Trace.TraceInformation("Starting game service host..."); _serviceHost = new ServiceHost(typeof(GameService)); _serviceHost.Faulted += (sender, e) => { Trace.TraceError("Host fault occured. Aborting and restarting the host. Retry count: {0}", retries); _serviceHost.Abort(); StartGameService(--retries); }; var binding = new NetTcpBinding(SecurityMode.None); RoleInstanceEndpoint externalEndPoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["GameServer"]; RoleInstanceEndpoint mexpEndPoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["mexport"]; var metadatabehavior = new ServiceMetadataBehavior(); _serviceHost.Description.Behaviors.Add(metadatabehavior); Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding(); string mexendpointurl = string.Format("net.tcp://{0}/GameServerMetadata", mexpEndPoint.IPEndpoint); _serviceHost.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, mexendpointurl); _serviceHost.AddServiceEndpoint( typeof(IGameService), binding, string.Format("net.tcp://{0}/GameServer", externalEndPoint.IPEndpoint)); try { _serviceHost.Open(); Trace.TraceInformation("Game service host started successfully."); } catch (TimeoutException timeoutException) { Trace.TraceError( "The service operation timed out. {0}", timeoutException.Message); } catch (CommunicationException communicationException) { Trace.TraceError( "Could not start game service host. {0}", communicationException.Message); } }
      
      







このメソッドでは、クライアントコードを自動的に生成するために、サービス自体とメタデータの2つのエンドポイントを作成します。 サービスは指定された回数の開始を試みます。



Run()メソッドで、サービスの起動を記述します。



 public override void Run() { //     .    . Trace.WriteLine("GameServerWorkRole entry point called", "Information"); StartGameService(3); while (true) { Thread.Sleep(10000); Trace.WriteLine("Working", "Information"); } }
      
      





サーバーサービス


すべてのコードを提供するわけではありません、なぜなら 記事のために膨大な。 (ソースコードはこちらからダウンロードできます)概念を説明するコードのセクションのみを提供します。



ユーザーマネージャーとゲームマネージャーの2つのマネージャーがあり、それぞれ現在のユーザーセッションと現在のゲームセッションに関する情報を保存します。 データは静的ディクショナリに格納されます。キーは一意のユーザー/ゲーム識別子です。 マネージャーは、スレッドセーフな辞書アクセスも提供します。



サーバーはモバイルプラットフォーム用のゲーム用に開発されているため、サーバーとの通信が絶えず失われ、セッションが終了することに留意する必要があります。 この問題を解決するために、30秒の頻度でクライアントでRegister()サーバーメソッドを呼び出します。



サーバーコードでメソッドを登録します。



 public ClientInformation Register(string uid, string userName) { // retrieve session information string roleId = RoleEnvironment.CurrentRoleInstance.Id; string sessionId = uid; var callback = OperationContext.Current.GetCallbackChannel<IClientNotification>(); var game = GameManager.GetCurrentGamesForPlayer(sessionId); if (game != null && !game.IsActive) { game.IsActive = true; NotifyConnectedClientsGame(game); } SessionInformation session; if (SessionManager.CreateOrUpdateSession(sessionId, userName, roleId, callback, out session)) { // ensure that the session is killed when channel is closed OperationContext.Current.Channel.Closed += (sender, e) => { session.IsActive = false; game = GameManager.GetCurrentGamesForPlayer(sessionId); if (game != null && game.IsActive) { game.IsActive = false; NotifyConnectedClientsGame(game); } Trace.TraceInformation("Session '{0}' by user '{1}' has been closed in role '{2}'.", sessionId, userName, roleId); }; Trace.TraceInformation("Session '{0}' by user '{1}' has been opened in role '{2}'.", sessionId, userName, roleId); } return new ClientInformation { SessionId = sessionId, UserName = userName, RoleId = roleId }; }
      
      







当初は、OperationContext.CurrentからsessionIdを取得する予定でしたが、 通信は絶えず中断され、中断後のクライアントの呼び出しごとに新しいセッションが作成されます。 新しいセッションを作成する必要はなく、既存のセッションを更新する必要があります。 これを行うには、一意のユーザー識別子がRegisterメソッドで受信され、これはGuid.NewGuid()。ToString()関数によってクライアントで受信されます。



接続が失われた場合、ゲームとプレーヤーのセッションは非アクティブとしてマークされます。 ユーザーがゲームに戻るかどうかがわからないため、これが必要です。 Registerが呼び出されると、現在のゲームを取得し、アクティブとしてマークします(つまり、コミュニケーションが中断され、プレーヤーがゲームに戻ったことが理解されます)。



60秒ごとに、非アクティブなセッションとゲームを切断するためのハンドラーがサーバーで呼び出されます。



 imer timer; TimeSpan TimeForDelete; public GameService() { TimeForDelete = new TimeSpan(0, 0, 60); timer = new Timer(timerCallback,null,60000,60000); } private void timerCallback(object state) { var sesions = SessionManager.GetNotAcitiveSessions(); var sesionForDelete = sesions.Where(x => DateTime.Now.Subtract(x.LastSyncTime) > TimeForDelete).Select(x=>x.SessionId); foreach (var sessionId in sesionForDelete) { SessionManager.RemoveSession(sessionId); DeleteGame(sessionId); } }
      
      







したがって、クライアントが60秒以内にセッションを更新しなかった場合、クライアントは戻らず、セッションとゲームは削除されたと見なされます。 同時に、このゲームに接続しているユーザーには、ゲームが利用できなくなったという通知が送信されます。



MakeTurn()メソッドは、現在のゲームのすべてのユーザーにメッセージを送信します。



 public void MakeTurn(string uid, string type, string data) { var game = GameManager.GetCurrentGamesForPlayer(uid); if (game != null) { foreach (var player in game.Players) { if (player.SessionId != uid) { var playerSession = SessionManager.GetSession(player.SessionId); if (playerSession.Callback != null) { try { playerSession.Callback.DeliverGameMessage(type, data); } catch { } } } } } }
      
      







エラーによるサーバーのクラッシュは許可されないため、すべての重要なコードをtry / catchでフレーム化します。

メッセージは文字列なので、サーバーコードを変更せずに任意のゲームを開発できます。



ゲームの識別子は、ゲームを作成したユーザーの識別子です。 したがって、ゲームを作成したユーザーがゲームを作成した場合、そのゲームはリストから削除されます。 ユーザーがゲームに接続している場合、彼は単にゲームのユーザーのリストから削除されます。



 public void DeleteGame(string uid) { var deletingGame = GameManager.GetGame(uid); if (deletingGame != null) { GameManager.RemoveGame(uid); NotifyConnectedClientsGame(deletingGame); } else { deletingGame = GameManager.GetCurrentGamesForPlayer(uid); if (deletingGame != null) { try { deletingGame.Players.RemoveAt(1); NotifyConnectedClientsGame(deletingGame); } catch { } } } }
      
      







ローカルデバッグワーカーロール


ローカルデバッグを行うには、管理者としてVisual Studioを実行し、プロジェクトを実行する必要があります。 これにより、Windows Azureのデバッグ環境が開始されます。 (エミュレータウィンドウを開くには、トレイのエミュレータアイコンをクリックします)



画像



このウィンドウには、各インスタンスのメッセージコンソールが表示されます。

テストクライアントを作成するには、Visual Studioの新しいインスタンスを起動し、プロジェクトを作成します。

プロジェクトで、サービスへのリンクを追加します。



画像



アドレスフィールドに、エンドポイントのURLにメタデータを記述します。 (私の場合、ローカルIPは127.255.0.1でした)。 生成されたコードの名前空間を規定し、「OK」ボタンをクリックします。



クラウドでのWorkerロールの展開


ポータルからAzureをセットアップします。 ワーカーロールは、「クラウドサービス」メニューで作成および管理されます。



画像



[クラウドサービスの作成]をクリックし、最も近いデータセンターを持つ地域を選択し、ロールにアクセスするためのURLを作成します。



画像



フォームの下部にある[クラウドサービスの作成]をクリックすると、数秒後にサービスが作成されます。



次に、Visual Studioでのロールのパッケージを準備する必要があります。 これを行うには、ロールのコンテキストメニューを呼び出し、「パッケージ」を選択します。



画像



サービスの構成を指定できる新しいウィンドウが開きます(この場合、構成は同じです)。



画像



[パック]をクリックすると、数秒後にWindowsエクスプローラーで2つの* .cspkgファイルと* .cscfgファイルが含まれたフォルダーが開きます。



ポータルに戻り、「サービス」に移動して、「新しい本番展開をアップロード」をクリックします。



画像



パッケージのファイルを選択する必要があるウィンドウが開きます。



画像



ロールにインスタンスが1つしかない場合(この場合のように)、[1つ以上のロールに1つのインスタンスが含まれる場合でもデプロイする]チェックボックスをオンにする必要があります。



しばらくすると、役割が展開されて調整されます。



これで、net.tcp://mytestgameserver.cloudapp.net:8001 / GameServerMetadataを行から指定することで、クライアントにサービスを追加できます。



重要な ロールはループでスピンするため、クラウドのプロセッサー時間を使用します。 サービスを使用していない場合は、ロールを無効にして、プロセッサ時間の使用に対して支払わないようにします。



Windows RTでクライアントを作成する


記事の冒頭にあるリンクからクライアントの例をダウンロードできます。 この例は、ゲームサーバーを介した2つのクライアントの相互作用を示すことのみを目的としているため、ゲームロジック、チェックなどはありません。



Reference.csファイルのURLを次のように変更します。

net.tcp://mytestgameserver.cloudapp.net:3030 / GameServer

なぜなら コードの自動生成後、間違ったIPが発生します。



2つのクライアントの相互作用をテストするには、以下を行う必要があります。

1.「ローカルコンピューター」に1つのアプリケーションを展開する

2.「シミュレータ」で2番目のアプリケーションを実行します。



同じコンピューター上で同じアプリケーションの複数のインスタンスをWinRTで実行する方法は見つかりませんでした。 シミュレータでは、起動時に実行中のサービスのチェックが行われ、実行中のサービスの場合、2番目のインスタンスは開始されません。 これにより、サーバーのデバッグが複雑になり、3つ以上のクライアントでサーバーの検証を達成することが難しくなります。



まとめ


その結果、簡単なゲームサーバーが作成され、その作成には数時間しかかかりませんでした。 もちろん、サーバーをさらに開発する必要があり、新しい機能を実現する必要がありますが、その基本的なタスクにうまく対応し、インターネットを介して2人にゲームを提供します。



All Articles