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’uneIPAddress
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 :
- AddressFamily.InterNetwork : pour le socket IPv4.
- AddressFamily.InterNetworkV6 : pour le socket IPv6.
- AddressFamily.Unknown : tente de créer un socket à double pile, de la même façon que le constructeur par défaut.
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 va également lier au IPEndPoint
local 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.