
Amazon GameLift In C# 11: より良いチャットクライアントを作ります(Part2)
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
概要
前回の記事の続編です。
前回はGameLiftの接続担当クラスGameLiftClient.csのコードを分析しましたので、これから残りのChatClient.csとManagedConnection.csを解説させていただきます。
マシン環境
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 (受送信クラス)
ChatClient.cs ソースコード
ChatClient.csソースコードを貼り付けます。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Sockets;
namespace AGLW_CSharp_BetterChatClientSample
{
class ChatClient
{
public TcpClient ManagedConnection { get; private set; } = null;
public Messenger Messenger { get; private set; } = null;
public ChatClient(TcpClient client)
{
ManagedConnection = client;
Messenger = new Messenger(ManagedConnection.GetStream());
}
public void StartClient()
{
if (ManagedConnection != null &&
Messenger != null)
{
Messenger.StartMessenger();
}
}
}
}
ChatClient.cs メンバー変数の解説
public TcpClient ManagedConnection { get; private set; } = null;
public Messenger Messenger { get; private set; } = null;
- TcpClient :
ManagedConnection
GameLiftClient.csからもらったTcpClientです。TCP通信全体を管理するメンバーです。 - Messenger :
Messenger
入力の監視、受送信の管理を担当するメンバーです。
ChatClient.cs 関数の解説
public ChatClient(TcpClient client)
{
ManagedConnection = client;
Messenger = new Messenger(ManagedConnection.GetStream());
}
構造体は簡単です。クラスのインスタンスが作られた際、GameLiftClientからTcpClientを渡してくれます。ManagedConnection.GetStream()でNetworkStreamを受け取って、Messengerのインスタンスを作ります。
public void StartClient()
{
if (ManagedConnection != null &&
Messenger != null)
{
Messenger.StartMessenger();
}
}
インスタンスの所有者がStartClient()をコールします。ManagedConnectionとMessenger両方とも存在する場合、Messenger.StartMessenger()でMessengerを起動します。
Messenger.cs ソースコード
Messenger.csソースコードを貼り付けます。
using System;
using System.Threading;
using System.Net.Sockets;
namespace AGLW_CSharp_BetterChatClientSample
{
class Messenger
{
public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8;
public static int SleepDuration = 100; // 100ms
public static int MessageLength = 256;
public NetworkStream Stream { get; private set; } = null;
public Thread SenderThread { get; private set; } = null;
public Thread ReceiverThread { get; private set; } = null;
public string Username { get; private set; } = string.Empty;
public Messenger(NetworkStream stream)
{
Stream = stream;
}
public void StartMessenger()
{
Console.WriteLine("Enter your username: ");
Username = Console.ReadLine();
Console.Clear();
SenderThread = new Thread(() => SendMessage());
SenderThread.Start();
ReceiverThread = new Thread(() => ReceiveMessage());
ReceiverThread.Start();
}
void SendMessage()
{
while (true)
{
SleepForAWhile();
MonitorInput();
}
}
void MonitorInput()
{
string text = Console.ReadLine();
string message = new string($"{Username}: {text}");
WriteMessage(Encoder.GetBytes(message));
}
void WriteMessage(byte[] bytes)
{
ClearCurrentConsoleLine();
Stream.Write(bytes);
// Console.WriteLine($"Sent : {Encoder.GetString(bytes)}");
}
void ReceiveMessage()
{
byte[] bytes = new byte[MessageLength];
while (Stream.Read(bytes) > 0)
{
string text = Encoder.GetString(bytes);
// Console.WriteLine($"Received : {text}");
Console.WriteLine($"{text}");
}
}
void SleepForAWhile()
{
Thread.Sleep(SleepDuration);
}
void ClearCurrentConsoleLine()
{
int currentLineCursor = Console.CursorTop;
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, currentLineCursor - 1);
}
}
}
Messenger.cs メンバー変数の解説
public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8;
public static int SleepDuration = 100; // 100ms
public static int MessageLength = 256;
public NetworkStream Stream { get; private set; } = null;
public Thread SenderThread { get; private set; } = null;
public Thread ReceiverThread { get; private set; } = null;
public string Username { get; private set; } = string.Empty;
- System.Text.Encoding :
Encoder
byte[](送信)とstring(解析)を転換する担当です。 - int :
SleepDurationとMessageLength
常に瞬時処理することは負荷が高いので、処理頻度をSleepDurationの100msにします。
サンプルとして長文の処理は対応していませんので、一つメッセージの長さはMessageLengthの256bytesにします。 - NetworkStream :
Stream
このクラスのインスタンスが作られた時点に貰った通信の通路(stream)です。 - Thread :
SenderThreadとReceiverThread
受信(SenderThread)と送信(`ReceiverThread)処理は二つThread``に分かれています。 - string :
Username
接続できてから受送信処理を起動する前に、Usernameをユーザーに入力してもらいます。Usernameは送信者としてメッセージの先頭に表示されます。
Messenger.cs 関数の解説
構造体は非常に簡単なので、省略いたします。
public void StartMessenger()
{
Console.WriteLine("Enter your username: ");
Username = Console.ReadLine();
Console.Clear();
SenderThread = new Thread(() => SendMessage());
SenderThread.Start();
ReceiverThread = new Thread(() => ReceiveMessage());
ReceiverThread.Start();
}
StartMessenger()でこのクラスを起動します。
Username = Console.ReadLine();でユーザーネームを入力して貰ってから、送信作業SendMessage()をSenderThreadに、受信作業ReceiveMessage()をReceiverThreadに管理してもらいます。
void SendMessage()
{
while (true)
{
SleepForAWhile();
MonitorInput();
}
}
void MonitorInput()
{
string text = Console.ReadLine();
string message = new string($"{Username}: {text}");
WriteMessage(Encoder.GetBytes(message));
}
void WriteMessage(byte[] bytes)
{
ClearCurrentConsoleLine();
Stream.Write(bytes);
// Console.WriteLine($"Sent : {Encoder.GetString(bytes)}");
}
void SleepForAWhile()
{
Thread.Sleep(SleepDuration);
}
void ClearCurrentConsoleLine()
{
int currentLineCursor = Console.CursorTop;
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, currentLineCursor - 1);
}
SleepForAWhile()とMonitorInput()で100ms毎ユーザーの入力を確認しています。
入力された情報はstring message = new string($"{Username}: {text}")という形式、Encoder.GetBytes(message)でbyte化してからWriteMessage(byte[] bytes)で送信されます。
ClearCurrentConsoleLine()は送信する直前、画面に入力したものを消して、送信したものはサーバーから返してくれます。
byte化されるメッセージはStream.Write()を使ってTcpClientを経由して、サーバーに送ります。
void ReceiveMessage()
{
byte[] bytes = new byte[MessageLength];
while (Stream.Read(bytes) > 0)
{
string text = Encoder.GetString(bytes);
// Console.WriteLine($"Received : {text}");
Console.WriteLine($"{text}");
}
}
長さはMessageLength(256bytes)のbyte[]バファを用意し、while (Stream.Read(bytes) > 0)で常に受信確認を行っています。
受信できたものをbyte[]からstringに転換し、Console.WriteLine($"{text}")で画面にプリントします。
最後
Cognito記事のようにAmazon Cognitoを用意してから、アプリのビルドとAmazon GameLiftにアップロード手順で前に作ったBetterChatServerをアップロードします。
サーバー部分が準備できてからこのクライアントプログラムを起動すると、普通にチャットできる状態になります。






