Vue d’ensemble de TCP

Important

La classe Socket est fortement recommandée pour les utilisateurs avancés, au lieu de TcpClient et TcpListener.

Pour utiliser le protocole TCP (Transmission Control Protocol), vous avez deux options : utiliser Socket pour optimiser le contrôle et les performances, ou utiliser les classes d’assistance TcpClient et TcpListener. TcpClient et TcpListener reposent sur la classe System.Net.Sockets.Socket, et gèrent les informations relatives au transfert des données pour faciliter l’utilisation.

Les classes de protocole utilisent la classe Socket sous-jacente pour fournir un accès simple aux services réseau sans avoir à gérer les informations d’état ou à connaître les paramètres de configuration des sockets propres au protocole. Vous pouvez utiliser les méthodes Socket asynchrones par le biais des méthodes asynchrones fournies par la classe NetworkStream. Pour accéder aux fonctionnalités de la classe Socket qui ne sont pas exposées par les classes de protocole, vous devez utiliser la classe Socket.

TcpClient et TcpListener représentent le réseau utilisant la classe NetworkStream. Utilisez la méthode GetStream pour retourner le flux réseau, puis appelez les méthodes NetworkStream.ReadAsync et NetworkStream.WriteAsync du flux. La classe NetworkStream ne possède pas le socket sous-jacent des classes de protocole. Sa fermeture n’a donc pas d’effet sur le socket.

Utiliser TcpClient et TcpListener

La classe TcpClient demande les données d’une ressource Internet en utilisant TCP. Les méthodes et propriétés de TcpClient assurent l’abstraction des informations nécessaires pour créer un Socket permettant l’envoi et la réception de données avec le protocole TCP. La connexion à l’appareil distant étant représentée sous forme de flux, les données peuvent être lues et écrites à l’aide des techniques de gestion des flux de données de .NET Framework.

Le protocole TCP établit une connexion à un point de terminaison distant, puis il utilise cette connexion pour envoyer et recevoir des paquets de données. TCP est chargé de s’assurer que les paquets de données sont envoyés au point de terminaison et assemblés dans le bon ordre lors de leur réception.

Créer un point de terminaison IP

Quand vous utilisez System.Net.Sockets, vous représentez un point de terminaison réseau sous forme d’objet IPEndPoint. L’IPEndPoint est construit avec une IPAddress et son numéro de port correspondant. Avant de démarrer une conversation via un Socket, vous créez un canal de données entre votre application et la destination distante.

TCP/IP utilise une adresse réseau et un numéro de port de service pour identifier un service de manière unique. L’adresse réseau identifie un appareil réseau spécifique. Le numéro de port identifie le service spécifique sur cet appareil auquel se connecter. La combinaison de l’adresse réseau et du port de service est appelée point de terminaison, qui est représenté dans .NET par la classe EndPoint. Un descendant du point de terminaison (EndPoint) est défini pour chaque famille d’adresses prise en charge. Pour la famille d’adresses IP, il s’agit de la classe IPEndPoint.

La classe Dns fournit des services de nom de domaine aux applications qui utilisent des services Internet TCP/IP. La méthode GetHostEntryAsync interroge un serveur DNS pour mapper un nom de domaine convivial (par exemple, « host.contoso.com ») à une adresse Internet numérique (par exemple, 192.168.1.1). GetHostEntryAsync retourne un Task<IPHostEntry> qui contient, quand il est attendu, une liste des adresses et alias pour le nom demandé. Dans la plupart des cas, vous pouvez utiliser la première adresse retournée dans le tableau AddressList. Le code suivant obtient une IPAddress contenant l’adresse IP du serveur host.contoso.com.

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

Conseil

Pour les tests et le débogage manuels, vous pouvez généralement utiliser la méthode GetHostEntryAsync avec le nom d’hôte résultant de la valeur Dns.GetHostName() pour résoudre le nom d’hôte local en adresse IP. Prenez l'exemple de l'extrait de code suivant :

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

L’IANA (Internet Assigned Numbers Authority) définit les numéros de port des services courants. Pour plus d’informations, consultez IANA: Service Name and Transport Protocol Port Number Registry). Les autres services peuvent avoir un numéro de port compris dans la plage 1 024 à 65 535. Le code suivant combine l’adresse IP de host.contoso.com avec un numéro de port afin de créer un point de terminaison distant pour une connexion.

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

Après avoir déterminé l’adresse de l’appareil distant et choisi le port à utiliser pour la connexion, l’application peut établir une connexion avec l’appareil distant.

Créez une classe TcpClient

La classe TcpClient fournit des services TCP à un niveau d’abstraction plus élevé que la classe Socket. TcpClient est utilisé pour connecter un client à un hôte distant. En sachant comment obtenir un IPEndPoint, supposons que vous avez une IPAddress à coupler avec le numéro de port de votre choix. L’exemple suivant montre comment configurer un TcpClient pour qu’il se connecte à un serveur de temps sur le port TCP 13 :

var ipEndPoint = new IPEndPoint(ipAddress, 13);

using TcpClient client = new();
await client.ConnectAsync(ipEndPoint);
await using NetworkStream stream = client.GetStream();

var buffer = new byte[1_024];
int received = await stream.ReadAsync(buffer);

var message = Encoding.UTF8.GetString(buffer, 0, received);
Console.WriteLine($"Message received: \"{message}\"");
// Sample output:
//     Message received: "📅 8/22/2022 9:07:17 AM 🕛"

Le code C# précédent :

  • Crée un IPEndPoint à partir d’une IPAddress et d’un port connus.
  • Instancie un nouvel objet TcpClient.
  • Connecte le client au serveur de temps TCP distant sur le port 13 en utilisant TcpClient.ConnectAsync.
  • Utilise un NetworkStream pour lire les données de l’hôte distant.
  • Déclare un tampon de lecture de 1_024 octets.
  • Lit les données de stream dans le tampon de lecture.
  • Écrit les résultats sous forme de chaîne dans la console.

Comme le client sait que le message est petit, le message entier peut être lu dans le tampon de lecture en une seule opération. Avec des messages plus grands ou de longueur indéterminée, le client doit utiliser le tampon de manière plus appropriée et lire dans une boucle while.

Important

Pour l’envoi et la réception de messages, l’Encoding doit être connu à l’avance pour le serveur et le client. Par exemple, si le serveur communique avec ASCIIEncoding, mais que le client tente d’utiliser UTF8Encoding, les messages sont mal formés.

Créez une classe TcpListener

Le type TcpListener permet de monitorer les demandes entrantes sur un port TCP, puis de créer un Socket ou un TcpClient qui gère la connexion au client. La méthode Start active l’écoute sur le port et la méthode Stop la désactive. La méthode AcceptTcpClientAsync accepte les demandes de connexion entrantes et crée un TcpClient qui gère la demande. La méthode AcceptSocketAsync accepte les demandes de connexion entrantes et crée un Socket pour gérer la demande.

L’exemple suivant montre comment créer un serveur de temps réseau en utilisant un TcpListener pour surveiller le port TCP 13. Quand une demande de connexion entrante est acceptée, le serveur de temps répond en retournant la date et l’heure actuelles du serveur hôte.

var ipEndPoint = new IPEndPoint(IPAddress.Any, 13);
TcpListener listener = new(ipEndPoint);

try
{    
    listener.Start();

    using TcpClient handler = await listener.AcceptTcpClientAsync();
    await using NetworkStream stream = handler.GetStream();

    var message = $"📅 {DateTime.Now} 🕛";
    var dateTimeBytes = Encoding.UTF8.GetBytes(message);
    await stream.WriteAsync(dateTimeBytes);

    Console.WriteLine($"Sent message: \"{message}\"");
    // Sample output:
    //     Sent message: "📅 8/22/2022 9:07:17 AM 🕛"
}
finally
{
    listener.Stop();
}

Le code C# précédent :

  • Crée un IPEndPoint avec une IPAddress.Any et un port.
  • Instancie un nouvel objet TcpListener.
  • Appelle la méthode Start pour commencer à écouter sur le port.
  • Utilise un TcpClient de la méthode AcceptTcpClientAsync pour accepter les demandes de connexion entrantes.
  • Encode la date et l’heure actuelles dans un message de chaîne.
  • Utilise un NetworkStream pour écrire des données dans le client connecté.
  • Écrit le message envoyé dans la console.
  • Enfin, appelle la méthode Stop pour arrêter l’écoute sur le port.

Terminer le contrôle TCP avec la classe Socket

TcpClient et TcpListener s’appuient en interne sur la classe Socket, ce qui signifie que tout ce que vous pouvez faire avec ces classes peut être obtenu directement en utilisant des sockets. Cette section décrit plusieurs cas d’usage de TcpClient et TcpListener, ainsi que leur équivalent fonctionnel Socket.

Créer un socket de client

Le constructeur par défaut de TcpClient tente de créer un socket à double pile via le constructeur Socket(SocketType, ProtocolType). Ce constructeur crée un socket à double pile si IPv6 est pris en charge, sinon, il utilise IPv4.

Prenons le code client TCP suivant :

using var client = new TcpClient();

Le code client TCP précédent est fonctionnellement équivalent au code de socket suivant :

using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);

Constructeur TcpClient(AddressFamily)

Ce constructeur accepte seulement trois valeurs AddressFamily, sinon il lève une ArgumentException. Les valeurs autorisées sont :

Prenons le code client TCP suivant :

using var client = new TcpClient(AddressFamily.InterNetwork);

Le code client TCP précédent est fonctionnellement équivalent au code de socket suivant :

using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Constructeur TcpClient(IPEndPoint)

Après la création du socket, ce constructeur fait la liaison également avec le IPEndPointlocal fourni. La propriété IPEndPoint.AddressFamily est utilisée pour déterminer la famille d’adresses du socket.

Prenons le code client TCP suivant :

var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5001);
using var client = new TcpClient(endPoint);

Le code client TCP précédent est fonctionnellement équivalent au code de socket suivant :

// Example IPEndPoint object
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5001);
using var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(endPoint);

Constructeur TcpClient(String, Int32)

Ce constructeur tente de créer une double pile similaire au constructeur par défaut et de la connecter au point de terminaison DNS distant défini par la paire hostname et port.

Prenons le code client TCP suivant :

using var client = new TcpClient("www.example.com", 80);

Le code client TCP précédent est fonctionnellement équivalent au code de socket suivant :

using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);

Se connecter au serveur

Toutes les surcharges Connect, ConnectAsync, BeginConnect et EndConnect dans TcpClient sont fonctionnellement équivalentes aux méthodes Socket correspondantes.

Prenons le code client TCP suivant :

using var client = new TcpClient();
client.Connect("www.example.com", 80);

Le code TcpClient ci-dessus est équivalent au code de socket suivant :

using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);

Créer un socket de serveur

Tout comme les instances TcpClient qui ont une équivalence fonctionnelle avec leurs homologues bruts Socket, cette section mappe les constructeurs TcpListener à leur code de socket correspondant. Le premier constructeur à prendre en compte est TcpListener(IPAddress localaddr, int port).

var listener = new TcpListener(IPAddress.Loopback, 5000);

Le code d’écouteur TCP précédent est fonctionnellement équivalent au code de socket suivant :

var ep = new IPEndPoint(IPAddress.Loopback, 5000);
using var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

Démarrer l’écoute sur le serveur

La méthode Start() est un wrapper combinant les fonctionnalités Bind et Listen() de Socket.

Prenons le code d’écouteur TCP suivant :

var listener = new TcpListener(IPAddress.Loopback, 5000);
listener.Start(10);

Le code d’écouteur TCP précédent est fonctionnellement équivalent au code de socket suivant :

var endPoint = new IPEndPoint(IPAddress.Loopback, 5000);
using var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(endPoint);
try
{
    socket.Listen(10);
}
catch (SocketException)
{
    socket.Dispose();
}

Accepter une connexion de serveur

En coulisses, les connexions TCP entrantes créent toujours un socket quand elles sont acceptées. TcpListener peut accepter une instance Socket directement (via AcceptSocket() ou AcceptSocketAsync()) ou accepter un TcpClient (via AcceptTcpClient() et AcceptTcpClientAsync()).

Prenons le code TcpListener suivant :

var listener = new TcpListener(IPAddress.Loopback, 5000);
using var acceptedSocket = await listener.AcceptSocketAsync();

// Synchronous alternative.
// var acceptedSocket = listener.AcceptSocket();

Le code d’écouteur TCP précédent est fonctionnellement équivalent au code de socket suivant :

var endPoint = new IPEndPoint(IPAddress.Loopback, 5000);
using var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
using var acceptedSocket = await socket.AcceptAsync();

// Synchronous alternative
// var acceptedSocket = socket.Accept();

Créer un NetworkStream pour envoyer et recevoir des données

Avec TcpClient, vous devez instancier un NetworkStream avec la méthode GetStream() pour pouvoir envoyer et recevoir des données. Avec Socket, vous devez créer le NetworkStream manuellement.

Prenons le code TcpClient suivant :

using var client = new TcpClient();
using NetworkStream stream = client.GetStream();

Il est équivalent au code de socket suivant :

using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);

// Be aware that transferring the ownership means that closing/disposing the stream will also close the underlying socket.
using var stream = new NetworkStream(socket, ownsSocket: true);

Conseil

Si votre code n’a pas besoin de fonctionner avec une instance Stream, vous pouvez utiliser directement les méthodes d’envoi/de réception de Socket (Send, SendAsync, Receive et ReceiveAsync) au lieu de créer un NetworkStream.

Voir aussi