Общие сведения о TCP
Внимание
Класс Socket настоятельно рекомендуется для расширенных пользователей, а не TcpClient
TcpListener
для пользователей.
Для работы с протоколом управления передачей (TCP) можно использовать два варианта: использовать Socket для максимального контроля и производительности, а также использовать вспомогательные TcpClient TcpListener классы. TcpClient и TcpListener создаются на основе System.Net.Sockets.Socket класса и заботятся о деталях передачи данных для удобства использования.
Классы протокола используют базовый Socket
класс для предоставления простого доступа к сетевым службам без дополнительных расходов на обслуживание сведений о состоянии или знание сведений о настройке сокетов, относящихся к протоколу. Чтобы использовать асинхронные методы, можно использовать асинхронные Socket
методы, предоставляемые классом NetworkStream . Чтобы получить доступ к функциям Socket
класса, не предоставляемым классами протокола, необходимо использовать Socket
класс.
TcpClient
и TcpListener
представляет сеть с помощью NetworkStream
класса. С помощью метода GetStream возвращается сетевой поток, после чего вызываются методы NetworkStream.ReadAsync и NetworkStream.WriteAsync потока. Он NetworkStream
не владеет базовым сокетом классов протокола, поэтому закрытие не влияет на сокет.
Использование TcpClient
и TcpListener
Класс TcpClient запрашивает данные из интернет-ресурса с помощью TCP. Методы и свойства абстрактных сведений TcpClient
для создания Socket запроса и получения данных с помощью TCP. Так как подключение к удаленному устройству представлено в виде потока, данные можно считывать и записывать с помощью методов работы с потоками платформы .NET Framework.
Протокол TCP устанавливает соединение с удаленной конечной точкой, а затем использует его для отправки и получения пакетов данных. Протокол TCP отвечает за отправку пакетов данных в конечную точку и их сборку в правильном порядке после доставки.
Создание конечной точки IP
При работе System.Net.Socketsс объектом представляется сетевая конечная точка IPEndPoint . Создается IPEndPoint
с соответствующим номером IPAddress порта. Прежде чем начать беседу с помощью Socket, создайте канал данных между приложением и удаленным назначением.
В качестве уникального идентификатора службы протокол TCP/IP использует сетевой адрес и номер порта службы. Сетевой адрес определяет определенное назначение сети; Номер порта определяет определенную службу на этом устройстве для подключения. Сочетание сетевого адреса и порта службы называется конечной точкой, которая представлена в .NET классом EndPoint . Потомок EndPoint
определяется для каждого поддерживаемого семейства адресов; для семейства IP-адресов класс имеет значение IPEndPoint.
Класс Dns предоставляет службы доменных имен приложениям, используюющим интернет-службы TCP/IP. Метод GetHostEntryAsync запрашивает DNS-сервер для сопоставления понятного доменного имени (например, "host.contoso.com") с числовым интернет-адресом (например 192.168.1.1
, ). GetHostEntryAsync
возвращает значение Task<IPHostEntry>
, которое, когда ожидается, содержит список адресов и псевдонимов для запрошенного имени. В большинстве случаев можно использовать первый адрес из возвращенного массива AddressList. Следующий код получает IPAddress IP-адрес сервера host.contoso.com
.
IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("host.contoso.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
Совет
Для ручного тестирования и отладки метод обычно можно использовать GetHostEntryAsync с результирующий имя узла из Dns.GetHostName() значения для разрешения имени localhost на IP-адрес. Рассмотрим следующий фрагмент кода:
var hostName = Dns.GetHostName();
IPHostEntry localhost = await Dns.GetHostEntryAsync(hostName);
// This is the IP address of the local machine
IPAddress localIpAddress = localhost.AddressList[0];
Центр назначения номеров Интернета (IANA) определяет номера портов для общих служб. Дополнительные сведения см. в разделе IANA: Имя службы и реестр номеров портов транспорта). Другие службы могут использовать номера портов в диапазоне от 1024 до 65535. Следующий код объединяет IP-адрес host.contoso.com
с номером порта, чтобы создать удаленную конечную точку для подключения.
IPEndPoint ipEndPoint = new(ipAddress, 11_000);
После определения адреса удаленного устройства и выбора порта, используемого для подключения, приложение может установить подключение к удаленному устройству.
Создание класса TcpClient
Класс TcpClient
предоставляет службы TCP на более высоком уровне абстракции, чем Socket
класс. TcpClient
используется для создания подключения клиента к удаленному узлу. Зная, как получить IPEndPoint
, предположим, что у вас есть IPAddress
пара с нужным номером порта. В следующем примере показано, как настроить TcpClient
подключение к серверу времени на 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 🕛"
В приведенном выше коде C#:
- Создает из
IPEndPoint
известногоIPAddress
и порта. - Создайте экземпляр нового
TcpClient
объекта. client
Подключается к удаленному серверу времени TCP через порт 13 с помощьюTcpClient.ConnectAsync.- NetworkStream Используется для чтения данных с удаленного узла.
- Объявляет буфер чтения байтов
1_024
. - Считывает данные из
stream
буфера чтения. - Записывает результаты в виде строки в консоль.
Так как клиент знает, что сообщение небольшое, все сообщение можно считывать в буфер чтения в одной операции. При наличии больших сообщений или сообщений с неопределенной длиной клиент должен использовать буфер более соответствующим образом и читать в цикле while
.
Внимание
При отправке и получении сообщений должно быть известно заранее как серверу, Encoding так и клиенту. Например, если сервер взаимодействует с использованием ASCIIEncoding , но клиент пытается использовать UTF8Encoding, сообщения будут неправильно сформированы.
Создание класса TcpListener
Тип TcpListener используется для отслеживания TCP-порта для входящих запросов, а затем создания либо Socket
TcpClient
для управления подключением к клиенту. Метод Start включает прослушивание порта, а метод Stop отключает его. Метод AcceptTcpClientAsync принимает входящие запросы подключения и создает TcpClient
запрос для обработки запроса, а AcceptSocketAsync метод принимает входящие запросы подключения и создает Socket
для обработки запроса.
В следующем примере показано создание сервера времени сети с помощью TcpListener
tcp-порта 13. При получении входящего запроса на подключение сервер времени сообщает в ответ текущую дату и время с сервера узла.
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();
}
В приведенном выше коде C#:
IPEndPoint
Создает с помощью IPAddress.Any и портом.- Создайте экземпляр нового
TcpListener
объекта. - Start Вызывает метод, чтобы начать прослушивание порта.
TcpClient
Использует метод из AcceptTcpClientAsync метода для принятия входящих запросов на подключение.- Кодирует текущую дату и время в виде строкового сообщения.
- NetworkStream Используется для записи данных в подключенный клиент.
- Записывает отправленное сообщение в консоль.
- Наконец, вызывает Stop метод, чтобы остановить прослушивание порта.
Конечный элемент управления TCP с классом Socket
Оба TcpClient
класса и TcpListener
внутренне полагаются на Socket
класс, то есть все, что можно сделать с этими классами, можно достичь напрямую с помощью сокетов. В этом разделе демонстрируется несколько TcpClient
вариантов использования и TcpListener
их Socket
аналог, который функционально эквивалентен.
Создание сокета клиента
TcpClient
Конструктор по умолчанию пытается создать сокет с двумя стеками с помощью конструктора Socket(SocketType, ProtocolType). Этот конструктор создает сокет с двумя стеками, если поддерживается IPv6, в противном случае он возвращается к IPv4.
Рассмотрим следующий код TCP-клиента:
using var client = new TcpClient();
Предыдущий код TCP-клиента функционально эквивалентен следующему коду сокета:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
TcpClient(AddressFamily) Конструктор
Этот конструктор принимает только три AddressFamily
значения, в противном случае создается исключение ArgumentException. Допустимые значения:
- AddressFamily.InterNetwork: для сокета IPv4.
- AddressFamily.InterNetworkV6: для сокета IPv6.
- AddressFamily.Unknown: это попытается создать сокет с двумя стеками, аналогично конструктору по умолчанию.
Рассмотрим следующий код TCP-клиента:
using var client = new TcpClient(AddressFamily.InterNetwork);
Предыдущий код TCP-клиента функционально эквивалентен следующему коду сокета:
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
TcpClient(IPEndPoint) Конструктор
При создании сокета этот конструктор также привязывается к предоставленному локальному IPEndPoint
объекту. Свойство IPEndPoint.AddressFamily используется для определения семейства адресов сокета.
Рассмотрим следующий код TCP-клиента:
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5001);
using var client = new TcpClient(endPoint);
Предыдущий код TCP-клиента функционально эквивалентен следующему коду сокета:
// 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);
TcpClient(String, Int32) Конструктор
Этот конструктор попытается создать двойной стек, аналогичный конструктору по умолчанию, и подключить его к удаленной конечной точке DNS, определенной hostname
с помощью пары.port
Рассмотрим следующий код TCP-клиента:
using var client = new TcpClient("www.example.com", 80);
Предыдущий код TCP-клиента функционально эквивалентен следующему коду сокета:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);
Подключиться к серверу
Все Connect
, ConnectAsync
BeginConnect
и EndConnect
перегрузки в TcpClient
функционально эквивалентны соответствующим Socket
методам.
Рассмотрим следующий код TCP-клиента:
using var client = new TcpClient();
client.Connect("www.example.com", 80);
Приведенный выше TcpClient
код эквивалентен следующему коду сокета:
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("www.example.com", 80);
Создание сокета сервера
Подобно TcpClient
экземплярам, имеющим функциональную эквивалентность со своими необработанными Socket
аналогами, в этом разделе конструкторы сопоставляют TcpListener
конструкторы с соответствующим кодом сокета. Первый конструктор, который следует рассмотреть, — это TcpListener(IPAddress localaddr, int port)
.
var listener = new TcpListener(IPAddress.Loopback, 5000);
Предыдущий код прослушивателя TCP функционально эквивалентен следующему коду сокета:
var ep = new IPEndPoint(IPAddress.Loopback, 5000);
using var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
Запуск прослушивания на сервере
Метод Start() — это оболочка, Socket
объединяющая функции Bind и Listen() функции.
Рассмотрим следующий код прослушивателя TCP:
var listener = new TcpListener(IPAddress.Loopback, 5000);
listener.Start(10);
Предыдущий код прослушивателя TCP функционально эквивалентен следующему коду сокета:
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();
}
Принятие подключения к серверу
Под капотом входящие TCP-подключения всегда создают новый сокет при принятии. TcpListener
может принимать экземпляр напрямую Socket (через AcceptSocket() или) или AcceptSocketAsync()может принимать TcpClient (через AcceptTcpClient() и AcceptTcpClientAsync()).
Рассмотрим следующий TcpListener
код:
var listener = new TcpListener(IPAddress.Loopback, 5000);
using var acceptedSocket = await listener.AcceptSocketAsync();
// Synchronous alternative.
// var acceptedSocket = listener.AcceptSocket();
Предыдущий код прослушивателя TCP функционально эквивалентен следующему коду сокета:
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();
Создание отправки NetworkStream
и получения данных
При TcpClient
этом необходимо создать экземпляр NetworkStream с GetStream() помощью метода, чтобы иметь возможность отправлять и получать данные. При этом Socket
необходимо вручную создать NetworkStream
.
Рассмотрим следующий TcpClient
код:
using var client = new TcpClient();
using NetworkStream stream = client.GetStream();
Эквивалентно следующему коду сокета:
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);
Совет
Если код не нужен для работы с экземпляромStream, можно использовать Socket
методы отправки и получения напрямую Receive ReceiveAsyncSendSendAsyncвместо создания.NetworkStream