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 conhecidaIPAddress
. - 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
TcpClient
O 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:
- AddressFamily.InterNetwork: para soquete IPv4.
- AddressFamily.InterNetworkV6: para soquete IPv6.
- AddressFamily.Unknown: isso tentará criar um soquete de pilha dupla, de forma semelhante ao construtor padrã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 IPEndPoint
fornecido. 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 , Connect
e 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 Socket
o , 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 Socket
métodos Send/Receive (Send, SendAsyncReceive , e ReceiveAsync) diretamente em vez de criar um NetworkStream.