소켓을 사용하여 TCP를 통해 데이터 보내기 및 받기

소켓을 사용하여 원격 디바이스와 통신하려면 먼저 프로토콜 및 네트워크 주소 정보를 사용하여 소켓을 초기화해야 합니다. Socket 클래스에 대한 생성자에는 소켓이 연결을 만드는 데 사용하는 주소 패밀리, 소켓 형식 및 프로토콜 형식을 지정하는 매개 변수가 있습니다. 클라이언트 소켓을 서버 소켓에 연결할 때 클라이언트는 IPEndPoint 개체를 사용하여 서버의 네트워크 주소를 지정합니다.

IP 엔드포인트 만들기

System.Net.Sockets로 작업할 때 네트워크 엔드포인트를 IPEndPoint 개체로 나타냅니다. IPEndPointIPAddress 및 해당 포트 번호로 생성됩니다. 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 메서드를 사용하여 로컬 호스트 이름을 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];

IANA(Internet Assigned Numbers Authority)는 공통 서비스의 포트 번호를 정의합니다. 자세한 내용은 IANA: 서비스 이름 및 전송 프로토콜 포트 번호 레지스트리를 참조하세요. 다른 서비스에는 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...

추가 정보