「C Sharpとネットワーク - TCP Client」の版間の差分
編集の要約なし |
編集の要約なし |
||
(同じ利用者による、間の12版が非表示) | |||
1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
C# | TCP (Transmission Control Protocol) は、インターネットプロトコルスイートの中核をなす通信プロトコルの1つである。<br> | ||
TCPは信頼性の高い通信を提供する強力なプロトコルであり、C#の豊富なネットワーキングAPIと組み合わせることで、効率的なネットワークアプリケーションの開発が可能になる。<br> | |||
<br> | |||
C#においても、TCPを利用したネットワークプログラミングは重要な位置を占めている。<br> | |||
<br> | |||
TCPの主な特徴は、信頼性の高い通信を提供することである。<br> | |||
これは、データの送受信を確実に行い、パケットの損失や重複、順序の入れ替わりを防ぐ機能を持っているためである。<br> | |||
<br> | |||
また、フロー制御や輻輳制御の仕組みも備えており、ネットワークの状況に応じて通信速度を調整する。<br> | |||
<br> | |||
C#でTCP通信を実装する場合は、主に<code>System.Net.Sockets</code>名前空間を使用する。<br> | |||
この名前空間には、<code>TcpClient</code>クラスや<code>TcpListener</code>クラス等が含まれており、これらを使用してクライアントとサーバの通信を簡単に実装できる。<br> | |||
<br> | |||
<code>TcpClient</code>クラスを使用すると、サーバへの接続やデータの送信が可能になる。<br> | |||
<code>TcpListener</code>クラスは、サーバ側でクライアントからの接続を待ち受けるために使用する。<br> | |||
<br> | |||
実際の通信では、ストリームを介してデータのやり取りを行う。<br> | |||
<code>NetworkStream</code>クラスを使用して、接続したソケットからデータを読み書きする。<br> | |||
これにより、テキストやバイナリデータを効率的に送受信することができる。<br> | |||
<br> | |||
C#でTCP通信を実装する場合は、非同期プログラミングの手法を活用することが推奨される。<br> | |||
<code>BeginConnect</code>メソッドや<code>BeginAccept</code>メソッド等を使用するこにより、アプリケーションの応答性を向上させることができる。<br> | |||
<br> | |||
セキュリティ面では、SSL / TLS証明書を使用して通信を暗号化することが可能である。<br> | |||
C#では、<code>SslStream</code>クラスを使用してこれを実現することができる。<br> | |||
<br> | |||
また、接続の確立、切断、タイムアウトの処理、エラーハンドリング等を適切に実装することにより、安定したネットワークアプリケーションを開発することができる。<br> | |||
<br><br> | |||
== | |||
< | == クライアント == | ||
==== 送信 ==== | |||
以下の例では、TcpClientクラスを使用して、非同期でサーバに接続およびメッセージを送信している。<br> | |||
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。<br> | |||
<br> | |||
<code>NetworkStream</code>クラスの<code>ReadTimeout</code>や<code>WriteTimeout</code>プロパティを非同期処理と組み合わせることは非推奨である。<br> | |||
非同期操作でタイムアウトを実装する場合、<code>Task.WhenAny</code>と<code>Task.Delay</code>を組み合わせる方法が効果的である。<br> | |||
<br> | |||
* 互換性の問題 | |||
*: <code>ReadTimeout</code>プロパティおよび<code>WriteTimeout</code>プロパティは、同期メソッド (Read, Write) に対してのみ有効である。 | |||
*: 非同期メソッド (ReadAsync, WriteAsync) には影響しない。 | |||
*: <br> | |||
* 非同期操作との不適合 | |||
*: 非同期操作は本質的にタイムアウトの概念と相容れない。 | |||
*: 非同期メソッドは完了するまで制御を返さないため、タイムアウトを設定しても期待通りに動作しない。 | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Text; | |||
using System.Net.Sockets; | |||
using System.Threading.Tasks; | |||
class AsyncTcpClientSender | |||
{ | { | ||
var | static async Task Main(string[] args) | ||
{ | |||
string server = "<ホスト名またはIPアドレス>"; // サーバのアドレス | |||
int port = <ポート番号>; // ポート番号 | |||
int timeoutMilliseconds = 5000; // タイムアウト (ミリ秒) | |||
try | |||
{ | |||
using (TcpClient client = new TcpClient()) | |||
{ | |||
// サーバへ接続 | |||
var connectTask = client.ConnectAsync(server, port); | |||
// サーバへの接続が確立するまで待機 | |||
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask) | |||
{ // タイムアウトが発生した場合 | |||
throw new TimeoutException("エラー: サーバへの接続がタイムアウト"); | |||
} | |||
using (NetworkStream stream = client.GetStream()) | |||
{ // サーバへメッセージを送信 | |||
string message = "Hello, Server"; | |||
byte[] data = Encoding.UTF8.GetBytes(message); | |||
var sendTask = stream.WriteAsync(data, 0, data.Length); | |||
if (await Task.WhenAny(sendTask, Task.Delay(timeoutMilliseconds)) != sendTask) | |||
{ | |||
throw new TimeoutException("エラー: メッセージの送信がタイムアウト"); | |||
} | |||
Console.WriteLine($"送信メッセージ: {message}"); | |||
} | |||
} | |||
} | |||
catch (SocketException e) | |||
{ // ネットワーク関連のエラーが発生した場合 | |||
Console.WriteLine($"SocketException: {e.Message}"); | |||
} | |||
catch (TimeoutException e) | |||
{ // タイムアウトが発生した場合 | |||
Console.WriteLine($"TimeoutException: {e.Message}"); | |||
} | |||
catch (Exception e) | |||
{ // その他の予期せぬエラーが発生した場合 | |||
Console.WriteLine($"予期せぬエラーが発生: {e.Message}"); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 送信 (SSL / TLS対応) ==== | |||
以下の例では、SSL / TLS証明書を使用して、非同期でサーバに接続およびメッセージを送信している。<br> | |||
<code>SslStream</code>クラスを使用して、SSL / TLS証明書に対応したセキュアな通信を実現している。<br> | |||
<br> | |||
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。<br> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>サーバ側でもSSL / TLS証明書の対応が必要となる。</u><br> | |||
<u>実務では、適切な証明書の管理と定期的な更新が重要である。</u><br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using System.Net.Sockets; | |||
using System.Net.Security; | |||
using System.Security.Cryptography.X509Certificates; | |||
class SslAsyncTcpClientSender | |||
{ | |||
static async Task Main(string[] args) | |||
{ | { | ||
string server = "<ホスト名またはIPアドレス>"; // サーバのアドレス | |||
int port = <ポート番号>; // ポート番号 | |||
int timeoutMilliseconds = 5000; // タイムアウト (ミリ秒) | |||
try | |||
{ | { | ||
using (TcpClient client = new TcpClient()) | |||
{ | |||
// サーバへ接続 | |||
var connectTask = client.ConnectAsync(server, port); | |||
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask) | |||
{ | |||
throw new TimeoutException("エラー: サーバーへの接続がタイムアウト"); | |||
} | |||
using (SslStream sslStream = new SslStream( | |||
client.GetStream(), | |||
false, | |||
new RemoteCertificateValidationCallback(SslCertificateValidator.ValidateServerCertificate), | |||
null)) | |||
{ | |||
// SSL / TLS接続を確立 | |||
await sslStream.AuthenticateAsClientAsync(server); | |||
// サーバへメッセージを送信 | |||
string message = "Hello, Secure Server"; | |||
byte[] data = Encoding.UTF8.GetBytes(message); | |||
var sendTask = sslStream.WriteAsync(data, 0, data.Length); | |||
if (await Task.WhenAny(sendTask, Task.Delay(timeoutMilliseconds)) != sendTask) | |||
{ | |||
throw new TimeoutException("エラー: メッセージの送信がタイムアウト"); | |||
} | |||
Console.WriteLine($"送信メッセージ: {message}"); | |||
} | |||
} | |||
} | |||
catch (SocketException e) | |||
{ // ネットワーク関連のエラーが発生した場合 | |||
Console.WriteLine($"SocketException: {e.Message}"); | |||
} | |||
catch (TimeoutException e) | |||
{ // タイムアウトが発生した場合 | |||
Console.WriteLine($"TimeoutException: {e.Message}"); | |||
} | |||
catch (AuthenticationException e) | |||
{ // SSL/TLS認証に失敗した場合 | |||
Console.WriteLine($"AuthenticationException: SSL/TLS認証に失敗: {e.Message}"); | |||
} | |||
catch (Exception e) | |||
{ // その他の予期せぬエラーが発生した場合 | |||
Console.WriteLine($"予期せぬエラーが発生: {e.Message}"); | |||
} | } | ||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
// ※注意 | |||
// 実務では、使用環境に応じて、さらに厳密な検証ロジックを追加することを推奨する。 | |||
// また、可能な限り信頼された認証局によって発行された証明書を使用することを推奨する。 | |||
// | |||
// 信頼された証明書のリストは定期的に更新して、不要になった証明書は速やかに削除すること。 | |||
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); | |||
// ファイルやデータベースへのログ記録処理等を追加 | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 受信 ==== | |||
以下の例では、TcpClientクラスを使用して、非同期でサーバに接続およびメッセージを受信している。<br> | |||
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。<br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Text; | |||
using System.IO; | |||
using System.Threading.Tasks; | |||
using System.Net.Sockets; | |||
class AsyncTcpClientReceiver | |||
{ | |||
static async Task Main(string[] args) | |||
{ | |||
string server = "<ホスト名またはIPアドレス>"; // サーバーのアドレス | |||
int port = <ポート番号>; // ポート番号 | |||
int timeoutMilliseconds = 5000; // タイムアウト(ミリ秒) | |||
try | |||
{ | |||
using (TcpClient client = new TcpClient()) | |||
{ | |||
// サーバへ接続 | |||
var connectTask = client.ConnectAsync(server, port); | |||
// サーバへの接続が確立するまで待機 | |||
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask) | |||
{ // タイムアウトが発生した場合 | |||
throw new TimeoutException("エラー: サーバへの接続がタイムアウト"); | |||
} | |||
using (NetworkStream stream = client.GetStream()) | |||
{ // サーバへメッセージを送信 | |||
byte[] data = new byte[256]; | |||
var readTask = stream.ReadAsync(data, 0, data.Length); | |||
if (await Task.WhenAny(readTask, Task.Delay(timeoutMilliseconds)) != readTask) | |||
{ | |||
throw new TimeoutException("エラー: メッセージの受信がタイムアウト"); | |||
} | |||
int bytes = await readTask; | |||
string responseData = Encoding.UTF8.GetString(data, 0, bytes); | |||
Console.WriteLine($"受信メッセージ: {responseData}"); | |||
} | |||
} | |||
} | |||
catch (ArgumentNullException e) | |||
{ // 無効な引数が渡された場合 | |||
Console.WriteLine($"ArgumentNullException: {e.Message}"); | |||
} | |||
catch (SocketException e) | |||
{ // ネットワーク関連のエラーが発生した場合 | |||
Console.WriteLine($"SocketException: {e.Message}"); | |||
} | |||
catch (IOException e) | |||
{ // ストリームの読み書き中にエラーが発生した場合 | |||
Console.WriteLine($"IOException: {e.Message}"); | |||
} | |||
catch (TimeoutException e) | |||
{ // タイムアウトが発生した場合 | |||
Console.WriteLine($"TimeoutException: {e.Message}"); | |||
} | |||
catch (Exception e) | |||
{ // その他の予期せぬエラーが発生した場合 | |||
Console.WriteLine($"予期せぬエラーが発生: {e.Message}"); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 受信 (SSL / TLS対応) ==== | |||
以下の例では、SSL / TLS証明書を使用して、非同期でサーバに接続およびメッセージを受信している。<br> | |||
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。<br> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>サーバ側でもSSL / TLS証明書の対応が必要となる。</u><br> | |||
<u>実務では、適切な証明書の管理と定期的な更新が重要である。</u><br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using System.Net.Sockets; | |||
using System.Net.Security; | |||
using System.Security.Cryptography.X509Certificates; | |||
class SslAsyncTcpClientReceiver | |||
{ | |||
static async Task Main(string[] args) | |||
{ | |||
string server = "<ホスト名またはIPアドレス>"; // サーバーのアドレス | |||
int port = <ポート番号>; // ポート番号 | |||
int timeoutMilliseconds = 5000; // タイムアウト(ミリ秒) | |||
try | |||
{ | |||
using (TcpClient client = new TcpClient()) | |||
{ | |||
// サーバへ接続 | |||
var connectTask = client.ConnectAsync(server, port); | |||
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask) | |||
{ | |||
throw new TimeoutException("エラー: サーバーへの接続がタイムアウト"); | |||
} | |||
using (SslStream sslStream = new SslStream( | |||
client.GetStream(), | |||
false, | |||
new RemoteCertificateValidationCallback(SslCertificateValidator.ValidateServerCertificate), | |||
null)) | |||
{ | |||
// SSL/TLS接続を確立 | |||
await sslStream.AuthenticateAsClientAsync(server); | |||
// サーバからメッセージを受信 | |||
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 receivedMessage = Encoding.UTF8.GetString(buffer, 0, bytesRead); | |||
Console.WriteLine($"受信メッセージ: {receivedMessage}"); | |||
} | |||
} | |||
} | |||
catch (SocketException e) | |||
{ // ネットワーク関連のエラーが発生した場合 | |||
Console.WriteLine($"SocketException: {e.Message}"); | |||
} | |||
catch (TimeoutException e) | |||
{ // タイムアウトが発生した場合 | |||
Console.WriteLine($"TimeoutException: {e.Message}"); | |||
} | |||
catch (AuthenticationException e) | |||
{ // SSL/TLS証明書の認証に失敗した場合 | |||
Console.WriteLine($"AuthenticationException: SSL/TLS認証に失敗: {e.Message}"); | |||
} | |||
catch (Exception e) | |||
{ // その他の予期せぬエラーが発生した場合 | |||
Console.WriteLine($"予期せぬエラーが発生: {e.Message}"); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
// ※注意 | |||
// 実務では、使用環境に応じて、さらに厳密な検証ロジックを追加することを推奨する。 | |||
// また、可能な限り信頼された認証局によって発行された証明書を使用することを推奨する。 | |||
// | |||
// 信頼された証明書のリストは定期的に更新して、不要になった証明書は速やかに削除すること。 | |||
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); | |||
// ファイルやデータベースへのログ記録処理等を追加 | |||
} | |||
} | } | ||
</ | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
__FORCETOC__ | __FORCETOC__ | ||
[[カテゴリ:C_Sharp]] | [[カテゴリ:C_Sharp]] |
2024年9月13日 (金) 19:35時点における最新版
概要
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
クラスを使用してこれを実現することができる。
また、接続の確立、切断、タイムアウトの処理、エラーハンドリング等を適切に実装することにより、安定したネットワークアプリケーションを開発することができる。
クライアント
送信
以下の例では、TcpClientクラスを使用して、非同期でサーバに接続およびメッセージを送信している。
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。
NetworkStream
クラスのReadTimeout
やWriteTimeout
プロパティを非同期処理と組み合わせることは非推奨である。
非同期操作でタイムアウトを実装する場合、Task.WhenAny
とTask.Delay
を組み合わせる方法が効果的である。
- 互換性の問題
ReadTimeout
プロパティおよびWriteTimeout
プロパティは、同期メソッド (Read, Write) に対してのみ有効である。- 非同期メソッド (ReadAsync, WriteAsync) には影響しない。
- 非同期操作との不適合
- 非同期操作は本質的にタイムアウトの概念と相容れない。
- 非同期メソッドは完了するまで制御を返さないため、タイムアウトを設定しても期待通りに動作しない。
using System;
using System.Text;
using System.Net.Sockets;
using System.Threading.Tasks;
class AsyncTcpClientSender
{
static async Task Main(string[] args)
{
string server = "<ホスト名またはIPアドレス>"; // サーバのアドレス
int port = <ポート番号>; // ポート番号
int timeoutMilliseconds = 5000; // タイムアウト (ミリ秒)
try
{
using (TcpClient client = new TcpClient())
{
// サーバへ接続
var connectTask = client.ConnectAsync(server, port);
// サーバへの接続が確立するまで待機
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask)
{ // タイムアウトが発生した場合
throw new TimeoutException("エラー: サーバへの接続がタイムアウト");
}
using (NetworkStream stream = client.GetStream())
{ // サーバへメッセージを送信
string message = "Hello, Server";
byte[] data = Encoding.UTF8.GetBytes(message);
var sendTask = stream.WriteAsync(data, 0, data.Length);
if (await Task.WhenAny(sendTask, Task.Delay(timeoutMilliseconds)) != sendTask)
{
throw new TimeoutException("エラー: メッセージの送信がタイムアウト");
}
Console.WriteLine($"送信メッセージ: {message}");
}
}
}
catch (SocketException e)
{ // ネットワーク関連のエラーが発生した場合
Console.WriteLine($"SocketException: {e.Message}");
}
catch (TimeoutException e)
{ // タイムアウトが発生した場合
Console.WriteLine($"TimeoutException: {e.Message}");
}
catch (Exception e)
{ // その他の予期せぬエラーが発生した場合
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
}
送信 (SSL / TLS対応)
以下の例では、SSL / TLS証明書を使用して、非同期でサーバに接続およびメッセージを送信している。
SslStream
クラスを使用して、SSL / TLS証明書に対応したセキュアな通信を実現している。
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。
※注意
サーバ側でもSSL / TLS証明書の対応が必要となる。
実務では、適切な証明書の管理と定期的な更新が重要である。
using System;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
class SslAsyncTcpClientSender
{
static async Task Main(string[] args)
{
string server = "<ホスト名またはIPアドレス>"; // サーバのアドレス
int port = <ポート番号>; // ポート番号
int timeoutMilliseconds = 5000; // タイムアウト (ミリ秒)
try
{
using (TcpClient client = new TcpClient())
{
// サーバへ接続
var connectTask = client.ConnectAsync(server, port);
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask)
{
throw new TimeoutException("エラー: サーバーへの接続がタイムアウト");
}
using (SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(SslCertificateValidator.ValidateServerCertificate),
null))
{
// SSL / TLS接続を確立
await sslStream.AuthenticateAsClientAsync(server);
// サーバへメッセージを送信
string message = "Hello, Secure Server";
byte[] data = Encoding.UTF8.GetBytes(message);
var sendTask = sslStream.WriteAsync(data, 0, data.Length);
if (await Task.WhenAny(sendTask, Task.Delay(timeoutMilliseconds)) != sendTask)
{
throw new TimeoutException("エラー: メッセージの送信がタイムアウト");
}
Console.WriteLine($"送信メッセージ: {message}");
}
}
}
catch (SocketException e)
{ // ネットワーク関連のエラーが発生した場合
Console.WriteLine($"SocketException: {e.Message}");
}
catch (TimeoutException e)
{ // タイムアウトが発生した場合
Console.WriteLine($"TimeoutException: {e.Message}");
}
catch (AuthenticationException e)
{ // SSL/TLS認証に失敗した場合
Console.WriteLine($"AuthenticationException: SSL/TLS認証に失敗: {e.Message}");
}
catch (Exception e)
{ // その他の予期せぬエラーが発生した場合
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
}
// ※注意
// 実務では、使用環境に応じて、さらに厳密な検証ロジックを追加することを推奨する。
// また、可能な限り信頼された認証局によって発行された証明書を使用することを推奨する。
//
// 信頼された証明書のリストは定期的に更新して、不要になった証明書は速やかに削除すること。
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);
// ファイルやデータベースへのログ記録処理等を追加
}
}
受信
以下の例では、TcpClientクラスを使用して、非同期でサーバに接続およびメッセージを受信している。
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。
using System;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Net.Sockets;
class AsyncTcpClientReceiver
{
static async Task Main(string[] args)
{
string server = "<ホスト名またはIPアドレス>"; // サーバーのアドレス
int port = <ポート番号>; // ポート番号
int timeoutMilliseconds = 5000; // タイムアウト(ミリ秒)
try
{
using (TcpClient client = new TcpClient())
{
// サーバへ接続
var connectTask = client.ConnectAsync(server, port);
// サーバへの接続が確立するまで待機
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask)
{ // タイムアウトが発生した場合
throw new TimeoutException("エラー: サーバへの接続がタイムアウト");
}
using (NetworkStream stream = client.GetStream())
{ // サーバへメッセージを送信
byte[] data = new byte[256];
var readTask = stream.ReadAsync(data, 0, data.Length);
if (await Task.WhenAny(readTask, Task.Delay(timeoutMilliseconds)) != readTask)
{
throw new TimeoutException("エラー: メッセージの受信がタイムアウト");
}
int bytes = await readTask;
string responseData = Encoding.UTF8.GetString(data, 0, bytes);
Console.WriteLine($"受信メッセージ: {responseData}");
}
}
}
catch (ArgumentNullException e)
{ // 無効な引数が渡された場合
Console.WriteLine($"ArgumentNullException: {e.Message}");
}
catch (SocketException e)
{ // ネットワーク関連のエラーが発生した場合
Console.WriteLine($"SocketException: {e.Message}");
}
catch (IOException e)
{ // ストリームの読み書き中にエラーが発生した場合
Console.WriteLine($"IOException: {e.Message}");
}
catch (TimeoutException e)
{ // タイムアウトが発生した場合
Console.WriteLine($"TimeoutException: {e.Message}");
}
catch (Exception e)
{ // その他の予期せぬエラーが発生した場合
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
}
受信 (SSL / TLS対応)
以下の例では、SSL / TLS証明書を使用して、非同期でサーバに接続およびメッセージを受信している。
また、タイムアウトが発生した場合の再試行処理や高度なエラー処理等も追加することを推奨する。
※注意
サーバ側でもSSL / TLS証明書の対応が必要となる。
実務では、適切な証明書の管理と定期的な更新が重要である。
using System;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
class SslAsyncTcpClientReceiver
{
static async Task Main(string[] args)
{
string server = "<ホスト名またはIPアドレス>"; // サーバーのアドレス
int port = <ポート番号>; // ポート番号
int timeoutMilliseconds = 5000; // タイムアウト(ミリ秒)
try
{
using (TcpClient client = new TcpClient())
{
// サーバへ接続
var connectTask = client.ConnectAsync(server, port);
if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask)
{
throw new TimeoutException("エラー: サーバーへの接続がタイムアウト");
}
using (SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(SslCertificateValidator.ValidateServerCertificate),
null))
{
// SSL/TLS接続を確立
await sslStream.AuthenticateAsClientAsync(server);
// サーバからメッセージを受信
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 receivedMessage = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"受信メッセージ: {receivedMessage}");
}
}
}
catch (SocketException e)
{ // ネットワーク関連のエラーが発生した場合
Console.WriteLine($"SocketException: {e.Message}");
}
catch (TimeoutException e)
{ // タイムアウトが発生した場合
Console.WriteLine($"TimeoutException: {e.Message}");
}
catch (AuthenticationException e)
{ // SSL/TLS証明書の認証に失敗した場合
Console.WriteLine($"AuthenticationException: SSL/TLS認証に失敗: {e.Message}");
}
catch (Exception e)
{ // その他の予期せぬエラーが発生した場合
Console.WriteLine($"予期せぬエラーが発生: {e.Message}");
}
}
}
// ※注意
// 実務では、使用環境に応じて、さらに厳密な検証ロジックを追加することを推奨する。
// また、可能な限り信頼された認証局によって発行された証明書を使用することを推奨する。
//
// 信頼された証明書のリストは定期的に更新して、不要になった証明書は速やかに削除すること。
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);
// ファイルやデータベースへのログ記録処理等を追加
}
}