Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Внимание
Класс Socket настоятельно рекомендуется для опытных пользователей вместо TcpClient и TcpListener.
Для работы с протоколом управления передачей (TCP) можно использовать два варианта: использовать Socket для максимального контроля и производительности, а также использовать вспомогательные TcpClientTcpListener классы. 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() — это оболочка, объединяющая функциональность Bind и Listen() от Socket.
Рассмотрим следующий код прослушивателя 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 (Send, SendAsync, Receive и ReceiveAsync) напрямую вместо создания NetworkStream.