Omówienie protokołu TCP
Ważne
Klasa jest zdecydowanie zalecana Socket dla zaawansowanych użytkowników, a nie TcpClient
i TcpListener
.
Aby pracować z protokołem Transmission Control Protocol (TCP), dostępne są dwie opcje: użyj Socket dla maksymalnej kontroli i wydajności lub użyj TcpClient klas pomocnika i .TcpListener TcpClienti są oparte na System.Net.Sockets.Socket klasie i TcpListener dbają o szczegóły przesyłania danych w celu ułatwienia użytkowania.
Klasy protokołów używają podstawowej Socket
klasy do zapewnienia prostego dostępu do usług sieciowych bez konieczności utrzymywania informacji o stanie lub znajomości szczegółów konfigurowania gniazd specyficznych dla protokołu. Aby użyć metod asynchronicznych Socket
, można użyć metod asynchronicznych dostarczonych przez klasę NetworkStream . Aby uzyskać dostęp do funkcji Socket
klasy, które nie są uwidocznione przez klasy protokołów, należy użyć Socket
klasy .
TcpClient
i TcpListener
reprezentują sieć przy użyciu NetworkStream
klasy . Metoda służy GetStream do zwracania strumienia sieciowego, a następnie wywoływania metod i NetworkStream.WriteAsync strumieniaNetworkStream.ReadAsync. Element NetworkStream
nie jest właścicielem bazowego gniazda klas protokołów, więc zamknięcie nie ma wpływu na gniazdo.
Używanie i TcpClient
TcpListener
Klasa TcpClient żąda danych z zasobu internetowego przy użyciu protokołu TCP. Metody i właściwości abstrakcyjne TcpClient
szczegóły tworzenia Socket obiektu do żądania i odbierania danych przy użyciu protokołu TCP. Ponieważ połączenie z urządzeniem zdalnym jest reprezentowane jako strumień, dane można odczytywać i zapisywać za pomocą technik obsługi strumienia programu .NET Framework.
Protokół TCP ustanawia połączenie z zdalnym punktem końcowym, a następnie używa tego połączenia do wysyłania i odbierania pakietów danych. Protokół TCP jest odpowiedzialny za zapewnienie, że pakiety danych są wysyłane do punktu końcowego i montowane w odpowiedniej kolejności po ich nadejściu.
Tworzenie punktu końcowego adresu IP
Podczas pracy z usługą System.Net.Socketsreprezentujesz punkt końcowy sieci jako IPEndPoint obiekt. Obiekt IPEndPoint
jest skonstruowany z numerem IPAddress i odpowiadającym mu numerem portu. Zanim będzie można zainicjować konwersację za pośrednictwem elementu , należy utworzyć potok danych między aplikacją Socketa zdalnym miejscem docelowym.
Protokół TCP/IP używa adresu sieciowego i numeru portu usługi, aby jednoznacznie zidentyfikować usługę. Adres sieciowy identyfikuje określone miejsce docelowe sieci; numer portu identyfikuje konkretną usługę na tym urządzeniu do nawiązania połączenia. Połączenie adresu sieciowego i portu usługi jest nazywane punktem końcowym, który jest reprezentowany na platformie .NET przez klasę EndPoint . Element potomny elementu jest definiowany dla każdej obsługiwanej EndPoint
rodziny adresów; dla rodziny adresów IP klasa to IPEndPoint.
Klasa Dns udostępnia usługi nazw domen dla aplikacji korzystających z usług internetowych TCP/IP. Metoda GetHostEntryAsync wysyła zapytanie do serwera DNS, aby zamapować przyjazną dla użytkownika nazwę domeny (na przykład "host.contoso.com") na numeryczny adres internetowy (na przykład 192.168.1.1
). GetHostEntryAsync
Zwraca element Task<IPHostEntry>
, który w przypadku oczekiwania zawiera listę adresów i aliasów dla żądanej nazwy. W większości przypadków można użyć pierwszego adresu zwróconego w tablicy AddressList . Poniższy kod pobiera IPAddress adres IP serwera host.contoso.com
.
IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("host.contoso.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
Napiwek
Do celów testowania ręcznego i debugowania zazwyczaj można użyć GetHostEntryAsync metody z wynikową nazwą hosta z Dns.GetHostName() wartości, aby rozpoznać nazwę hosta lokalnego na adres IP. Rozważmy następujący fragment kodu:
var hostName = Dns.GetHostName();
IPHostEntry localhost = await Dns.GetHostEntryAsync(hostName);
// This is the IP address of the local machine
IPAddress localIpAddress = localhost.AddressList[0];
Urząd IANA (Internet Assigned Numbers Authority) definiuje numery portów dla typowych usług. Aby uzyskać więcej informacji, zobacz IANA: Service Name and Transport Protocol Port Number Registry (IANA: nazwa usługi i rejestr numerów portów protokołu transportu). Inne usługi mogą mieć zarejestrowane numery portów w zakresie od 1024 do 65 535. Poniższy kod łączy adres IP z host.contoso.com
numerem portu w celu utworzenia zdalnego punktu końcowego dla połączenia.
IPEndPoint ipEndPoint = new(ipAddress, 11_000);
Po określeniu adresu urządzenia zdalnego i wybraniu portu do użycia dla połączenia aplikacja może nawiązać połączenie z urządzeniem zdalnym.
Utwórz TcpClient
Klasa TcpClient
udostępnia usługi TCP na wyższym poziomie abstrakcji niż Socket
klasa. TcpClient
służy do tworzenia połączenia klienta z hostem zdalnym. Wiedząc, jak uzyskać element IPEndPoint
, załóżmy, że masz element IPAddress
do parowania z żądanym numerem portu. W poniższym przykładzie pokazano konfigurowanie TcpClient
elementu w celu nawiązania połączenia z serwerem czasu na porcie 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 🕛"
Poprzedni kod języka C#:
- Tworzy obiekt
IPEndPoint
na podstawie znanegoIPAddress
portu i . - Utwórz wystąpienie nowego
TcpClient
obiektu. - Nawiązuje połączenie z
client
zdalnym serwerem czasu TCP na porcie 13 przy użyciu polecenia TcpClient.ConnectAsync. - Używa elementu do NetworkStream odczytu danych z hosta zdalnego.
- Deklaruje bufor odczytu bajtów
1_024
. - Odczytuje dane z
stream
buforu odczytu do buforu odczytu. - Zapisuje wyniki jako ciąg w konsoli programu .
Ponieważ klient wie, że komunikat jest mały, cały komunikat można odczytać do buforu odczytu w jednej operacji. W przypadku większych komunikatów lub komunikatów o nieokreślonej długości klient powinien używać buforu bardziej odpowiednio i odczytywać while
w pętli.
Ważne
Podczas wysyłania i odbierania komunikatów Encoding powinien być znany przed upływem czasu zarówno do serwera, jak i klienta. Jeśli na przykład serwer komunikuje się przy użyciu programu ASCIIEncoding , ale klient spróbuje użyć UTF8Encodingpolecenia , komunikaty będą źle sformułowane.
Utwórz TcpListener
Typ TcpListener służy do monitorowania portu TCP dla żądań przychodzących, a następnie utworzenia elementu Socket
lub TcpClient
, który zarządza połączeniem z klientem. Metoda Start włącza nasłuchiwanie, a Stop metoda wyłącza nasłuchiwanie na porcie. Metoda AcceptTcpClientAsync akceptuje przychodzące żądania połączenia i tworzy obiekt TcpClient
w celu obsługi żądania, a AcceptSocketAsync metoda akceptuje przychodzące żądania połączenia i tworzy obiekt Socket
do obsługi żądania.
W poniższym przykładzie pokazano tworzenie serwera czasu sieciowego przy użyciu polecenia TcpListener
do monitorowania portu TCP 13. Po zaakceptowaniu przychodzącego żądania połączenia serwer czasu odpowiada z bieżącą datą i godziną z serwera hosta.
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();
}
Poprzedni kod języka C#:
- Tworzy obiekt
IPEndPoint
z portem IPAddress.Any i . - Utwórz wystąpienie nowego
TcpListener
obiektu. - Wywołuje metodę Start , aby rozpocząć nasłuchiwanie na porcie.
TcpClient
Używa metody z AcceptTcpClientAsync metody do akceptowania przychodzących żądań połączeń.- Koduje bieżącą datę i godzinę jako komunikat ciągu.
- Używa obiektu do zapisywania NetworkStream danych na połączonym kliencie.
- Zapisuje wysłaną wiadomość do konsoli programu .
- Na koniec wywołuje metodę Stop , aby zatrzymać nasłuchiwanie na porcie.
Skończona kontrolka TCP z klasą Socket
Zarówno, TcpClient
jak i TcpListener
wewnętrznie polegaj na Socket
klasie, co oznacza, że wszystko, co można zrobić z tymi klasami, można osiągnąć bezpośrednio przy użyciu gniazd. W tej sekcji przedstawiono kilka TcpClient
przypadków użycia oraz TcpListener
ich Socket
odpowiednik, który jest funkcjonalnie równoważny.
Tworzenie gniazda klienta
TcpClient
Domyślny konstruktor próbuje utworzyć gniazdo podwójne stosu za pomocą konstruktora Socket(SocketType, ProtocolType). Ten konstruktor tworzy gniazdo dwustosowe, jeśli protokół IPv6 jest obsługiwany, w przeciwnym razie wraca do protokołu IPv4.
Rozważmy następujący kod klienta TCP:
using var client = new TcpClient();
Powyższy kod klienta TCP jest funkcjonalnie równoważny z następującym kodem gniazda:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
Konstruktor TcpClient(AddressFamily)
Ten konstruktor akceptuje tylko trzy AddressFamily
wartości. W przeciwnym razie zwróci wartość ArgumentException. Prawidłowe wartości to:
- AddressFamily.InterNetwork: dla gniazda IPv4.
- AddressFamily.InterNetworkV6: dla gniazda IPv6.
- AddressFamily.Unknown: spowoduje to próbę utworzenia gniazda z dwoma stosami, podobnie jak w przypadku konstruktora domyślnego.
Rozważmy następujący kod klienta TCP:
using var client = new TcpClient(AddressFamily.InterNetwork);
Powyższy kod klienta TCP jest funkcjonalnie równoważny z następującym kodem gniazda:
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Konstruktor TcpClient(IPEndPoint)
Podczas tworzenia gniazda ten konstruktor będzie również wiązać się z podanym lokalnym IPEndPoint
elementem . Właściwość IPEndPoint.AddressFamily służy do określania rodziny adresów gniazda.
Rozważmy następujący kod klienta TCP:
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5001);
using var client = new TcpClient(endPoint);
Powyższy kod klienta TCP jest funkcjonalnie równoważny z następującym kodem gniazda:
// 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);
Konstruktor TcpClient(String, Int32)
Ten konstruktor spróbuje utworzyć podwójny stos podobny do konstruktora domyślnego i połączyć go z zdalnym punktem końcowym DNS zdefiniowanym przez hostname
parę iport
.
Rozważmy następujący kod klienta TCP:
using var client = new TcpClient("www.example.com", 80);
Powyższy kod klienta TCP jest funkcjonalnie równoważny z następującym kodem gniazda:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);
Nawiąż połączenie z serwerem
Wszystkie Connect
przeciążenia , BeginConnect
ConnectAsync
i EndConnect
w systemie TcpClient
są funkcjonalnie równoważne odpowiednim Socket
metodom.
Rozważmy następujący kod klienta TCP:
using var client = new TcpClient();
client.Connect("www.example.com", 80);
Powyższy TcpClient
kod jest odpowiednikiem następującego kodu gniazda:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);
Tworzenie gniazda serwera
Podobnie jak TcpClient
wystąpienia mające równoważność funkcjonalną ze swoimi nieprzetworzonymi Socket
odpowiednikami, ta sekcja mapuje TcpListener
konstruktory na odpowiadający im kod gniazda. Pierwszym konstruktorem, który należy wziąć pod uwagę, jest .TcpListener(IPAddress localaddr, int port)
var listener = new TcpListener(IPAddress.Loopback, 5000);
Powyższy kod odbiornika TCP jest funkcjonalnie odpowiednikiem następującego kodu gniazda:
var ep = new IPEndPoint(IPAddress.Loopback, 5000);
using var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
Rozpocznij nasłuchiwanie na serwerze
Metoda Start() jest otoką łączącą Socket
funkcje i sBind.Listen()
Rozważmy następujący kod odbiornika TCP:
var listener = new TcpListener(IPAddress.Loopback, 5000);
listener.Start(10);
Powyższy kod odbiornika TCP jest funkcjonalnie odpowiednikiem następującego kodu gniazda:
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();
}
Akceptowanie połączenia z serwerem
Pod maską przychodzące połączenia TCP zawsze tworzą nowe gniazdo po zaakceptowaniu. TcpListener
może zaakceptować Socket wystąpienie bezpośrednio (za pośrednictwem lub AcceptSocketAsync()) lub zaakceptować TcpClient element (za pośrednictwem AcceptSocket() i AcceptTcpClient() AcceptTcpClientAsync()).
Rozważ następujący TcpListener
kod:
var listener = new TcpListener(IPAddress.Loopback, 5000);
using var acceptedSocket = await listener.AcceptSocketAsync();
// Synchronous alternative.
// var acceptedSocket = listener.AcceptSocket();
Powyższy kod odbiornika TCP jest funkcjonalnie odpowiednikiem następującego kodu gniazda:
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();
Tworzenie elementu NetworkStream
do wysyłania i odbierania danych
Aby TcpClient
móc wysyłać i odbierać dane, należy utworzyć wystąpienie NetworkStream obiektu za GetStream() pomocą metody . W programie Socket
należy ręcznie utworzyć NetworkStream
plik .
Rozważ następujący TcpClient
kod:
using var client = new TcpClient();
using NetworkStream stream = client.GetStream();
Co jest równoważne następującemu kodowi gniazda:
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);
Napiwek
Jeśli kod nie musi pracować z wystąpieniem Stream , możesz opierać się na Socket
metodach Send/Receive (Send, SendAsync, Receive i ReceiveAsync) bezpośrednio zamiast tworzyć NetworkStream.