この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
概要
前回の記事まではより良いチャットサーバーのソースコード解説でしたが、これからより良いチャットクライアントのソースコードを分析します。
マシン環境
CPU : Intel i7-6920HQ
GPU : AMD Radeon Pro 460
Memory : 16GB
System : Windows 10 (with .NET 5 installed)
IDE : Visual Studio 2019 Community
Editor : Visual Studio Code
Terminal : Windows Terminal (or Command Prompt)
ファイルの構造とクラスの運用
このプロジェクトはGitHubにアップロードしています: AGLW-CSharp-BetterChatClientSample
ソースコードファイル :
- Program.cs (プログラムの入口)
- GameLiftClient.cs (GameLiftとの接続を管理するクラス)
- ChatClient.cs (接続できたものと受送信クラスの管理クラス)
- ManagedConnection.cs (受送信クラス)
GameLiftClient.cs ソースコード
GameLiftClient.cs
ソースコードを貼り付けます。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Sockets;
using Amazon;
using Amazon.GameLift;
using Amazon.GameLift.Model;
using Amazon.CognitoIdentity;
namespace AGLW_CSharp_BetterChatClientSample
{
class GameLiftClient
{
// All the GameLift related things will be processed by this AmazonGameLiftClient
private AmazonGameLiftClient gameLiftClient = null;
// Sessions contain general information
private GameSession gameSession = null;
private PlayerSession playerSession = null;
private string playerId = string.Empty;
// A instance's status flag of this class
public bool IsAlive { get; private set; } = false;
public GameLiftClient()
{
IsAlive = true;
playerId = Guid.NewGuid().ToString();
Console.WriteLine($"Client : playerId {playerId}");
// gameLiftClient = new AmazonGameLiftClient("fake", "fake", new AmazonGameLiftConfig() { ServiceURL = "http://localhost:9080" });
CognitoAWSCredentials credentials = new CognitoAWSCredentials(
"Your-Identity-Pool-ID", // Identity pool ID
RegionEndpoint.APNortheast1 // Region
);
gameLiftClient = new AmazonGameLiftClient(credentials, RegionEndpoint.APNortheast1);
}
public void Start()
{
// SearchGameSessionAsync(async) -> Create GameSession(async) -> Create PlayerSession(async) -> Connect ()
CreateSessionsAndConnect();
}
async private Task CreateSessionsAndConnect()
{
await SearchGameSessionAsync();
await CreateGameSessionAsync();
await CreatePlayerSessionAsync();
// Connect to the IP provided by PlayerSession
Connect();
}
// Search if there's available session to join
async private Task SearchGameSessionAsync()
{
Console.WriteLine($"Client : SearchGameSessionAsync() start");
var request = new DescribeGameSessionsRequest();
request.FleetId = "fleet-id";
Console.WriteLine($"Client : Sending request and await");
var response = await gameLiftClient.DescribeGameSessionsAsync(request);
Console.WriteLine($"Client : request sent");
foreach (var session in response.GameSessions)
{
if (session.MaximumPlayerSessionCount > session.CurrentPlayerSessionCount && session.Status == GameSessionStatus.ACTIVE)
{
gameSession = session;
break;
}
}
}
// Create a GameSession
async private Task CreateGameSessionAsync()
{
if (gameSession != null) return;
Console.WriteLine($"Client : CreateGameSessionAsync() start");
var request = new CreateGameSessionRequest();
request.FleetId = "fleet-id";
request.CreatorId = playerId;
request.MaximumPlayerSessionCount = 2;
Console.WriteLine($"Client : Sending request and await");
var response = await gameLiftClient.CreateGameSessionAsync(request);
Console.WriteLine($"Client : request sent");
if (response.GameSession != null)
{
Console.WriteLine($"Client : GameSession Created!");
Console.WriteLine($"Client : GameSession ID {response.GameSession.GameSessionId}!");
gameSession = response.GameSession;
}
else
{
Console.Error.WriteLine($"Client : Failed creating GameSession!");
IsAlive = false;
}
}
// Create a PlayerSession from a GameSession
async private Task CreatePlayerSessionAsync()
{
Console.WriteLine($"Client : CreatePlayerSessionAsync() start");
if (gameSession == null) return;
while (gameSession.Status != GameSessionStatus.ACTIVE)
{
Console.WriteLine($"Client : Wait for GameSession to be ACTIVE, Sleep for a while");
Thread.Sleep(100);
await UpdateGameSession();
}
var request = new CreatePlayerSessionRequest();
request.GameSessionId = gameSession.GameSessionId;
request.PlayerId = playerId;
Console.WriteLine($"Client : Sending request and await");
var response = await gameLiftClient.CreatePlayerSessionAsync(request);
Console.WriteLine($"Client : request sent");
if (response.PlayerSession != null)
{
Console.WriteLine($"Client : PlayerSession Created!");
Console.WriteLine($"Client : PlayerSession ID {response.PlayerSession.PlayerSessionId}!");
playerSession = response.PlayerSession;
}
else
{
Console.Error.WriteLine($"Client : Failed creating PlayerSession!");
IsAlive = false;
}
}
// Update GameSession until it's active
async private Task UpdateGameSession()
{
DescribeGameSessionsRequest request = new DescribeGameSessionsRequest();
request.GameSessionId = gameSession.GameSessionId;
Console.WriteLine($"Client : Describe GameSession {gameSession.GameSessionId}...");
DescribeGameSessionsResponse response = await gameLiftClient.DescribeGameSessionsAsync(request);
if (response != null)
{
if (response.GameSessions.Count == 1)
{
// Update GameSession
gameSession = response.GameSessions[0];
}
else
{
Console.WriteLine($"Client : Warning, describe GameSession count {response.GameSessions.Count}");
}
}
else
{
Console.WriteLine($"Client : Warning, describe GameSession no response");
}
}
// Connect to the IP which PlayerSession provides
// When client connects :
// 1) Receive the msg sent by server
// 2) Send another msg back
// 3) Close the connection
private void Connect()
{
Console.WriteLine($"Client : Connect() start");
if (playerSession == null) return;
Console.WriteLine($"Client : Try to connect {playerSession.IpAddress}:{playerSession.Port}");
TcpClient client = new TcpClient(playerSession.IpAddress, playerSession.Port);
if (client.Connected)
{
Console.WriteLine($"Client : Connected");
ChatClient chatClient = new ChatClient(client);
chatClient.StartClient();
}
}
}
}
GameLiftClient.cs メンバー変数の解説
// All the GameLift related things will be processed by this AmazonGameLiftClient
private AmazonGameLiftClient gameLiftClient = null;
// Sessions contain general information
private GameSession gameSession = null;
private PlayerSession playerSession = null;
private string playerId = string.Empty;
// A instance's status flag of this class
public bool IsAlive { get; private set; } = false;
- AmazonGameLiftClient :
gameLiftClient
検証情報と地域情報を持っているGameLiftとの接続の担当です。 - GameSession :
gameSession
GameSession情報を保存するメンバーです。 - PlayerSession :
playerSession
PlayerSessionを保存するメンバーです。プレイヤーが接続すべきサーバーのIP
とPort
などを持っています。 - string :
playerId
GameSessionとPlayerSessionなどの請求情報に登録する、プレイヤー唯一のIDです。 - bool :
IsAlive
プログラムまだ生きてるかどうかを記録するフラグです(今後よく使うかもしれません。例えば、接続失敗したらこのフラグをfalse
にするとか)。
ConnectedClient.cs 関数の解説
public GameLiftClient()
{
IsAlive = true;
playerId = Guid.NewGuid().ToString();
Console.WriteLine($"Client : playerId {playerId}");
// gameLiftClient = new AmazonGameLiftClient("fake", "fake", new AmazonGameLiftConfig() { ServiceURL = "http://localhost:9080" });
CognitoAWSCredentials credentials = new CognitoAWSCredentials(
"Your-Identity-Pool-ID", // Identity pool ID
RegionEndpoint.APNortheast1 // Region
);
gameLiftClient = new AmazonGameLiftClient(credentials, RegionEndpoint.APNortheast1);
}
まずはGameLiftClient()
構造体です。
プログラムの存続フラグIsAlive
をtrue
にします。
そしてGuid.NewGuid().ToString()
でプレイヤーの唯一IDを生成します。
Identity Pool ID
と RegionEndpoint
で CognitoAWSCredentials
を生成し、検証情報と地域情報を持っているGameLiftとの接続の担当gameLiftClient
を作成します。
// Search if there's available session to join
async private Task SearchGameSessionAsync()
{
Console.WriteLine($"Client : SearchGameSessionAsync() start");
var request = new DescribeGameSessionsRequest();
request.FleetId = "fleet-id";
Console.WriteLine($"Client : Sending request and await");
var response = await gameLiftClient.DescribeGameSessionsAsync(request);
Console.WriteLine($"Client : request sent");
foreach (var session in response.GameSessions)
{
if (session.MaximumPlayerSessionCount > session.CurrentPlayerSessionCount && session.Status == GameSessionStatus.ACTIVE)
{
gameSession = session;
break;
}
}
}
SearchGameSessionAsync()
で現在利用できるGameSession
を検索します。new DescribeGameSessionsRequest()
で作ったrequest
にFleet IDを入れて、gameLiftClient.DescribeGameSessionsAsync(request)
でセッション検索請求を送ります。
response
に利用できるGameSession
情報に返してくれます。session.CurrentPlayerSessionCount
で現在のプレイヤー人数を取得し、入れるかどうかを判断します。そしてsession.Status
でそのセッションが働いてるかどうか確認できます。
条件に満足したsession
はメンバーのgameSession
に保存します。
// Looping in a sender thread
async private Task CreateGameSessionAsync()
{
if (gameSession != null) return;
Console.WriteLine($"Client : CreateGameSessionAsync() start");
var request = new CreateGameSessionRequest();
request.FleetId = "fleet-id";
request.CreatorId = playerId;
request.MaximumPlayerSessionCount = 2;
Console.WriteLine($"Client : Sending request and await");
var response = await gameLiftClient.CreateGameSessionAsync(request);
Console.WriteLine($"Client : request sent");
if (response.GameSession != null)
{
Console.WriteLine($"Client : GameSession Created!");
Console.WriteLine($"Client : GameSession ID {response.GameSession.GameSessionId}!");
gameSession = response.GameSession;
}
else
{
Console.Error.WriteLine($"Client : Failed creating GameSession!");
IsAlive = false;
}
}
CreateGameSessionAsync()
でGameSession
を作ります。
new CreateGameSessionRequest()
で請求を作成します。FleetId
、CreatorId
、MaximumPlayerSessionCount
を記入し、gameLiftClient.CreateGameSessionAsync(request)
で請求を送ります。
セッション情報がresponse.GameSession
に返したら、ローカルのメンバーgameSession
に保存します。
// Create a PlayerSession from a GameSession
async private Task CreatePlayerSessionAsync()
{
Console.WriteLine($"Client : CreatePlayerSessionAsync() start");
if (gameSession == null) return;
while (gameSession.Status != GameSessionStatus.ACTIVE)
{
Console.WriteLine($"Client : Wait for GameSession to be ACTIVE, Sleep for a while");
Thread.Sleep(100);
await UpdateGameSession();
}
var request = new CreatePlayerSessionRequest();
request.GameSessionId = gameSession.GameSessionId;
request.PlayerId = playerId;
Console.WriteLine($"Client : Sending request and await");
var response = await gameLiftClient.CreatePlayerSessionAsync(request);
Console.WriteLine($"Client : request sent");
if (response.PlayerSession != null)
{
Console.WriteLine($"Client : PlayerSession Created!");
Console.WriteLine($"Client : PlayerSession ID {response.PlayerSession.PlayerSessionId}!");
playerSession = response.PlayerSession;
}
else
{
Console.Error.WriteLine($"Client : Failed creating PlayerSession!");
IsAlive = false;
}
}
CreatePlayerSessionAsync()
で指定したGameSession
に接続するPlayerSession
を作ります。
GameSession
が準備できていないこともあるので、UpdateGameSession()
でGameSession
の最新情報を取得します。gameSession.Status == GameSessionStatus.ACTIVE
になってから、次に進めます。
new CreatePlayerSessionRequest()
で請求を作って、GameSessionId
とPlayerId
を記入しgameLiftClient.CreatePlayerSessionAsync(request)
で請求を送ります。
無事にresponse.PlayerSession
にPlayerSession
の情報が返してくれたら、ローカルのメンバーplayerSession
に保存します。
PlayerSession
にIP
とPort
情報があるので、それをTcpClient
に渡して、サーバーと通信ができます。
最後
より良いチャットクライアントPart1はここで完了です。
今回の記事は接続部分ソースコードの説明でしたので、次回の記事は受送信のソースコードを解説します。