Usare socket per inviare e ricevere dati tramite TCP

Prima di usare un socket per comunicare con dispositivi remoti, è necessario inizializzare il socket con informazioni su protocollo e indirizzo di rete. Il costruttore per la classe Socket dispone di parametri che specificano la famiglia di indirizzi, il tipo di socket e il tipo di protocollo usato per stabilire connessioni. Quando si connette un socket client a un socket del server, il client userà un oggetto IPEndPoint per specificare l'indirizzo di rete del server.

Creare un endpoint IP

Quando si usa System.Net.Sockets, si rappresenta un endpoint di rete come oggetto IPEndPoint. IPEndPoint viene costruito con un oggetto IPAddress e il numero di porta corrispondente. Prima di poter avviare una conversazione tramite un Socket, creare una pipe di dati tra l'app e la destinazione remota.

TCP/IP usa un indirizzo di rete e un numero di porta del servizio per identificare in modo univoco un servizio. L'indirizzo di rete identifica una destinazione di rete specifica. Il numero di porta identifica il servizio specifico in tale dispositivo a cui connettersi. La combinazione di indirizzo di rete e porta del servizio viene denominata endpoint, rappresentato in .NET dalla classe EndPoint. Viene definito un discendente di EndPoint per ogni famiglia di indirizzi supportata. Per la famiglia di indirizzi IP, la classe è IPEndPoint.

La classe Dns fornisce servizi DNS alle app che usano i servizi TCP/IP Internet. Il metodo GetHostEntryAsync richiede a un server DNS di eseguire il mapping di un nome descrittivo di dominio (ad esempio "host.contoso.com") a un indirizzo Internet numerico (ad esempio 192.168.1.1). GetHostEntryAsync restituisce una voce Task<IPHostEntry> che, quando è attesa, contiene un elenco di indirizzi e alias per il nome richiesto. Nella maggior parte dei casi, è possibile usare il primo indirizzo restituito nella matrice AddressList. Il codice seguente ottiene un IPAddress contenente l'indirizzo IP del server host.contoso.com.

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

Suggerimento

Per scopi di test e debug manuali, in genere è possibile usare il metodo GetHostEntryAsync con il nome host risultante derivante dal valore Dns.GetHostName() per risolvere il nome localhost in un indirizzo IP. Si consideri il frammento di codice seguente:

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) definisce i numeri di porta per i servizi comuni. Per altre informazioni, vedere IANA: Nome del servizio e registro dei numeri di porta del protocollo di trasporto. Altri servizi possono avere numeri di porta registrati nell'intervallo da 1024 a 65.535. Il codice seguente combina l'indirizzo IP per host.contoso.com con un numero di porta per creare un endpoint remoto per una connessione.

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

Dopo aver determinato l'indirizzo del dispositivo remoto e aver scelto la porta da usare per la connessione, l'applicazione può stabilire una connessione con il dispositivo remoto.

Creare un client Socket

Dopo aver creato l'oggetto endPoint, creare un socket client per connettersi al server. Una volta connesso, il socket può inviare e ricevere dati dalla connessione socket del server.

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

Il codice C# precedente:

  • Crea un'istanza di un nuovo oggetto Socket con una famiglia di indirizzi di istanze endPoint specifica, SocketType.Stream e ProtocolType.Tcp.

  • Chiama il metodo Socket.ConnectAsync con l'istanza endPoint come argomento.

  • In un ciclo while:

    • Codifica e invia un messaggio al server usando Socket.SendAsync.
    • Scrive il messaggio inviato nella console.
    • Inizializza un buffer per ricevere dati dal server utilizzando Socket.ReceiveAsync.
    • Quando response è un riconoscimento, viene scritto nella console e il ciclo viene chiuso.
  • Infine, il socket client chiama Socket.Shutdown con SocketShutdown.Both specificato, che arresta le operazioni di invio e ricezione.

Creare un server Socket

Per creare il socket del server, l'oggetto endPoint può restare in ascolto delle connessioni in ingresso su qualsiasi indirizzo IP, ma è necessario specificare il numero di porta. Dopo aver creato il socket, il server può accettare connessioni in ingresso e comunicare con i client.

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

Il codice C# precedente:

  • Crea un'istanza di un nuovo oggetto Socket con una famiglia di indirizzi di istanze endPoint specifica, SocketType.Stream e ProtocolType.Tcp.

  • listener chiama il metodo Socket.Bind con l'istanza endPoint come argomento per associare il socket all'indirizzo di rete.

  • Il metodo Socket.Listen() viene chiamato per restare in ascolto delle connessioni in ingresso.

  • listener chiama il metodo Socket.AcceptAsync per accettare una connessione in ingresso sul socket handler.

  • In un ciclo while:

    • Chiama Socket.ReceiveAsync per ricevere dati dal client.
    • Quando i dati vengono ricevuti, vengono decodificati e scritti nella console.
    • Se il messaggio response termina con <|EOM|>, viene inviato un riconoscimento al client usando Socket.SendAsync.

Eseguire il client e il server di esempio

Avviare prima l'applicazione server e quindi avviare l'applicazione client.

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

L'applicazione client invierà un messaggio al server e il server risponderà con un riconoscimento.

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

Vedi anche