この記事は公開されてから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をアップロードします。
サーバー部分が準備できてからこのクライアントプログラムを起動すると、普通にチャットできる状態になります。