Sockets を使用し TCP を介してデータの送受信を行う

ソケットを使用してリモート デバイスと通信するには、ソケットをプロトコルとネットワーク アドレスの情報を使用して事前に初期化する必要があります。 Socket クラスのコンストラクターには、アドレス ファミリ、ソケットの種類、およびソケットが接続を行うために使用するプロトコルの種類を指定するパラメーターがあります。 クライアント ソケットをサーバー ソケットに接続するとき、クライアントでは IPEndPoint オブジェクトを使ってサーバーのネットワーク アドレスを指定します。

IP エンドポイントを作成する

System.Net.Sockets を使うときは、ネットワーク エンドポイントを IPEndPoint オブジェクトとして表します。 IPEndPoint は、IPAddress とそれに対応するポート番号を使って構築されます。 Socket を使って会話を始める前に、アプリとリモートの通信先との間にデータ パイプを作成します。

TCP/IP はネットワーク アドレスとサービス ポート番号を使用して、サービスを一意に識別しています。 ネットワーク アドレスは、特定のネットワーク通信先を示します。ポート番号は、そのデバイス上の接続先である特定のサービスを示します。 ネットワーク アドレスとサービス ポートの組み合わせはエンドポイントと呼ばれ、.NET では EndPoint クラスによって表されます。 EndPoint の子孫は、サポートされるアドレス ファミリごとに定義されます。IP アドレス ファミリの場合、クラスは IPEndPoint です。

Dns クラスは、TCP/IP インターネット サービスを使うアプリにドメインネーム サービスを提供します。 GetHostEntryAsync メソッドは、DNS サーバーのクエリを実行して、ユーザー フレンドリなドメイン名 ("host.contoso.com" など) を数値のインターネット アドレス (192.168.1.1 など) にマップします。 GetHostEntryAsync は、要求した名前のアドレスとエイリアスの一覧を待機した後に含む Task<IPHostEntry> を返します。 ほとんどの場合、AddressList 配列で返された最初のアドレスを使用できます。 次のコードは、サーバー host.contoso.com の IP アドレスを含む IPAddress を取得します。

IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("host.contoso.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];

ヒント

手動テストとデバッグには、通常は、Dns.GetHostName() 値の結果のホスト名と共に GetHostEntryAsync メソッドを使って、localhost 名を IP アドレスに解決できます。 次のコード スニペットを考えてみます。

var hostName = Dns.GetHostName();
IPHostEntry localhost = await Dns.GetHostEntryAsync(hostName);
// This is the IP address of the local machine
IPAddress localIpAddress = localhost.AddressList[0];

Internet Assigned Numbers Authority (IANA) により、一般的なサービス用のポート番号が定義されています。 詳細については、IANA: 「Service Name and Transport Protocol Port Number Registry (サービス名とトランスポート プロトコル ポート番号の登録)」を参照してください。 他のサービスが、1,024 から 65,535 の範囲内でポート番号を登録している可能性があります。 次のコードは、host.contoso.com の IP アドレスとポート番号を組み合わせて、接続のためのリモート エンドポイントを作成します。

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

リモート デバイスのアドレスを決定し、接続に使用するポートを選択すると、アプリはそのリモート デバイスとの接続を確立できます。

Socket クライアントを作成する

endPoint オブジェクトを作成したら、クライアント ソケットを作成してサーバーに接続します。 ソケットが接続されると、サーバー ソケット接続からデータを送受信できます。

using Socket client = new(
    ipEndPoint.AddressFamily, 
    SocketType.Stream, 
    ProtocolType.Tcp);

await client.ConnectAsync(ipEndPoint);
while (true)
{
    // Send message.
    var message = "Hi friends 👋!<|EOM|>";
    var messageBytes = Encoding.UTF8.GetBytes(message);
    _ = await client.SendAsync(messageBytes, SocketFlags.None);
    Console.WriteLine($"Socket client sent message: \"{message}\"");

    // Receive ack.
    var buffer = new byte[1_024];
    var received = await client.ReceiveAsync(buffer, SocketFlags.None);
    var response = Encoding.UTF8.GetString(buffer, 0, received);
    if (response == "<|ACK|>")
    {
        Console.WriteLine(
            $"Socket client received acknowledgment: \"{response}\"");
        break;
    }
    // Sample output:
    //     Socket client sent message: "Hi friends 👋!<|EOM|>"
    //     Socket client received acknowledgment: "<|ACK|>"
}

client.Shutdown(SocketShutdown.Both);

前述の C# コードでは、次のことが行われます。

  • 指定した endPoint インスタンスのアドレス ファミリ、SocketType.StreamProtocolType.Tcp を使用して、新しい Socket オブジェクトのインスタンスを作成します。

  • endPoint インスタンスを引数として指定して Socket.ConnectAsync メソッドを呼び出します。

  • while ループ内では:

    • メッセージをエンコードし、Socket.SendAsync を使ってサーバーに送信します。
    • 送信されたメッセージをコンソールに書き込みます。
    • Socket.ReceiveAsync を使って、サーバーからデータを受信するバッファーを初期化します。
    • response が受信確認であれば、それをコンソールに書き込み、ループを終了します。
  • 最後に、client ソケットが SocketShutdown.Both を指定して Socket.Shutdown を呼び出し、送信操作と受信操作の両方をシャットダウンします。

Socket サーバーを作成する

サーバー ソケットを作成する場合、endPoint オブジェクトは任意の IP アドレスで着信接続をリッスンできますが、ポート番号は指定する必要があります。 ソケットが作成されると、サーバーは着信接続を受け入れ、クライアントと通信できます。

using Socket listener = new(
    ipEndPoint.AddressFamily,
    SocketType.Stream,
    ProtocolType.Tcp);

listener.Bind(ipEndPoint);
listener.Listen(100);

var handler = await listener.AcceptAsync();
while (true)
{
    // Receive message.
    var buffer = new byte[1_024];
    var received = await handler.ReceiveAsync(buffer, SocketFlags.None);
    var response = Encoding.UTF8.GetString(buffer, 0, received);
    
    var eom = "<|EOM|>";
    if (response.IndexOf(eom) > -1 /* is end of message */)
    {
        Console.WriteLine(
            $"Socket server received message: \"{response.Replace(eom, "")}\"");

        var ackMessage = "<|ACK|>";
        var echoBytes = Encoding.UTF8.GetBytes(ackMessage);
        await handler.SendAsync(echoBytes, 0);
        Console.WriteLine(
            $"Socket server sent acknowledgment: \"{ackMessage}\"");

        break;
    }
    // Sample output:
    //    Socket server received message: "Hi friends 👋!"
    //    Socket server sent acknowledgment: "<|ACK|>"
}

前述の C# コードでは、次のことが行われます。

  • 指定した endPoint インスタンスのアドレス ファミリ、SocketType.StreamProtocolType.Tcp を使用して、新しい Socket オブジェクトのインスタンスを作成します。

  • listenerendPoint を引数として指定して Socket.Bind メソッドを呼び出し、ソケットをネットワーク アドレスに関連付けます。

  • Socket.Listen() メソッドを呼び出して、着信接続をリッスンします。

  • listenerSocket.AcceptAsync メソッドを呼び出して、handler ソケットでの着信接続を受け入れます。

  • while ループ内では:

    • Socket.ReceiveAsync を呼び出してクライアントからデータを受信します。
    • データを受信したら、デコードしてコンソールに書き込みます。
    • response メッセージが <|EOM|> で終わっていた場合は、Socket.SendAsync を使ってクライアントに受信確認を送信します。

サンプル クライアントとサーバーを実行する

最初にサーバー アプリケーションを起動してから、クライアント アプリケーションを起動します。

dotnet run --project socket-server
Socket server starting...
Found: 172.23.64.1 available on port 9000.
Socket server received message: "Hi friends 👋!"
Socket server sent acknowledgment: "<|ACK|>"
Press ENTER to continue...

クライアント アプリケーションはサーバーにメッセージを送信し、サーバーは受信確認で応答します。

dotnet run --project socket-client
Socket client starting...
Found: 172.23.64.1 available on port 9000.
Socket client sent message: "Hi friends 👋!<|EOM|>"
Socket client received acknowledgment: "<|ACK|>"
Press ENTER to continue...

関連項目