Partilhar via


Visão geral do TCP

Importante

A Socket classe é altamente recomendada para usuários avançados, em vez de TcpClient e TcpListener.

Para trabalhar com TCP (Transmission Control Protocol), você tem duas opções: usar Socket para obter o máximo de controle e desempenho ou usar as TcpClient classes auxiliares e TcpListener . TcpClient e TcpListener são construídos em cima da System.Net.Sockets.Socket classe e cuidam dos detalhes da transferência de dados para facilitar o uso.

As classes de protocolo usam a classe subjacente Socket para fornecer acesso simples a serviços de rede sem a sobrecarga de manter informações de estado ou conhecer os detalhes da configuração de soquetes específicos de protocolo. Para usar métodos assíncronos Socket , você pode usar os métodos assíncronos NetworkStream fornecidos pela classe. Para acessar recursos da classe não expostos pelas classes de Socket protocolo, você deve usar a Socket classe.

TcpClient e TcpListener representar a rede usando a NetworkStream classe. Use o GetStream método para retornar o fluxo de rede e, em seguida, chame os métodos e NetworkStream.WriteAsync do NetworkStream.ReadAsync fluxo. O NetworkStream não possui o soquete subjacente das classes de protocolo, portanto, fechá-lo não afeta o soquete.

Utilização TcpClient e TcpListener

A TcpClient classe solicita dados de um recurso da Internet usando TCP. Os métodos e propriedades de TcpClient abstraem os detalhes para criar um Socket para solicitar e receber dados usando TCP. Como a conexão com o dispositivo remoto é representada como um fluxo, os dados podem ser lidos e gravados com técnicas de manipulação de fluxo do .NET Framework.

O protocolo TCP estabelece uma conexão com um ponto de extremidade remoto e, em seguida, usa essa conexão para enviar e receber pacotes de dados. O TCP é responsável por garantir que os pacotes de dados sejam enviados para o ponto final e montados na ordem correta quando chegam.

Criar um ponto de extremidade IP

Ao trabalhar com System.Net.Socketso , você representa um ponto de extremidade de rede como um IPEndPoint objeto. O IPEndPoint é construído com um e seu número de IPAddress porta correspondente. Antes de iniciar uma conversa por meio de um Socket, crie um pipe de dados entre seu aplicativo e o destino remoto.

O TCP/IP usa um endereço de rede e um número de porta de serviço para identificar exclusivamente um serviço. O endereço de rede identifica um destino de rede específico; O número da porta identifica o serviço específico nesse dispositivo ao qual se conectar. A combinação de endereço de rede e porta de serviço é chamada de ponto de extremidade, que é representado no .NET pela EndPoint classe. Um descendente de é definido para cada família de endereços suportada, para a família de EndPoint endereços IP, a classe é IPEndPoint.

A Dns classe fornece serviços de nome de domínio para aplicativos que usam serviços de Internet TCP/IP. O GetHostEntryAsync método consulta um servidor DNS para mapear um nome de domínio amigável (como "host.contoso.com") para um endereço numérico da Internet (como 192.168.1.1). GetHostEntryAsync Retorna um Task<IPHostEntry> que, quando aguardado, contém uma lista de endereços e aliases para o nome solicitado. Na maioria dos casos, você pode usar o primeiro endereço retornado na AddressList matriz. O código a seguir obtém um IPAddress contendo o endereço IP para o servidor host.contoso.com.

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

Gorjeta

Para fins de teste manual e depuração, você normalmente pode usar o GetHostEntryAsync método com o nome de host resultante do valor para resolver o nome do Dns.GetHostName() host local para um endereço IP. Considere o seguinte trecho de código:

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

A Internet Assigned Numbers Authority (IANA) define números de porta para serviços comuns. Para obter mais informações, consulte IANA: Service Name and Transport Protocol Port Number Registry). Outros serviços podem ter números de porta registrados na faixa de 1.024 a 65.535. O código a seguir combina o endereço IP para host.contoso.com com um número de porta para criar um ponto de extremidade remoto para uma conexão.

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

Depois de determinar o endereço do dispositivo remoto e escolher uma porta para usar para a conexão, o aplicativo pode estabelecer uma conexão com o dispositivo remoto.

Criar um TcpClient

A TcpClient classe fornece serviços TCP em um nível mais alto de abstração do que a Socket classe. TcpClient é usado para criar uma conexão de cliente com um host remoto. Sabendo como obter um IPEndPoint, vamos supor que você tenha um IPAddress para emparelhar com o número de porta desejado. O exemplo a seguir demonstra a configuração de um TcpClient para se conectar a um servidor de tempo na porta 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 🕛"

O código C# anterior:

  • Cria um IPEndPoint a partir de uma porta e conhecida IPAddress .
  • Instanciar um novo TcpClient objeto.
  • Conecta o client ao servidor de tempo TCP remoto na porta 13 usando TcpClient.ConnectAsynco .
  • Usa um NetworkStream para ler dados do host remoto.
  • Declara um buffer de leitura de 1_024 bytes.
  • Lê dados do buffer de leitura para o stream buffer de leitura.
  • Grava os resultados como uma cadeia de caracteres no console.

Como o cliente sabe que a mensagem é pequena, a mensagem inteira pode ser lida no buffer de leitura em uma operação. Com mensagens maiores, ou mensagens com um comprimento indeterminado, o cliente deve usar o buffer de forma mais adequada e ler em um while loop.

Importante

Ao enviar e receber mensagens, o Encoding deve ser conhecido com antecedência para o servidor e cliente. Por exemplo, se o servidor se comunicar usando ASCIIEncoding , mas o cliente tentar usar UTF8Encoding, as mensagens serão malformadas.

Criar um TcpListener

O TcpListener tipo é usado para monitorar uma porta TCP para solicitações de entrada e, em seguida, criar um Socket ou um TcpClient que gerencia a conexão com o cliente. O Start método permite a escuta e o Stop método desativa a escuta na porta. O AcceptTcpClientAsync método aceita solicitações de conexão de entrada e cria um TcpClient para manipular a solicitação, e o AcceptSocketAsync método aceita solicitações de conexão de entrada e cria um Socket para lidar com a solicitação.

O exemplo a seguir demonstra a criação de um servidor de tempo de rede usando um TcpListener para monitorar a porta TCP 13. Quando uma solicitação de conexão de entrada é aceita, o servidor de hora responde com a data e hora atuais do servidor host.

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

O código C# anterior:

  • Cria um IPEndPoint com IPAddress.Any e porta.
  • Instanciar um novo TcpListener objeto.
  • Chama o método para começar a Start escutar na porta.
  • Usa um TcpClient do método para aceitar solicitações de conexão de AcceptTcpClientAsync entrada.
  • Codifica a data e hora atuais como uma mensagem de cadeia de caracteres.
  • Usa a NetworkStream para gravar dados no cliente conectado.
  • Grava a mensagem enviada no console.
  • Finalmente, chama o método para parar de Stop escutar na porta.

Controle TCP finito com a Socket classe

Ambos e TcpClient TcpListener internamente dependem da classe, o Socket que significa que qualquer coisa que você possa fazer com essas classes pode ser alcançada usando soquetes diretamente. Esta seção demonstra vários TcpClient casos de TcpListener uso, juntamente com sua Socket contraparte que é funcionalmente equivalente.

Criar um soquete de cliente

TcpClientO construtor padrão do tenta criar um soquete de pilha dupla através do construtor Socket(SocketType, ProtocolType). Este construtor cria um soquete de pilha dupla se o IPv6 for suportado, caso contrário, ele retornará ao IPv4.

Considere o seguinte código de cliente TCP:

using var client = new TcpClient();

O código do cliente TCP anterior é funcionalmente equivalente ao seguinte código de soquete:

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

O TcpClient(AddressFamily) construtor

Este construtor aceita apenas três AddressFamily valores, caso contrário, ele lançará um ArgumentExceptionarquivo . Os valores válidos são:

Considere o seguinte código de cliente TCP:

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

O código do cliente TCP anterior é funcionalmente equivalente ao seguinte código de soquete:

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

O TcpClient(IPEndPoint) construtor

Ao criar o soquete, esse construtor também se ligará ao local IPEndPointfornecido. A IPEndPoint.AddressFamily propriedade é usada para determinar a família de endereços do soquete.

Considere o seguinte código de cliente TCP:

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

O código do cliente TCP anterior é funcionalmente equivalente ao seguinte código de soquete:

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

O TcpClient(String, Int32) construtor

Este construtor tentará criar uma pilha dupla semelhante ao construtor padrão e conectá-lo ao ponto de extremidade DNS remoto definido pelo hostname par e port .

Considere o seguinte código de cliente TCP:

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

O código do cliente TCP anterior é funcionalmente equivalente ao seguinte código de soquete:

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

Ligar ao servidor

Todos , Connecte BeginConnect EndConnect sobrecargas em TcpClient são funcionalmente equivalentes aos métodos correspondentesSocket. ConnectAsync

Considere o seguinte código de cliente TCP:

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

O código acima TcpClient é equivalente ao seguinte código de soquete:

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

Criar um soquete de servidor

Assim como TcpClient as instâncias que têm equivalência funcional com suas contrapartes brutas Socket , esta seção mapeia TcpListener os construtores para seu código de soquete correspondente. O primeiro construtor a considerar é o TcpListener(IPAddress localaddr, int port).

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

O código de ouvinte TCP anterior é funcionalmente equivalente ao seguinte código de soquete:

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

Comece a ouvir no servidor

O Start() método é um wrapper que combina Socket's Bind e Listen() funcionalidade.

Considere o seguinte código de ouvinte TCP:

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

O código de ouvinte TCP anterior é funcionalmente equivalente ao seguinte código de soquete:

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

Aceitar uma conexão de servidor

Sob o capô, as conexões TCP de entrada estão sempre criando um novo soquete quando aceitas. TcpListener pode aceitar uma Socket instância diretamente (via AcceptSocket() ou AcceptSocketAsync()) ou pode aceitar uma TcpClient (via AcceptTcpClient() e AcceptTcpClientAsync()).

Considere o seguinte TcpListener código:

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

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

O código de ouvinte TCP anterior é funcionalmente equivalente ao seguinte código de soquete:

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

Criar um NetworkStream para enviar e receber dados

Com TcpClient você precisa instanciar um NetworkStream com o GetStream() método para ser capaz de enviar e receber dados. Com Socketo , você tem que fazer a NetworkStream criação manualmente.

Considere o seguinte TcpClient código:

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

Que é equivalente ao seguinte código de soquete:

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

Gorjeta

Se seu código não precisar trabalhar com uma Stream instância, você poderá confiar nos Socketmétodos Send/Receive (Send, SendAsyncReceive , e ReceiveAsync) diretamente em vez de criar um NetworkStream.

Consulte também