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 método GetStream para retornar o fluxo de rede e, em seguida, chame os métodos NetworkStream.ReadAsync e NetworkStream.WriteAsync do 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 o System.Net.Sockets, representa-se um ponto de extremidade de rede como um objeto IPEndPoint. O IPEndPoint é construído com um IPAddress e seu número de 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 EndPoint é definido para cada família de endereços suportada. Para a família de 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

Normalmente, para fins de teste manual e depuração, pode utilizar o método GetHostEntryAsync com o nome de host resultante do valor de Dns.GetHostName() para resolver o nome localhost 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 tenhas um IPAddress para associar 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 conhecida IPAddress e porta.
  • Instanciar um novo TcpClient objeto.
  • Conecta o client ao servidor TCP remoto na porta 13 usando TcpClient.ConnectAsync.
  • Usa um NetworkStream para ler dados do host remoto.
  • Declara um buffer de leitura de 1_024 bytes.
  • Lê dados do stream para o 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 método Start ativa a escuta e o método Stop 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 Start para começar a escutar na porta.
  • Usa um TcpClient do método AcceptTcpClientAsync para aceitar solicitações de conexões de 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 Stop para parar de escutar na porta.

Controle TCP finito com a classe Socket

Ambos os TcpClient e TcpListener internamente dependem da classe Socket, o que significa que qualquer coisa que se possa fazer com essas classes pode ser alcançada usando sockets diretamente. Esta seção demonstra vários casos de uso de TcpClient e TcpListener, juntamente com a Socket correspondente 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, lançará uma ArgumentException. 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, este construtor também se ligará ao local fornecidoIPEndPoint. 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 estrutura de dual-stack semelhante ao construtor padrão e conectá-lo ao endpoint DNS remoto definido pelo par hostname 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

Todas as sobrecargas Connect, ConnectAsync, BeginConnect e EndConnect em TcpClient são funcionalmente equivalentes aos métodos correspondentes Socket.

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

Tal como os TcpClient são funcionalmente equivalentes às suas contrapartes brutas Socket, esta secção mapeia os TcpListener construtores para o respetivo código de socket 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 método Start() é um wrapper que combina a funcionalidade de SocketBind e Listen().

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

Nos bastidores, as conexões TCP de entrada estão sempre a criar 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 Socket, tem que fazer a criação NetworkStream 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 o seu código não precisar trabalhar com uma instância de Stream, poderá utilizar diretamente os métodos Send/Receive de Socket (Send, SendAsync, Receive e ReceiveAsync) em vez de criar um NetworkStream.

Consulte também