「C Sharpとネットワーク - TCP Client」の版間の差分

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

編集の要約なし
 
(同じ利用者による、間の7版が非表示)
29行目: 29行目:
<br><br>
<br><br>


== サンプルコード ==
 
  <syntaxhighlight lang="cpp">
== クライアント ==
  public void tcpConnectAndSend()
==== 送信 ====
以下の例では、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
{
    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
  {
  {
     var ip = "localhost";
     static async Task Main(string[] args)
    var port = 8888;
    {
      string server          = "<ホスト名またはIPアドレス>"; // サーバーのアドレス
      int port                = <ポート番号>;                // ポート番号
      int timeoutMilliseconds = 5000;                      // タイムアウト(ミリ秒)
      try
      {
          using (TcpClient client = new TcpClient())
          {
            // サーバへ接続
            var connectTask = client.ConnectAsync(server, port);
   
   
    client.Connect(ip, port);
            // サーバへの接続が確立するまで待機
            if (await Task.WhenAny(connectTask, Task.Delay(timeoutMilliseconds)) != connectTask)
            {  // タイムアウトが発生した場合
                throw new TimeoutException("エラー: サーバへの接続がタイムアウト");
            }
   
   
     try
            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)
     {
     {
       using (System.Net.Sockets.NetworkStream ns = client.GetStream())
       string server = "<ホスト名またはIPアドレス>";  // サーバーのアドレス
      int port = <ポート番号>;                    // ポート番号
      int timeoutMilliseconds = 5000;            // タイムアウト(ミリ秒)
      try
       {
       {
           ns.ReadTimeout  = 1000;
           using (TcpClient client = new TcpClient())
          ns.WriteTimeout = 1000;
          {
          byte[] sendBytes = Encoding.UTF8.GetBytes("message: hoge");
            // サーバへ接続
           ns.Write(sendBytes, 0, sendBytes.Length);
            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}");
       }
       }
    }
    }
    catch (IOException e)
}
    {
</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>
  </syntaxhighlight>