A TCP áttekintése

Fontos

Az Socket osztály kifejezetten ajánlott a haladó felhasználók számára, ahelyett, hogy a TcpClient és TcpListener osztályokat használják.

A Transmission Control Protocol (TCP) használatához két lehetősége van: vagy a maximális vezérlés és teljesítmény érdekében használja a Socket-t, vagy alkalmazza a TcpClient és TcpListener segédosztályokat. TcpClient és TcpListener az osztályra System.Net.Sockets.Socket épülnek, és a könnyű használat érdekében gondoskodnak az adatok átvitelének részleteiről.

A protokollosztályok az alapul szolgáló Socket osztály használatával egyszerű hozzáférést biztosítanak a hálózati szolgáltatásokhoz az állapotinformációk fenntartása vagy a protokollspecifikus szoftvercsatornák beállításának részleteinek ismerete nélkül. Az aszinkron Socket metódusok használatához használhatja az osztály által NetworkStream biztosított aszinkron metódusokat. A Socket osztály olyan funkcióinak eléréséhez, amelyeket a protokollosztályok nem tesznek elérhetővé, az Socket osztályt kell használnia.

TcpClient és TcpListener a NetworkStream osztály használatával reprezentálják a hálózatot. A GetStream metódust használja a hálózati stream visszaadására, majd meghívja a stream NetworkStream.ReadAsync és NetworkStream.WriteAsync metódusait. A NetworkStream protokollosztályok alapjául szolgáló szoftvercsatornának nincs tulajdonosa, ezért a lezárás nem befolyásolja a szoftvercsatornát.

Használja TcpClient és TcpListener

Az TcpClient osztály TCP használatával kér adatokat egy internetes erőforrástól. Az TcpClient módszerei és tulajdonságai elvonatkoztatnak a TCP-t használó adatkérésekhez és -fogadásokhoz szükséges Socket létrehozásának részleteiről. Mivel a távoli eszközhöz való kapcsolat streamként van ábrázolva, az adatok .NET-keretrendszer streamkezelési technikákkal olvashatók és írhatók.

A TCP protokoll kapcsolatot létesít egy távoli végponttal, majd ezt a kapcsolatot használja adatcsomagok küldéséhez és fogadásához. A TCP feladata annak biztosítása, hogy az adatcsomagok a végpontra érkezve a megfelelő sorrendben legyenek összeállítva.

IP-végpont létrehozása

Amikor a System.Net.Sockets-val dolgozik, egy hálózati végpontot IPEndPoint objektumként ábrázol. A IPEndPoint felépítése egy IPAddress és annak megfelelő portszámmal történik. Mielőtt beszélgetést kezdeményezhet egy Socketalkalmazáson keresztül, hozzon létre egy adatcsatornát az alkalmazás és a távoli cél között.

A TCP/IP egy hálózati címet és egy szolgáltatásportszámot használ a szolgáltatás egyedi azonosításához. A hálózati cím azonosít egy adott hálózati célt; a portszám azonosítja az adott szolgáltatást azon az eszközön, amelyhez csatlakozni szeretne. A hálózati cím és a szolgáltatásport kombinációját végpontnak nevezzük, amelyet a .NET-ben az EndPoint osztály jelöl. A program minden támogatott címcsaládhoz meghatároz egy leszármazottat EndPoint ; az IP-címcsalád esetében az osztály a IPEndPoint.

Az Dns osztály tartománynév-szolgáltatásokat biztosít a TCP/IP internetszolgáltatásokat használó alkalmazások számára. A GetHostEntryAsync módszer egy DNS-kiszolgálót kérdez le, hogy egy felhasználóbarát tartománynevet (például "host.contoso.com") numerikus internetcímre (például 192.168.1.1) képezzen le. GetHostEntryAsync visszaad egy Task<IPHostEntry> olyan értéket, amely a várt állapotban tartalmazza a kért név címeinek és aliasainak listáját. A legtöbb esetben a AddressList tömbben visszaadott első címet használhatja. A következő kód megszerzi azt a IPAddress elemet, amely a kiszolgáló host.contoso.com IP-címét tartalmazza.

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

Tipp.

Manuális tesztelési és hibakeresési célokra általában a GetHostEntryAsync metódust használhatja az eredményül kapott gazdagépnévvel az Dns.GetHostName() értékből a localhost név IP-címre való feloldásához. Vegye figyelembe a következő kódrészletet:

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

Az Internet Assigned Numbers Authority (IANA) a gyakori szolgáltatások portszámait határozza meg. További információért lásd: IANA: Szolgáltatásnevek és szállítási protokoll portszámok nyilvántartása). Más szolgáltatások 1024 és 65 535 közötti tartományba tartozó regisztrált portszámokkal rendelkezhetnek. Az alábbi kód host.contoso.com IP-címet portszámmal kombinálva hoz létre távoli végpontot egy kapcsolathoz.

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

A távoli eszköz címének meghatározása és a kapcsolathoz használandó port kiválasztása után az alkalmazás kapcsolatot létesíthet a távoli eszközzel.

Hozzon létre egy TcpClient

Az TcpClient osztály a TCP-szolgáltatásokat az osztálynál Socket magasabb absztrakciós szinten biztosítja. TcpClient egy távoli gazdagép ügyfélkapcsolatának létrehozására szolgál. Mivel tudja, hogyan lehet beszerezni egy IPEndPoint, tegyük fel, hogy van egy IPAddress, amit párosíthat a kívánt portszámmal. Az alábbi példa bemutatja egy időkiszolgálóhoz való csatlakozás beállítását a 13-as TCP-porton: TcpClient

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

Az előző C# kód:

  • Hozz létre egy IPEndPoint egy ismert IPAddress és port alapján.
  • Példányosítson egy új TcpClient objektumot.
  • Csatlakoztatja a client távoli TCP-időkiszolgálót a 13-as porton a használatával TcpClient.ConnectAsync.
  • NetworkStream A távoli gazdagépről származó adatok olvasására szolgál.
  • Olvasási pufferként deklarál 1_024 byte-ot.
  • Beolvassa az adatokat az olvasási stream pufferbe.
  • Az eredményeket karakterláncként írja a konzolra.

Mivel az ügyfél tudja, hogy az üzenet kicsi, a teljes üzenet beolvasható az olvasási pufferbe egyetlen műveletben. Nagyobb üzenetek vagy meghatározhatatlan hosszúságú üzenetek esetén az ügyfélnek a puffert megfelelőbben kell használnia, és egy while hurokban kell olvasnia.

Fontos

Üzenetek küldésekor és fogadásakor a kiszolgálónak és az ügyfélnek is előre kell tudnia az Encoding. Ha például a kiszolgáló a ASCIIEncoding használatával kommunikál, de az ügyfél megpróbálja használni UTF8Encoding, az üzenetek hibásan lesznek formázva.

Hozzon létre egy TcpListener

A TcpListener típus arra szolgál, hogy figyelje a bejövő kérések TCP-portját, majd létrehoz egy Socket vagy egy TcpClient-t, amely az ügyféllel való kapcsolatot kezeli. A Start metódus lehetővé teszi a figyelés használatát, a Stop metódus pedig letiltja a port hallgatását. A AcceptTcpClientAsync metódus fogadja a bejövő kapcsolatkéréseket, és létrehoz egy TcpClient kérést a kérés kezeléséhez, a metódus pedig fogadja a AcceptSocketAsync bejövő kapcsolatkéréseket, és létrehoz egy Socket kérést a kérés kezeléséhez.

Az alábbi példa bemutatja, hogyan hozhat létre hálózati időkiszolgálót a TcpListener 13-ás TCP-port figyeléséhez. Bejövő kapcsolatkérés elfogadásakor az időkiszolgáló a gazdakiszolgáló aktuális dátumával és időpontjával válaszol.

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

Az előző C# kód:

  • Létrehoz egy IPEndPointIPAddress.Any-val és port.
  • Példányosítson egy új TcpListener objektumot.
  • Meghívja a Start metódust, hogy figyelni kezdjen a porton.
  • A AcceptTcpClientAsync metódus egy TcpClient-ját/ét használja a bejövő kapcsolatkérések fogadására.
  • Az aktuális dátumot és időt sztringüzenetként kódolja.
  • NetworkStream használatával adatot ír a csatlakoztatott ügyfélnek.
  • Az elküldött üzenetet a konzolra írja.
  • Végül meghívja a Stop metódust, hogy leállítsa a port figyelését.

Véges TCP-vezérlő az Socket osztálysal

Mind a TcpClient, mind a TcpListener belsőleg az Socket osztályra támaszkodik, ami azt jelenti, hogy bármi, amit ezekkel az osztályokkal el lehet érni, közvetlenül az aljzatok használatával is megvalósítható. Ez a szakasz számos TcpClient és TcpListener használati esetet mutat be, valamint funkcionálisan egyenértékű Socket megfelelőjüket.

Ügyfélcsatorna létrehozása

TcpClientAz alapértelmezett konstruktor egy kettős veremű socketet próbál létrehozni a Socket(SocketType, ProtocolType) konstruktoron keresztül. Ez a konstruktor kettős veremű csatlakozót hoz létre, ha az IPv6 támogatott, ellenkező esetben visszavált az IPv4-re.

Vegye figyelembe a következő TCP-ügyfélkódot:

using var client = new TcpClient();

Az előző TCP-ügyfélkód funkcionálisan egyenértékű a következő szoftvercsatornás kóddal:

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

A TcpClient(AddressFamily) konstruktor

Ez a konstruktor csak három AddressFamily értéket fogad el; ellenkező esetben egy ArgumentException kivételt dob. Az érvényes értékek a következők:

Vegye figyelembe a következő TCP-ügyfélkódot:

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

Az előző TCP-ügyfélkód funkcionálisan egyenértékű a következő szoftvercsatornás kóddal:

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

A TcpClient(IPEndPoint) konstruktor

A szoftvercsatorna létrehozásakor a konstruktor a megadott helyi címhez is kapcsolódik/csatlakozikIPEndPoint. A IPEndPoint.AddressFamily tulajdonság a socket cím-családjának meghatározására szolgál.

Vegye figyelembe a következő TCP-ügyfélkódot:

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

Az előző TCP-ügyfélkód funkcionálisan egyenértékű a következő szoftvercsatornás kóddal:

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

A TcpClient(String, Int32) konstruktor

Ez a konstruktor megpróbál létrehozni egy kettős veremű struktúrát, amely hasonló az alapértelmezett konstruktorhoz, és csatlakoztatja a távoli DNS végponthoz, amelyet a hostname és port páros határoz meg.

Vegye figyelembe a következő TCP-ügyfélkódot:

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

Az előző TCP-ügyfélkód funkcionálisan egyenértékű a következő szoftvercsatornás kóddal:

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

Csatlakozás kiszolgálóhoz

Az összes Connect, ConnectAsyncBeginConnectés EndConnect a TcpClient túlterhelések funkcionálisan egyenértékűek a megfelelő Socket módszerekkel.

Vegye figyelembe a következő TCP-ügyfélkódot:

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

A fenti TcpClient kód egyenértékű a következő socket kóddal:

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

Szerver socket létrehozása

A nyers TcpClient megfelelőkkel funkcionális egyenértékűséggel rendelkező példányokhoz hasonlóan ez a szakasz a TcpListener konstruktorokat a megfelelő socket kódhoz rendeli. Az első konstruktor, amelyet figyelembe kell venni, a TcpListener(IPAddress localaddr, int port).

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

Az előző TCP-figyelőkód funkcionálisan egyenértékű a következő szoftvercsatornás kóddal:

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

Hallgatózás indítása a kiszolgálón

A Start() metódus egy burkoló, amely egyesíti a Socket és a BindListen() funkcionalitását.

Vegye figyelembe a következő TCP-figyelőkódot:

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

Az előző TCP-figyelőkód funkcionálisan egyenértékű a következő szoftvercsatornás kóddal:

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

Kiszolgálókapcsolat elfogadása

A háttérben a bejövő TCP-kapcsolatok mindig új socketet hoznak létre, amikor elfogadják őket. TcpListener elfogadhat közvetlenül egy Socket példányt (AcceptSocket() vagy AcceptSocketAsync() útján), vagy elfogadhat egy TcpClient-t (AcceptTcpClient() és AcceptTcpClientAsync() útján).

Vegye figyelembe a következő TcpListener kódot:

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

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

Az előző TCP-figyelőkód funkcionálisan egyenértékű a következő szoftvercsatornás kóddal:

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

Hozzon létre egy NetworkStream az adatok küldésére és fogadására.

Az TcpClient esetében egy NetworkStream-t kell példányosítania a GetStream() metódus segítségével, hogy adatokat küldhessen és fogadhasson. A Socket használatával manuálisan kell elvégeznie a NetworkStream létrehozását.

Vegye figyelembe a következő TcpClient kódot:

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

Ami egyenértékű a következő socket-kóddal:

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.

Ha a kódnak nem kell egy példánnyal Stream dolgoznia, közvetlenül támaszkodhat a Socket küldési/fogadási metódusaira (Send, SendAsync, Receive és ReceiveAsync) ahelyett, hogy létrehoz egy példányt NetworkStream.

Lásd még