Übersicht über TCP
Wichtig
Die Socket-Klasse wird dringend für fortgeschrittene Benutzer anstelle von TcpClient
und TcpListener
empfohlen.
Um mit TCP (Transmission Control Protocol) zu arbeiten, haben Sie zwei Optionen: Entweder verwenden Sie Socket für maximale Kontrolle und Leistung, oder Sie die verwenden die Hilfsklassen TcpClient und TcpListener. TcpClient und TcpListener bauen auf der System.Net.Sockets.Socket-Klasse auf und kümmern sich um die Details der Datenübertragung, um die Benutzung zu erleichtern.
Die Protokollklassen verwenden die zugrunde liegende Socket
-Klasse, um einfachen Zugriff auf Netzwerkdienste bereitzustellen, ohne Mehraufwand für die Verwaltung von Statusinformationen oder Kenntnis der Details zum Einrichten der protokollspezifischen Sockets. Für die Verwendung der asynchronen Socket
-Methoden können Sie die asynchronen Methoden nutzen, die von der NetworkStream-Klasse bereitgestellt werden. Zum Zugriff auf Funktionen der Socket
-Klasse, die nicht von den Protokollklassen verfügbar gemacht werden, müsse Sie die Socket
-Klasse verwenden.
TcpClient
und TcpListener
stellen das Netzwerk mithilfe der NetworkStream
-Klasse dar. Verwenden Sie die GetStream-Methode zum Zurückgeben des Netzwerkstreams, und rufen Sie anschließend die NetworkStream.ReadAsync- und NetworkStream.WriteAsync-Methoden des Streams auf. Der NetworkStream
besitzt den den Protokollklassen zugrunde liegenden Socket nicht, sodass der Abschluss den Socket nicht betrifft.
Verwenden von TcpClient
und TcpListener
Die TcpClient-Klasse fordert über TCP Daten von einer Internetressource an. Die Eigenschaften und Methoden von TcpClient
abstrahieren die Details zum Erstellen einer Socket zum Anfordern und Empfangen von Daten mithilfe von TCP. Da die Verbindung mit dem Remotegerät als Datenstrom dargestellt wird, können Daten mit Techniken zur Datenstromverarbeitung von .NET Framework gelesen und geschrieben werden.
Das TCP-Protokoll stellt eine Verbindung mit einem Remoteendpunkt her, und verwendet dann diese Verbindung zum Senden und Empfangen von Datenpaketen. TCP ist dafür verantwortlich, dass die Datenpakete an den Endpunkt gesendet und in der richtigen Reihenfolge zusammengestellt werden, wenn sie ankommen.
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 der Ermittlung der Adresse des Remotegeräts und der Auswahl eines Ports für die Verbindung, kann die App versuchen, eine Verbindung mit dem Remotegerät herzustellen.
Erstellen der Datei TcpClient
Die TcpClient
-Klasse stellt TCP-Dienste auf einer höheren Abstraktionsebene bereit als die Socket
-Klasse. TcpClient
wird verwendet, um eine Clientverbindung mit einem Remotehost herzustellen. Wenn Sie wissen, wie Sie einen IPEndPoint
abrufen, nehmen wir an, dass Sie über eine IPAddress
zum Koppeln mit Ihrer gewünschten Portnummer verfügen. Das folgende Beispiel veranschaulicht das Festlegen eines TcpClient
zur Verbindung mit einem Zeitserver an TCP-Port 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 🕛"
Für den C#-Code oben gilt:
- Erstellt einen
IPEndPoint
aus einer bekanntenIPAddress
und einem Port. - Instanziiert ein neues
TcpClient
-Objekt. - Verbindet
client
unter Verwendung von TcpClient.ConnectAsync mit dem TCP-Remotezeitserver an Port 13. - Verwendet einen NetworkStream, um Daten vom Remotehost zu lesen.
- Deklariert einen Lesepuffer von
1_024
Bytes. - Liest Daten aus dem
stream
in den Lesepuffer. - Schreibt die Ergebnisse als Zeichenfolge in die Konsole.
Da der Client weiß, dass die Nachricht klein ist, kann die gesamte Nachricht in einem Vorgang in den Lesepuffer gelesen werden. Bei größeren Nachrichten oder Nachrichten mit unbestimmter Länge sollte der Client den Puffer effizienter verwenden und in einer while
-Schleife lesen.
Wichtig
Beim Senden und Empfangen von Nachrichten sollte sowohl dem Server als auch dem Client das Encoding im Voraus bekannt sein. Wenn der Server beispielsweise mit ASCIIEncoding kommuniziert, aber der Client versucht, UTF8Encoding zu verwenden, werden die Nachrichten falsch formatiert.
Erstellen der Datei TcpListener
Der Typ TcpListener wird verwendet, um einen TCP-Port auf eingehende Anforderungen zu überwachen, und entweder einen Socket
oder TcpClient
zu erstellen, der die Verbindung mit dem Client verwaltet. Die Start-Methode ermöglicht die Überwachung, und die Stop-Methode deaktiviert die Überwachung des Ports. Die AcceptTcpClientAsync-Methode akzeptiert eingehende Verbindungsanforderungen und erstellt einen TcpClient
zur Verarbeitung der Anforderung. Die AcceptSocketAsync-Methode akzeptiert eingehende Verbindungsanforderungen und erstellt einen Socket
zur Verarbeitung der Anforderung.
Das folgende Beispiel veranschaulicht das Erstellen eines Netzwerkzeitservers mithilfe eines TcpListener
zur Überwachung des TCP-Ports 13. Wenn eine eingehende Verbindungsanforderung akzeptiert wird, antwortet der Zeitserver mit dem aktuellen Datum und der Uhrzeit vom Hostserver.
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();
}
Für den C#-Code oben gilt:
- Erstellt einen
IPEndPoint
mit IPAddress.Any und einen Port. - Instanziiert ein neues
TcpListener
-Objekt. - Ruft die Start-Methode auf, um mit dem Lauschen am Port zu beginnen.
- Verwendet einen
TcpClient
aus der AcceptTcpClientAsync-Methode, um eingehende Verbindungsanforderungen zu akzeptieren. - Codiert das aktuelle Datum und die aktuelle Uhrzeit als Zeichenfolgennachricht.
- Verwendet einen NetworkStream, um Daten in den verbundenen Client zu schreiben.
- Schreibt die gesendete Nachricht in die Konsole.
- Ruft schließlich die Stop-Methode auf, um das Lauschen am Port zu beenden.
Endliche TCP-Steuerung mit der Socket
-Klasse
Sowohl TcpClient
als auch TcpListener
stützen sich intern auf die Socket
-Klasse, was bedeutet, dass alles, was Sie mit diesen Klassen erledigen können, auch direkt mit Sockets erreicht werden kann. In diesem Abschnitt werden mehrere Anwendungsfälle für TcpClient
und TcpListener
sowie deren funktional gleichwertige Socket
-Entsprechung gezeigt.
Erstellen eines Clientsockets
Der Standardkonstruktor von TcpClient
versucht, einen Dual-Stack-Socket über den Konstruktor Socket(SocketType, ProtocolType) zu erstellen. Dieser Konstruktor erstellt einen Dual-Stack-Socket, wenn IPv6 unterstützt wird. Andernfalls wird auf IPv4 zurückgegriffen.
Betrachten Sie den folgenden TCP-Clientcode:
using var client = new TcpClient();
Der TCP-Clientcode oben entspricht funktionell dem folgenden Socketcode:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
Der TcpClient(AddressFamily)-Konstruktor
Dieser Konstruktor akzeptiert nur drei AddressFamily
-Werte, andernfalls wird eine ArgumentException ausgelöst. Gültige Werte sind:
- AddressFamily.InterNetwork: für IPv4-Socket.
- AddressFamily.InterNetworkV6: für IPv6-Socket.
- AddressFamily.Unknown: Hiermit wird versucht, einen Dual-Stack-Socket zu erstellen, ähnlich wie beim Standardkonstruktor.
Betrachten Sie den folgenden TCP-Clientcode:
using var client = new TcpClient(AddressFamily.InterNetwork);
Der TCP-Clientcode oben entspricht funktionell dem folgenden Socketcode:
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Der TcpClient(IPEndPoint)-Konstruktor
Beim Erstellen des Sockets wird dieser Konstruktor auch an das bereitgestellte lokale IPEndPoint
-Element gebunden. Die IPEndPoint.AddressFamily-Eigenschaft wird verwendet, um die Adressfamilie des Sockets zu bestimmen.
Betrachten Sie den folgenden TCP-Clientcode:
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5001);
using var client = new TcpClient(endPoint);
Der TCP-Clientcode oben entspricht funktionell dem folgenden Socketcode:
// 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);
Der TcpClient(String, Int32)-Konstruktor
Dieser Konstruktor versucht, einen dualen Stapel ähnlich wie der Standardkonstruktor zu erstellen und ihn mit dem DNS-Remoteendpunkt zu verbinden, der durch das Paar hostname
und port
definiert wird.
Betrachten Sie den folgenden TCP-Clientcode:
using var client = new TcpClient("www.example.com", 80);
Der TCP-Clientcode oben entspricht funktionell dem folgenden Socketcode:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);
Mit Server verbinden
Alle Connect
-, ConnectAsync
-, BeginConnect
- und EndConnect
-Überladungen in TcpClient
sind funktionell mit den entsprechenden Socket
-Methoden gleichwertig.
Betrachten Sie den folgenden TCP-Clientcode:
using var client = new TcpClient();
client.Connect("www.example.com", 80);
Der TcpClient
-Code oben entspricht dem folgenden Socketcode:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);
Erstellen eines Serversockets
Ähnlich wie TcpClient
-Instanzen funktionale Äquivalenz mit ihren Socket
-Raw-Gegenstücken aufweisen, ordnet dieser Abschnitt TcpListener
-Konstruktoren ihrem entsprechenden Socketcode zu. Der erste zu berücksichtigende Konstruktor ist TcpListener(IPAddress localaddr, int port)
.
var listener = new TcpListener(IPAddress.Loopback, 5000);
Der TCP-Listener oben entspricht funktionell dem folgenden Socketcode:
var ep = new IPEndPoint(IPAddress.Loopback, 5000);
using var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
Starten des Lauschvorgangs auf dem Server
Die Start()-Methode ist ein Wrapper, der die Bind- und Listen()-Funktionalität von Socket
kombiniert.
Betrachten Sie den folgenden TCP-Listenercode:
var listener = new TcpListener(IPAddress.Loopback, 5000);
listener.Start(10);
Der TCP-Listener oben entspricht funktionell dem folgenden Socketcode:
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();
}
Annehmen einer Serververbindung
Im Hintergrund erstellen eingehende TCP-Verbindungen immer einen neuen Socket, wenn sie akzeptiert werden. TcpListener
kann eine Socket-Instanz direkt (über AcceptSocket() oder AcceptSocketAsync()) oder einen TcpClient (über AcceptTcpClient() und AcceptTcpClientAsync()) akzeptieren.
Betrachten Sie folgenden TcpListener
-Code:
var listener = new TcpListener(IPAddress.Loopback, 5000);
using var acceptedSocket = await listener.AcceptSocketAsync();
// Synchronous alternative.
// var acceptedSocket = listener.AcceptSocket();
Der TCP-Listener oben entspricht funktionell dem folgenden Socketcode:
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();
Erstellen eines NetworkStream
zum Senden und Empfangen von Daten
Mit TcpClient
müssen Sie einen NetworkStream mit der GetStream()-Methode instanziieren, um Daten senden und empfangen zu können. Mit Socket
müssen Sie die NetworkStream
-Erstellung manuell durchführen.
Betrachten Sie folgenden TcpClient
-Code:
using var client = new TcpClient();
using NetworkStream stream = client.GetStream();
Er entspricht dem folgenden Socketcode:
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);
Tipp
Wenn Ihr Code nicht mit einer Stream-Instanz arbeiten muss, können Sie sich direkt auf die Methoden zum Senden und Empfangen (Send, SendAsync, Receive und ReceiveAsync) von Socket
berufen, anstatt einen NetworkStream zu erstellen.