C Sharpとネットワーク - TCP Listener
概要
TCP (Transmission Control Protocol) は、インターネットプロトコルスイートの中核をなす通信プロトコルの1つである。
TCPは信頼性の高い通信を提供する強力なプロトコルであり、C#の豊富なネットワーキングAPIと組み合わせることで、効率的なネットワークアプリケーションの開発が可能になる。
C#においても、TCPを利用したネットワークプログラミングは重要な位置を占めている。
TCPの主な特徴は、信頼性の高い通信を提供することである。
これは、データの送受信を確実に行い、パケットの損失や重複、順序の入れ替わりを防ぐ機能を持っているためである。
また、フロー制御や輻輳制御の仕組みも備えており、ネットワークの状況に応じて通信速度を調整する。
C#でTCP通信を実装する場合は、主にSystem.Net.Sockets
名前空間を使用する。
この名前空間には、TcpClient
クラスやTcpListener
クラス等が含まれており、これらを使用してクライアントとサーバの通信を簡単に実装できる。
TcpClient
クラスを使用すると、サーバへの接続やデータの送信が可能になる。
TcpListener
クラスは、サーバ側でクライアントからの接続を待ち受けるために使用する。
実際の通信では、ストリームを介してデータのやり取りを行う。
NetworkStream
クラスを使用して、接続したソケットからデータを読み書きする。
これにより、テキストやバイナリデータを効率的に送受信することができる。
C#でTCP通信を実装する場合は、非同期プログラミングの手法を活用することが推奨される。
BeginConnect
メソッドやBeginAccept
メソッド等を使用するこにより、アプリケーションの応答性を向上させることができる。
セキュリティ面では、SSL / TLS証明書を使用して通信を暗号化することが可能である。
C#では、SslStream
クラスを使用してこれを実現することができる。
また、接続の確立、切断、タイムアウトの処理、エラーハンドリング等を適切に実装することにより、安定したネットワークアプリケーションを開発することができる。
サーバ
送信
以下の例では、TcpListenerクラスを使用して非同期で送信している。
複数のクライアントからの同時接続に対応できる非同期サーバとして動作する。
- TcpListenerクラスのインスタンスを生成して、指定されたIPアドレスとポートでリッスンを開始する。
- AcceptTcpClientAsyncメソッドを実行して、クライアントからの接続を非同期で待機する。
- クライアントが接続する時、ProcessClientAsyncメソッドを別タスクで実行して、複数のクライアントを同時に処理する。
- ProcessClientAsyncメソッドにおいて、NetworkStreamクラスを使用してクライアントにメッセージを非同期で送信する。
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
class AsyncTcpServerSender
{
static async Task Main(string[] args)
{
// サーバの設定
// IPアドレスを直接記述する場合
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
// ホスト名からIPアドレスを取得する場合
IPAddress[] ipAddresses = Dns.GetHostAddresses("<ホスト名>");
IPAddress ipAddress;
if (ipAddresses.Length == 0)
{
Console.WriteLine($"ホスト名 '{hostName}' に対応するIPアドレスが存在しない");
return;
}
else
{ // ホスト名に対応するIPアドレスが存在する場合
ipAddress = ipAddresses[0]; // 最初に見つかったIPアドレスを使用
}
try
{
// TcpListenerクラスのインスタンスの生成と開始
TcpListener listener = new TcpListener(ipAddress, port);
listener.Start();
Console.WriteLine($"サーバのリッスンを開始 IP: {ipAddress}, ポート: {port}");
while (true)
{
// クライアントからの接続を非同期で待機
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("クライアントとの接続に成功");
// 接続されたクライアントの処理を別タスクで実行
_ = ProcessClientAsync(client);
}
}
catch (SocketException e)
{
Console.WriteLine($"SocketException: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
static async Task ProcessClientAsync(TcpClient client)
{
try
{
using (NetworkStream stream = client.GetStream())
{
// 送信するメッセージ
string message = "Hello, from Server.";
byte[] data = Encoding.UTF8.GetBytes(message);
// メッセージを非同期で送信
await stream.WriteAsync(data, 0, data.Length);
}
}
catch (Exception e)
{
Console.WriteLine($"クライアント処理中にエラーが発生: {e.Message}");
}
finally
{
// クライアントとの接続を閉じる
client.Close();
Console.WriteLine("クライアントとの接続を終了");
}
}
}
送信 (SSL / TLS対応)
以下の例では、TcpListenerクラスおよびSSL/TLSを使用してセキュアな通信を行いながら、複数のクライアントからの同時接続に対応できる非同期サーバとして送信している。
SslStreamクラスを通じてストリーミング処理を実装しているため、大きなデータの送信にも対応できる。
- サーバ証明書を設定する。
実務では、適切な証明書ファイルとパスワードを使用する必要がある。 - TcpListenerクラスのインスタンスを生成してし、指定されたIPアドレスとポートでリッスンを開始する。
- AcceptTcpClientAsyncメソッドを実行して、クライアントからの接続を非同期で待機する。
- クライアントが接続する時、ProcessClientAsyncメソッドを別タスクで実行して、複数のクライアントを同時に処理できるようにしている。
- ProcessClientAsyncメソッドにおいて、SslStreamクラスを使用してSSL/TLS接続を確立する。
- AuthenticateAsServerAsyncメソッドを実行して、サーバとしてのSSL/TLS認証を行う。
- 認証が成功したら、クライアントへメッセージを非同期で送信する。
- 送信処理にタイムアウトを設定して、Task.WhenAnyメソッドを使用して制御する。
- 送信したメッセージを表示する。
オプション: クライアントからの応答を待機する機能も実装している。 - クライアントとの接続は、処理が完了した後は必ず閉じる。
また、ソケットエラー、SSL/TLS認証エラー、タイムアウト、その他の予期せぬエラーを捕捉している。
※注意
サーバ側でもSSL / TLS証明書の対応が必要となる。
実務では、適切な証明書の管理と定期的な更新が重要である。
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
class SslAsyncTcpServerSender
{
static async Task Main(string[] args)
{
// サーバの設定
// IPアドレスを直接記述する場合
IPAddress ipAddress = IPAddress.Parse("<IPアドレス>");
// ホスト名からIPアドレスを取得する場合
IPAddress[] ipAddresses = Dns.GetHostAddresses("<ホスト名>");
IPAddress ipAddress;
if (ipAddresses.Length == 0)
{
Console.WriteLine($"ホスト名 '{hostName}' に対応するIPアドレスが存在しない");
return;
}
else
{ // ホスト名に対応するIPアドレスが存在する場合
ipAddress = ipAddresses[0]; // 最初に見つかったIPアドレスを使用
}
int port = <ポート番号>; // 使用するポート番号
int timeoutMilliseconds = 5000; // タイムアウト (ミリ秒)
// サーバ証明書の設定
X509Certificate2 serverCertificate = new X509Certificate2("server.pfx", "password");
try
{
// TcpListenerクラスのインスタンスの生成および開始
TcpListener listener = new TcpListener(ipAddress, port);
listener.Start();
Console.WriteLine($"サーバがリッスンを開始 IP: {ipAddress}, ポート: {port}");
while (true)
{
// クライアントからの接続を非同期で待機
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("クライアントとの接続が確立");
// 接続されたクライアントの処理を別タスクで実行
_ = ProcessClientAsync(client, serverCertificate, timeoutMilliseconds);
}
}
catch (SocketException e)
{
Console.WriteLine($"SocketException: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
static async Task ProcessClientAsync(TcpClient client, X509Certificate serverCertificate, int timeoutMilliseconds)
{
try
{
using (SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(SslCertificateValidator.ValidateServerCertificate),
null))
{
// SSL/TLS接続を確立
await sslStream.AuthenticateAsServerAsync(serverCertificate);
// クライアントへ送信するメッセージ
string message = "Hello, Client! This is a secure message from the server.";
byte[] messageData = Encoding.UTF8.GetBytes(message);
// メッセージを非同期で送信
var sendTask = sslStream.WriteAsync(messageData, 0, messageData.Length);
if (await Task.WhenAny(sendTask, Task.Delay(timeoutMilliseconds)) != sendTask)
{
throw new TimeoutException("エラー: メッセージの送信がタイムアウト");
}
await sendTask;
Console.WriteLine($"送信メッセージ: {message}");
// クライアントからの応答を待機 (オプション)
byte[] buffer = new byte[4096];
var receiveTask = sslStream.ReadAsync(buffer, 0, buffer.Length);
if (await Task.WhenAny(receiveTask, Task.Delay(timeoutMilliseconds)) != receiveTask)
{
throw new TimeoutException("エラー: クライアントからの応答がタイムアウト");
}
int bytesRead = await receiveTask;
string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"クライアントからの応答: {response}");
}
}
catch (AuthenticationException e)
{
Console.WriteLine($"SSL/TLS認証エラー: {e.Message}");
}
catch (TimeoutException e)
{
Console.WriteLine($"タイムアウトエラー: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"クライアント処理中にエラーが発生: {e.Message}");
}
finally
{
// クライアントとの接続を閉じる
client.Close();
Console.WriteLine("クライアントとの切断が完了");
}
}
}
// ※注意
// 実務では、使用環境に応じて、さらに厳密な検証ロジックを追加することを推奨する。
// また、可能な限り信頼された認証局によって発行された証明書を使用することを推奨する。
//
// 信頼された証明書のリストは定期的に更新して、不要になった証明書は速やかに削除すること。
using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public class SslCertificateValidator
{
// 信頼された証明書のサムプリント
// 実際の環境に合わせて更新すること
private static readonly string[] TrustedCertificates = new string[]
{
"A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
"B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1"
};
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{ // 証明書チェーンとポリシーエラーが無い場合
LogMessage("情報", "証明書の検証に成功");
return true;
}
LogMessage("警告", $"証明書エラー: {sslPolicyErrors}");
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
{
return HandleChainErrors(chain);
}
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
{
LogMessage("エラー", "証明書の名前が不一致");
return false;
}
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
{
LogMessage("エラー", "リモート証明書が利用不可");
return false;
}
// その他のエラーの場合
return false;
}
private static bool HandleChainErrors(X509Chain chain)
{
foreach (X509ChainStatus status in chain.ChainStatus)
{
if (status.Status == X509ChainStatusFlags.UntrustedRoot)
{
X509Certificate2 rootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
if (ValidateSelfSignedCertificate(rootCert))
{
LogMessage("情報", "信頼されたルート証明書を確認");
return true;
}
}
LogMessage("エラー", $"証明書チェーンエラー: {status.StatusInformation}");
}
return false;
}
private static bool ValidateSelfSignedCertificate(X509Certificate certificate)
{
string certHash = certificate.GetCertHashString();
if (TrustedCertificates.Contains(certHash, StringComparer.OrdinalIgnoreCase))
{
LogMessage("情報", "信頼された自己署名証明書を確認");
return true;
}
LogMessage("エラー", $"未知の自己署名証明書を確認 サムプリント: {certHash}");
return false;
}
private static void LogMessage(string level, string message)
{
string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
Console.WriteLine(logMessage);
// ファイルやデータベースへのログ記録処理等を追加
}
}
受信
以下の例では、TcpListenerクラスを使用して非同期で受信している。
複数のクライアントからの同時接続に対応できる非同期サーバとして動作する。
NetworkStreamクラスを使用してストリーミング処理を実装しているため、大きなデータの受信にも対応できる。
- TcpListenerクラスのインスタンスを生成して、指定されたIPアドレスとポートでリッスンを開始する。
- AcceptTcpClientAsyncメソッドを実行して、クライアントからの接続を非同期で待機する。
- クライアントと接続する時、ProcessClientAsyncメソッドを別タスクで実行して、複数のクライアントを同時に処理できるようにしている。
- ProcessClientAsyncメソッドにおいて、NetworkStreamクラスを使用してクライアントからのメッセージを非同期で受信する。
- 受信処理にタイムアウトを設定して、Task.WhenAnyメソッドを使用して制御する。
- 受信したメッセージを表示する。
オプション: 確認応答メッセージを送信する機能も実装している。
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
class AsyncTcpServerReceiver
{
static async Task Main(string[] args)
{
// サーバの設定
// IPアドレスを直接記述する場合
IPAddress ipAddress = IPAddress.Parse("<IPアドレス>");
// ホスト名からIPアドレスを取得する場合
IPAddress[] ipAddresses = Dns.GetHostAddresses("<ホスト名>");
IPAddress ipAddress;
if (ipAddresses.Length == 0)
{
Console.WriteLine($"ホスト名 '{hostName}' に対応するIPアドレスが存在しない");
return;
}
else
{ // ホスト名に対応するIPアドレスが存在する場合
ipAddress = ipAddresses[0]; // 最初に見つかったIPアドレスを使用
}
int port = <ポート番号>; // 使用するポート番号
int timeoutMilliseconds = 5000; // タイムアウト (ミリ秒)
try
{
// TcpListenerクラスのインスタンスの生成および開始
TcpListener listener = new TcpListener(ipAddress, port);
listener.Start();
Console.WriteLine($"サーバのリッスンを開始 IP: {ipAddress}, ポート: {port}");
while (true)
{
// クライアントからの接続を非同期で待機
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("クライアントとの接続が完了");
// 接続されたクライアントの処理を別タスクで実行
_ = ProcessClientAsync(client, timeoutMilliseconds);
}
}
catch (SocketException e)
{
Console.WriteLine($"SocketException: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
static async Task ProcessClientAsync(TcpClient client, int timeoutMilliseconds)
{
try
{
using (NetworkStream stream = client.GetStream())
{
byte[] buffer = new byte[1024];
int bytesRead;
// メッセージを非同期で受信
var readTask = stream.ReadAsync(buffer, 0, buffer.Length);
if (await Task.WhenAny(readTask, Task.Delay(timeoutMilliseconds)) != readTask)
{
throw new TimeoutException("エラー: メッセージの受信がタイムアウト");
}
bytesRead = await readTask;
if (bytesRead > 0)
{
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"受信したメッセージ: {message}");
// 確認応答メッセージを送信
string response = "Recieved message";
byte[] responseData = Encoding.UTF8.GetBytes(response);
await stream.WriteAsync(responseData, 0, responseData.Length);
}
}
}
catch (TimeoutException e)
{
Console.WriteLine($"タイムアウトエラー: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"クライアント処理中にエラーが発生: {e.Message}");
}
finally
{ // クライアントとの接続を閉じる
client.Close();
Console.WriteLine("クライアントとの切断が完了");
}
}
}
受信 (SSL / TLS対応)
以下の例では、TcpListenerクラスおよびSSL/TLSを使用してセキュアな通信を行いながら、複数のクライアントからの同時接続に対応できる非同期サーバとして受信している。
SslStreamクラスを通じてストリーミング処理を実装しているため、大きなデータの受信にも対応できる。
- まず、サーバ証明書を設定する。
実務では、適切な証明書ファイルとパスワードを使用する必要がある。 - TcpListenerクラスのインスタンスを生成して、指定されたIPアドレスとポートでリッスンを開始する。
- AcceptTcpClientAsyncメソッドを使用して、クライアントからの接続を非同期で待機する。
- クライアントとの接続が完了した時、ProcessClientAsyncメソッドを別タスクで実行して、複数のクライアントを同時に処理する。
- ProcessClientAsyncメソッドにおいて、SslStreamクラスを使用してSSL/TLS接続を確立する。
- AuthenticateAsServerAsyncメソッドを実行して、サーバとしてのSSL/TLS認証を行う。
- 認証が成功した後、クライアントからのメッセージを非同期で受信する。
- また、受信処理にタイムアウトを設定して、Task.WhenAnyメソッドを使用して制御している。
- 受信したメッセージを表示する。
オプション: 確認応答メッセージを送信する機能も実装している。
※注意
サーバ側でもSSL / TLS証明書の対応が必要となる。
実務では、適切な証明書の管理と定期的な更新が重要である。
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
class SslAsyncTcpServerReceiver
{
static async Task Main(string[] args)
{
// サーバの設定
// IPアドレスを直接記述する場合
IPAddress ipAddress = IPAddress.Parse("<IPアドレス>");
// ホスト名からIPアドレスを取得する場合
IPAddress[] ipAddresses = Dns.GetHostAddresses("<ホスト名>");
IPAddress ipAddress;
if (ipAddresses.Length == 0)
{
Console.WriteLine($"ホスト名 '{hostName}' に対応するIPアドレスが存在しない");
return;
}
else
{ // ホスト名に対応するIPアドレスが存在する場合
ipAddress = ipAddresses[0]; // 最初に見つかったIPアドレスを使用
}
int port = <ポート番号>; // 使用するポート番号
int timeoutMilliseconds = 5000; // タイムアウト (ミリ秒)
// サーバ証明書の設定
X509Certificate2 serverCertificate = new X509Certificate2("server.pfx", "password");
try
{
// TcpListenerの作成と開始
TcpListener listener = new TcpListener(ipAddress, port);
listener.Start();
Console.WriteLine($"サーバのリッスンを開始 IP: {ipAddress}, ポート: {port}");
while (true)
{
// クライアントからの接続を非同期で待機
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("クライアントとの接続を確立");
// 接続されたクライアントの処理を別タスクで実行
_ = ProcessClientAsync(client, serverCertificate, timeoutMilliseconds);
}
}
catch (SocketException e)
{
Console.WriteLine($"SocketException: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
static async Task ProcessClientAsync(TcpClient client, X509Certificate serverCertificate, int timeoutMilliseconds)
{
try
{
using (SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(SslCertificateValidator.ValidateServerCertificate),
null))
{
// SSL/TLS接続を確立
await sslStream.AuthenticateAsServerAsync(serverCertificate);
byte[] buffer = new byte[4096];
int bytesRead;
// メッセージを非同期で受信
var readTask = sslStream.ReadAsync(buffer, 0, buffer.Length);
if (await Task.WhenAny(readTask, Task.Delay(timeoutMilliseconds)) != readTask)
{
throw new TimeoutException("エラー: メッセージの受信がタイムアウト");
}
bytesRead = await readTask;
if (bytesRead > 0)
{
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"受信したメッセージ: {message}");
// 確認応答メッセージを送信 (オプション)
string response = "Recieved from client";
byte[] responseData = Encoding.UTF8.GetBytes(response);
await sslStream.WriteAsync(responseData, 0, responseData.Length);
}
}
}
catch (AuthenticationException e)
{
Console.WriteLine($"SSL/TLS認証エラー: {e.Message}");
}
catch (TimeoutException e)
{
Console.WriteLine($"タイムアウトエラー: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"クライアント処理中にエラーが発生: {e.Message}");
}
finally
{
// クライアントとの接続を閉じる
client.Close();
Console.WriteLine("クライアントとの切断が完了");
}
}
}
// ※注意
// 実務では、使用環境に応じて、さらに厳密な検証ロジックを追加することを推奨する。
// また、可能な限り信頼された認証局によって発行された証明書を使用することを推奨する。
//
// 信頼された証明書のリストは定期的に更新して、不要になった証明書は速やかに削除すること。
using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public class SslCertificateValidator
{
// 信頼された証明書のサムプリント
// 実際の環境に合わせて更新すること
private static readonly string[] TrustedCertificates = new string[]
{
"A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
"B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1"
};
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{ // 証明書チェーンとポリシーエラーが無い場合
LogMessage("情報", "証明書の検証に成功");
return true;
}
LogMessage("警告", $"証明書エラー: {sslPolicyErrors}");
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
{
return HandleChainErrors(chain);
}
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
{
LogMessage("エラー", "証明書の名前が不一致");
return false;
}
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
{
LogMessage("エラー", "リモート証明書が利用不可");
return false;
}
// その他のエラーの場合
return false;
}
private static bool HandleChainErrors(X509Chain chain)
{
foreach (X509ChainStatus status in chain.ChainStatus)
{
if (status.Status == X509ChainStatusFlags.UntrustedRoot)
{
X509Certificate2 rootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
if (ValidateSelfSignedCertificate(rootCert))
{
LogMessage("情報", "信頼されたルート証明書を確認");
return true;
}
}
LogMessage("エラー", $"証明書チェーンエラー: {status.StatusInformation}");
}
return false;
}
private static bool ValidateSelfSignedCertificate(X509Certificate certificate)
{
string certHash = certificate.GetCertHashString();
if (TrustedCertificates.Contains(certHash, StringComparer.OrdinalIgnoreCase))
{
LogMessage("情報", "信頼された自己署名証明書を確認");
return true;
}
LogMessage("エラー", $"未知の自己署名証明書を確認 サムプリント: {certHash}");
return false;
}
private static void LogMessage(string level, string message)
{
string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
Console.WriteLine(logMessage);
// ファイルやデータベースへのログ記録処理等を追加
}
}