Verwenden von Sockets zum Senden und Empfangen von Daten über das TCP

Bevor Sie einen Socket verwenden können, um mit einem Remotegerät zu kommunizieren, muss der Socket mit Informationen zu Protokoll und Netzwerkadresse initialisiert werden. Der Konstruktor für die Klasse Socket besitzt Parameter, die die Adressfamilie, den Sockettyp und den Protokolltyp, den der Socket zum Aufbauen von Verbindungen verwendet, angeben. Beim Herstellen einer Verbindung zwischen Clientsocket und Serversocket verwendet der Client ein IPEndPoint-Objekt, um die Netzwerkadresse des Servers anzugeben.

Erstellen eines IP-Endpunkts

Wenn Sie mit System.Net.Sockets arbeiten, stellen Sie einen Netzwerkendpunkt als ein IPEndPoint-Objekt dar. Der IPEndPoint wird mit einer IPAddress und der entsprechenden Portnummer erstellt. Bevor Sie über einen Socket eine Konversation initiieren können, müssen Sie eine Datenpipeline zwischen Ihrer App und dem Remoteziel erstellen.

TCP/IP verwendet eine Netzwerkadresse und eine Dienstportnummer zur eindeutigen Identifizierung eines Diensts. Die Netzwerkadresse identifiziert ein bestimmtes Netzwerkziel und die Portnummer den Dienst auf diesem Gerät, mit dem eine Verbindung hergestellt werden soll. Die Kombination von Netzwerkadresse und Dienstport wird Endpunkt genannt. Dieser wird in .NET durch die EndPoint-Klasse dargestellt. Ein Nachfolger des EndPoint wird für jede unterstützte Adressfamilie definiert. Die Klasse für die IP-Adressfamilie ist IPEndPoint.

Die Dns-Klasse stellt Domain Name Services für Apps bereit, die TCP/IP-Internetdienste verwenden. Die GetHostEntryAsync-Methode fragt einen DNS-Server ab, um einer numerischen Internetadresse (z. B. 192.168.1.1) einen benutzerfreundlichen Domänennamen (z. B. „host.contoso.com“) zuzuordnen. GetHostEntryAsync gibt einen Task<IPHostEntry> zurück. Wenn dieser erwartet wird, enthält er eine Liste der Adressen und Aliasse für den angeforderten Namen. In den meisten Fällen können Sie die erste Adresse verwenden, die im AddressList-Array zurückgegeben wurde. Der folgende Code erhält eine IPAddress mit der IP-Adresse für den Server host.contoso.com.

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

Tipp

Zu manuellen Test- und Debugzwecken können Sie in der Regel die GetHostEntryAsync-Methode mit dem resultierenden Hostnamen aus dem Dns.GetHostName()-Wert verwenden, um den Localhostnamen in eine IP-Adresse aufzulösen. Betrachten Sie den folgenden Codeausschnitt:

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) definiert die Portnummern für verbreitete Dienste. Weitere Informationen finden Sie unter IANA: Portnummerregister für Dienstnamen und Transportprotokolle. Andere Dienste haben registrierte Portnummern im Bereich von 1024 bis 65.535. Der folgende Code kombiniert die IP-Adresse für host.contoso.com mit einer Portnummer zum Erstellen eines Remoteendpunkts für eine Verbindung.

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

Nach Ermitteln der Remotegerätadresse und Auswählen eines Ports für die Verbindung, kann die App eine Verbindung mit dem Remotegerät herstellen.

Erstellen eines Socket-Clients

Erstellen Sie nach dem endPoint-Objekt einen Clientsocket, um eine Verbindung mit dem Server herzustellen. Sobald der Socket verbunden ist, kann er Daten über die Serversocketverbindung senden und empfangen.

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);

Für den C#-Code oben gilt:

  • Instanziiert ein neues Socket-Objekt mit einer angegebenen endPoint-Instanzadressfamilie, SocketType.Stream und ProtocolType.Tcp

  • Ruft die Socket.ConnectAsync-Methode mit der endPoint-Instanz als Argument auf

  • In einer while-Schleife:

    • Codiert und sendet mithilfe von Socket.SendAsync eine Nachricht an den Server
    • Schreibt die gesendete Nachricht in die Konsole
    • Initialisiert einen Puffer zum Empfangen von Daten vom Server mithilfe von Socket.ReceiveAsync
    • Wenn es sich bei response um eine Bestätigung handelt, wird sie in die Konsole geschrieben, und die Schleife wird beendet.
  • Schließlich ruft der client-Socket Socket.Shutdown auf, wenn SocketShutdown.Both gegeben ist, wodurch sowohl Sende- als auch Empfangsvorgänge beendet werden.

Erstellen eines Socket-Servers

Zum Erstellen des Serversocket kann das endPoint-Objekt auf eingehende Verbindungen an einer beliebigen IP-Adresse lauschen, doch die Portnummer muss angegeben werden. Nachdem der Socket erstellt wurde, kann der Server eingehende Verbindungen akzeptieren und mit Clients kommunizieren.

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|>"
}

Für den C#-Code oben gilt:

  • Instanziiert ein neues Socket-Objekt mit einer angegebenen endPoint-Instanzadressfamilie, SocketType.Stream und ProtocolType.Tcp

  • Der listener ruft die Socket.Bind-Methode mit der endPoint-Instanz als Argument auf, um den Socket der Netzwerkadresse zuzuordnen.

  • Die Socket.Listen()-Methode wird aufgerufen, um auf eingehende Verbindungen zu lauschen.

  • Der listener ruft die Socket.AcceptAsync-Methode auf, um eine eingehende Verbindung für den handler-Socket zu akzeptieren.

  • In einer while-Schleife:

    • Ruft Socket.ReceiveAsync zum Empfangen von Daten vom Client auf
    • Wenn die Daten empfangen werden, werden sie decodiert und in die Konsole geschrieben.
    • Wenn die response-Nachricht mit <|EOM|> endet, wird mithilfe von Socket.SendAsync eine Bestätigung an den Client gesendet.

Ausführen des Beispielclients und -servers

Starten Sie zuerst die Server- und dann die Clientanwendung.

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...

Die Clientanwendung sendet eine Nachricht an den Server, der mit einer Bestätigung antwortet.

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...

Siehe auch