
Amazon GameLift In C# 09: より良いチャットサーバーを作ります(Part2)
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
概要
前回の記事はChatServer.csのソースコードを解説しました。今回はConnectedClient.csを中心に より良いチャットサーバーののPart2 の記事です。
マシン環境
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-BetterChatServerSample
ソースコードファイル :
- Program.cs (プログラムの入口)
- GameLiftServer.cs (Amazon GameLiftとの接続、そして
ChatServerを管理するクラス) - ChatServer.cs (クライアント
ConnectedClientとの接続、通信管理クラス) - ConnectedClient.cs (クライアントの接続情報と
NetworkStreamでの受送信管理クラス)
ChatServerは接続している複数のConnectedClientを管理しています。ChatServerは全体クライアントとの通信を管理していますが、細かいメッセージ処理などはConnectedClientでやっています(NetworkStream周り)。
ConnectedClient.cs ソースコード
ConnectedClient.csソースコードを貼り付けます。
class ConnectedClient
{
public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8;
public TcpClient TargetClient { get; private set; } = null;
public NetworkStream TargetStream { get; private set; } = null;
public ConcurrentQueue<byte[]> SendingQueue { get; private set; } = null;
public ConcurrentQueue<byte[]> ReceivingQueue { get; private set; } = null;
public Thread SenderThread { get; private set; } = null;
public Thread ReceiverThread { get; private set; } = null;
public ConnectedClient(TcpClient client)
{
TargetClient = client;
TargetStream = client.GetStream();
SendingQueue = new ConcurrentQueue<byte[]>();
ReceivingQueue = new ConcurrentQueue<byte[]>();
}
public void StartClient()
{
SenderThread = new Thread(() => Send());
SenderThread.Start();
ReceiverThread = new Thread(() => Receive());
ReceiverThread.Start();
}
// Should be called by server, add a message to queue, will be sent to client later
public void SendMessage(byte[] bytes)
{
SendingQueue.Enqueue(bytes);
}
// Should be called by server, retrieve message
public bool RetrieveMessage(out byte[] bytes)
{
bool retrieved = ReceivingQueue.TryDequeue(out bytes);
return retrieved;
}
// Looping in a sender thread
private void Send()
{
byte[] bytes;
while (TargetStream != null)
{
if (SendingQueue.TryDequeue(out bytes))
{
TargetStream.Write(bytes);
}
}
}
// Looping in a receiver thread
private void Receive()
{
byte[] bytes = new byte[ChatServer.MessageLength];
if (TargetStream != null)
{
try
{
while (TargetStream.Read(bytes) > 0)
{
Console.WriteLine($"Message Received: {Encoder.GetString(bytes)}");
ReceivingQueue.Enqueue(bytes);
bytes = new byte[ChatServer.MessageLength];
}
}
catch(SocketException e)
{
Console.WriteLine($"Excpetion catched : {e}");
}
catch(IOException e)
{
Console.WriteLine($"Excpetion catched : {e}");
}
}
}
}
ConnectedClient.cs メンバー変数の解説
public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8;
public TcpClient TargetClient { get; private set; } = null;
public NetworkStream TargetStream { get; private set; } = null;
public ConcurrentQueue<byte[]> SendingQueue { get; private set; } = null;
public ConcurrentQueue<byte[]> ReceivingQueue { get; private set; } = null;
public Thread SenderThread { get; private set; } = null;
public Thread ReceiverThread { get; private set; } = null;
- TcpClient :
TargetClient
ChatServerから渡してきたクライアント接続情報を持つTcpClientです。 - NetworkStream :
TargetStream
TargetClientのNetworkStreamです。メッセージ受送信を担当しています。 -
ConcurrentQueue<byte[]> :
SendingQueue/ReceivingQueue
メッセージの受送信のは一瞬でやるではなく、これから送信するメッセージはSendingQueueに保存して送信を待ち、受信したメッセージはReceivingQueueに保存してChatServerは後ほど集めます。 - Thread :
SenderThread/ReceiverThread送信することはSenderThreadで行い、受送することはReceiverThreadで行います。
ConnectedClient.cs 関数の解説
// Should be called by server, add a message to queue, will be sent to client later
public void SendMessage(byte[] bytes)
{
SendingQueue.Enqueue(bytes);
}
// Should be called by server, retrieve message
public bool RetrieveMessage(out byte[] bytes)
{
bool retrieved = ReceivingQueue.TryDequeue(out bytes);
return retrieved;
}
SendMessage()とRetrieveMessage()はChatServerで実行される関数です。
SendMessage()は最初からDelegateに登録されます。一斉送信される際は、すぐ送信ではなく、SendMessageでSendingQueueにメッセージを保存し、順序的に送信することを待ちます。
RetrieveMessage()は一旦受信してReceivingQueueに保存されるメッセージを取り出す関数です。ChatServerで呼ばれて、ReceivingQueueのメッセージを順序の並びで送信隊列に保存します。
// Looping in a sender thread
private void Send()
{
byte[] bytes;
while (TargetStream != null)
{
if (SendingQueue.TryDequeue(out bytes))
{
TargetStream.Write(bytes);
}
}
}
Send()関数はSenderThreadで実行され、SendingQueueに残っているメッセージを常に検出し、送信します。
// Looping in a receiver thread
private void Receive()
{
byte[] bytes = new byte[ChatServer.MessageLength];
if (TargetStream != null)
{
try
{
while (TargetStream.Read(bytes) > 0)
{
Console.WriteLine($"Message Received: {Encoder.GetString(bytes)}");
ReceivingQueue.Enqueue(bytes);
bytes = new byte[ChatServer.MessageLength];
}
}
catch(SocketException e)
{
Console.WriteLine($"Excpetion catched : {e}");
}
catch(IOException e)
{
Console.WriteLine($"Excpetion catched : {e}");
}
}
}
Receive()はReceiverThreadで実行されます。
while (TargetStream.Read(bytes) > 0)
{
Console.WriteLine($"Message Received: {Encoder.GetString(bytes)}");
ReceivingQueue.Enqueue(bytes);
bytes = new byte[ChatServer.MessageLength];
}
TargetStream(NetworkStream)に送信してきたメッセージを取り出して、ReceivingQueueに保存します。
catch(SocketException e)
{
Console.WriteLine($"Excpetion catched : {e}");
}
catch(IOException e)
{
Console.WriteLine($"Excpetion catched : {e}");
}
クライアントが接続した際、一瞬Exceptionが出ます。catchしないとプログラムが落ちるので、ここで予想したExceptionをcatchして、落ちないようにします。
これでConnectedClients.csの説明が完了いたします。
最後
より良いチャットサーバーはその前簡単なチャットサーバー記事とGameLift接続部分が基本一緒ですが、それ以外の違う部分はこれで説明完了になります。
次回の記事はクライアントのソースコードを解説します。






