【C#】ソケット通信の実装ガイド

## はじめに
C#でのソケット通信の実装方法について、基礎的な概念から実践的なコード例までをまとめました。SSL/TLSを使用したセキュアな通信の実装方法も含めて説明していきます。

## ソケット通信の基本概念

ソケット通信は、ネットワーク上のアプリケーション間でデータを送受信するための基本的な仕組みです。
### 通信の基本フロー

![0001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2253716/32594d79-d1cd-f109-15b3-d5cb65b970aa.png)

上図のように、ソケット通信は以下の3つの主要ステップで構成されています。

1. **接続要求**: クライアントからサーバーへの初期接続
2. **SSL/TLSハンドシェイク**: セキュアな通信チャネルの確立
3. **データ交換**: 暗号化されたデータの送受信

:::note info
SSL/TLSハンドシェイクは、通信の暗号化が必要な場合にのみ実施されます。
:::

## アーキテクチャの解説
![0000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2253716/c73c8c48-5c91-b758-edab-8780b119c31c.png)
ソケット通信の各層について説明します。

1. **アプリケーション層**
   - クライアント側：ユーザーインターフェースとビジネスロジック
   - サーバー側：リクエスト処理とビジネスロジック

2. **ソケット層**
   - 通信の確立と管理
   - データの送受信制御

3. **SSL/TLS層**
   - 通信の暗号化
   - 証明書による認証

4. **TCP/IP層**
   - パケットの転送
   - ネットワークルーティング

:::note warn
ソケット通信を実装する際は、必ずタイムアウト処理を実装してください。
:::

## 基本実装 <a id="基本実装"></a>

### サーバー側の実装

```csharp:C#
public class SocketServer
{
    private readonly TcpListener _server;
    private readonly CancellationTokenSource _cancellationSource;
    
    public SocketServer(string ipAddress, int port)
    {
        _server = new TcpListener(IPAddress.Parse(ipAddress), port);
        _cancellationSource = new CancellationTokenSource();
    }

    public async Task StartAsync()
    {
        try
        {
            _server.Start();
            Console.WriteLine("サーバーが起動しました");

            while (!_cancellationSource.Token.IsCancellationRequested)
            {
                var client = await _server.AcceptTcpClientAsync();
                _ = HandleClientAsync(client); // 非同期でクライアント処理を開始
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
            throw;
        }
    }

    private async Task HandleClientAsync(TcpClient client)
    {
        try
        {
            using var stream = client.GetStream();
            stream.ReadTimeout = 5000; // タイムアウト設定
            
            var buffer = new byte[1024];
            var data = new MemoryStream();
            
            int bytesRead;
            while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await data.WriteAsync(buffer.AsMemory(0, bytesRead));
            }

            // 受信データの処理
            var message = Encoding.UTF8.GetString(data.ToArray());
            Console.WriteLine($"受信: {message}");

            // 応答の送信
            var response = Encoding.UTF8.GetBytes("受信しました");
            await stream.WriteAsync(response);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"クライアント処理エラー: {ex.Message}");
        }
        finally
        {
            client.Close();
        }
    }
}
```

:::note info
上記のコードでは、非同期処理を使用して複数クライアントの同時接続に対応しています。
:::

### クライアント側の実装

```csharp:C#
public class SocketClient
{
    private readonly string _serverIp;
    private readonly int _port;
    
    public SocketClient(string serverIp, int port)
    {
        _serverIp = serverIp;
        _port = port;
    }

    public async Task SendMessageAsync(string message)
    {
        using var client = new TcpClient();
        client.SendTimeout = 5000; // タイムアウト設定
        
        try
        {
            await client.ConnectAsync(_serverIp, _port);
            using var stream = client.GetStream();

            var data = Encoding.UTF8.GetBytes(message);
            await stream.WriteAsync(data);
            
            // 応答の受信
            var buffer = new byte[1024];
            var bytesRead = await stream.ReadAsync(buffer);
            var response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            
            Console.WriteLine($"サーバーからの応答: {response}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
            throw;
        }
    }
}
```

## セキュアな実装 <a id="セキュア実装"></a>

SSL/TLSを使用したセキュアな通信の実装例です。

```csharp:C#
public class SecureSocketServer
{
    private readonly X509Certificate2 _certificate;
    
    public SecureSocketServer(string certificatePath, string password)
    {
        _certificate = new X509Certificate2(certificatePath, password);
    }

    private async Task HandleSecureClientAsync(TcpClient client)
    {
        using var sslStream = new SslStream(
            client.GetStream(),
            false,
            ValidateClientCertificate
        );

        try
        {
            await sslStream.AuthenticateAsServerAsync(
                _certificate,
                clientCertificateRequired: false,
                SslProtocols.Tls12,
                checkCertificateRevocation: true
            );

            // 以降は通常のストリーム処理と同様
            var buffer = new byte[1024];
            var bytesRead = await sslStream.ReadAsync(buffer);
            // データ処理...
        }
        catch (Exception ex)
        {
            Console.WriteLine($"SSL エラー: {ex.Message}");
        }
    }

    private bool ValidateClientCertificate(object sender, X509Certificate certificate, 
        X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        // 必要に応じて証明書の検証ロジックを実装
        return sslPolicyErrors == SslPolicyErrors.None;
    }
}
```

:::note warn
本番環境では、必ず有効な SSL 証明書を使用してください。
自己署名証明書は開発環境でのみ使用することを推奨します。
:::

## エラーハンドリング 

ソケット通信で考慮すべき主なエラーとその対処方法です。

 **接続エラー**
```csharp:C#
try
{
    await client.ConnectAsync(serverIp, port);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused)
{
    Console.WriteLine("サーバーが起動していません");
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
{
    Console.WriteLine("接続がタイムアウトしました");
}
```

**通信エラー**
```csharp:C#
try
{
    await stream.WriteAsync(data);
}
catch (IOException ex)
{
    Console.WriteLine("通信が切断されました");
}
```

## 実践的な使用例 <a id="実践例"></a>

### チャットアプリケーションの実装例

```csharp:C#
public class ChatServer : SocketServer
{
    private readonly ConcurrentDictionary<string, TcpClient> _clients 
        = new ConcurrentDictionary<string, TcpClient>();

    protected override async Task HandleClientAsync(TcpClient client)
    {
        var clientId = Guid.NewGuid().ToString();
        _clients.TryAdd(clientId, client);

        try
        {
            using var stream = client.GetStream();
            var buffer = new byte[1024];

            while (true)
            {
                var bytesRead = await stream.ReadAsync(buffer);
                if (bytesRead == 0) break;

                var message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                await BroadcastMessageAsync(message, clientId);
            }
        }
        finally
        {
            _clients.TryRemove(clientId, out _);
            client.Close();
        }
    }

    private async Task BroadcastMessageAsync(string message, string senderId)
    {
        var data = Encoding.UTF8.GetBytes(message);
        var tasks = _clients
            .Where(c => c.Key != senderId)
            .Select(c => SendToClientAsync(c.Value, data));
        
        await Task.WhenAll(tasks);
    }
}
```

:::note info
実際のアプリケーションでは、メッセージの形式やプロトコルを定義し、より堅牢な実装を行うことを推奨します。
:::

## まとめ

この記事では、C#でのソケット通信の基本的な実装から、セキュアな通信、エラーハンドリング、実践的な使用例までをまとめてみました。実装時は以下の点に注意してください。

1. 適切なエラーハンドリングの実装
2. タイムアウト処理の設定
3. リソースの適切な解放
4. セキュリティ対策の実施

## 参考資料
- [Microsoft Docs - ソケット通信](https://learn.microsoft.com/ja-jp/dotnet/fundamentals/networking/sockets/sockets-overview)
- [.NET Core セキュリティガイドライン](https://docs.microsoft.com/ja-jp/dotnet/standard/security/)
- [C# ネットワークプログラミング公式ガイド](https://docs.microsoft.com/ja-jp/dotnet/framework/network-programming/)