この記事は公開されてから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接続部分が基本一緒ですが、それ以外の違う部分はこれで説明完了になります。
次回の記事はクライアントのソースコードを解説します。