Использование сокетов для отправки и получения данных по протоколу TCP
Перед использованием сокета для связи с удаленными устройствами необходимо инициализировать сокет, указав протокол и сведения о сетевом адресе. Конструктор класса Socket имеет параметры, которые определяют семейство адресов, тип сокета и тип протокола, которые сокет использует для подключения. При подключении сокета клиента к сокету сервера клиент будет использовать IPEndPoint объект для указания сетевого адреса сервера.
Создание конечной точки 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 , который при ожидании содержит список адресов и псевдонимов для запрошенного имени. В большинстве случаев можно использовать первый адрес из возвращенного массива AddressList. Следующий код получает объект , IPAddress содержащий IP-адрес сервера host.contoso.com .
IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("host.contoso.com"); IPAddress ipAddress = ipHostInfo.AddressList[0];
Для ручного тестирования и отладки обычно можно использовать GetHostEntryAsync метод , чтобы получить заданное Dns.GetHostName() значение для разрешения имени localhost в IP-адрес.
Центр интернет-номеров (IANA) определяет номера портов для общих служб. Дополнительные сведения см. в разделе IANA: реестр имен служб и номеров портов транспортных протоколов). Другие службы могут использовать номера портов в диапазоне от 1024 до 65535. Следующий код объединяет IP-адрес для host.contoso.com с номером порта, чтобы создать удаленную конечную точку для подключения.
IPEndPoint ipEndPoint = new(ipAddress, 11_000);
После определения адреса удаленного устройства и выбора порта для подключения приложение может установить подключение к удаленному устройству.
Создание Socket клиента
Создав endPoint объект , создайте сокет клиента для подключения к серверу. После подключения сокета он может отправлять и получать данные из подключения к сокету сервера.
using Socket client = new( ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); await client.ConnectAsync(ipEndPoint); while (true) < // Send message. var message = "Hi friends 👋!<|EOM|>"; var messageBytes = Encoding.UTF8.GetBytes(message); _ = await client.SendAsync(messageBytes, SocketFlags.None); Console.WriteLine($"Socket client sent message: \"\""); // Receive ack. var buffer = new byte[1_024]; var received = await client.ReceiveAsync(buffer, SocketFlags.None); var response = Encoding.UTF8.GetString(buffer, 0, received); if (response == "<|ACK|>") < Console.WriteLine( $"Socket client received acknowledgment: \"\""); break; > // Sample output: // Socket client sent message: "Hi friends 👋!<|EOM|>" // Socket client received acknowledgment: "<|ACK|>" > client.Shutdown(SocketShutdown.Both);
В приведенном выше коде C#:
- Создает экземпляр нового Socket объекта с заданным endPoint семейством адресов экземпляров , SocketType.Streamи ProtocolType.Tcp.
- Socket.ConnectAsync Вызывает метод с экземпляром в endPoint качестве аргумента.
- В цикле while :
- Кодирует и отправляет сообщение на сервер с помощью Socket.SendAsync.
- Записывает отправленное сообщение в консоль.
- Инициализирует буфер для получения данных с сервера с помощью Socket.ReceiveAsync.
- response Когда является подтверждением, он записывается в консоль, и цикл завершается.
Создание Socket сервера
Чтобы создать сокет сервера, объект может прослушивать входящие подключения по любому IP-адресу, endPoint но необходимо указать номер порта. После создания сокета сервер может принимать входящие подключения и взаимодействовать с клиентами.
using Socket listener = new( ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listener.Bind(ipEndPoint); listener.Listen(100); var handler = await listener.AcceptAsync(); while (true) < // Receive message. var buffer = new byte[1_024]; var received = await handler.ReceiveAsync(buffer, SocketFlags.None); var response = Encoding.UTF8.GetString(buffer, 0, received); var eom = "<|EOM|>"; if (response.IndexOf(eom) > -1 /* is end of message */) < Console.WriteLine( $"Socket server received message: \"
\""); var ackMessage = "<|ACK|>"; var echoBytes = Encoding.UTF8.GetBytes(ackMessage); await handler.SendAsync(echoBytes, 0); Console.WriteLine( $"Socket server sent acknowledgment: \"\""); break; > // Sample output: // Socket server received message: "Hi friends 👋!" // Socket server sent acknowledgment: "<|ACK|>" > В приведенном выше коде C#:
- Создает экземпляр нового Socket объекта с заданным endPoint семейством адресов экземпляров , SocketType.Streamи ProtocolType.Tcp.
- Вызывает listener Socket.Bind метод с экземпляром в endPoint качестве аргумента для связывания сокета с сетевым адресом.
- Метод Socket.Listen() вызывается для прослушивания входящих подключений.
- Вызывает listener метод для Socket.AcceptAsync принятия входящего подключения к сокету handler .
- В цикле while :
- Вызовы Socket.ReceiveAsync для получения данных от клиента.
- При получении данных они декодируются и записываются в консоль.
- response Если сообщение заканчивается на <|EOM|>, подтверждение отправляется клиенту с помощью Socket.SendAsync.
Запуск примера клиента и сервера
Сначала запустите серверное приложение, а затем запустите клиентское приложение.
dotnet run --project socket-server Socket server starting. Found: 172.23.64.1 available on port 9000. Socket server received message: "Hi friends 👋!" Socket server sent acknowledgment: "<|ACK|>" Press ENTER to continue.
Клиентское приложение отправит сообщение серверу, а сервер ответит подтверждением.
dotnet run --project socket-client Socket client starting. Found: 172.23.64.1 available on port 9000. Socket client sent message: "Hi friends 👋!<|EOM|>" Socket client received acknowledgment: "<|ACK|>" Press ENTER to continue.
Клиент сервер си шарп
Класс Socket применяется не только для создания tcp-клиента, но для определения tcp-сервера. Общая схема работы серверного сокета TCP будет следующей:
Привязка к конечной точке. Метод Bind
Вначале серверный сокет с помощью метода Bind связывается с локальной точкой. В качестве параметра этот метод принимает локальную точку EndPoint, на которой сокет будет принимать подключения от клиентов:
public void Bind (EndPoint localEP);
Если не имеет значения, на каком именно локальном адресе сервер будет запущен, то можно в качестве адреса использовать значение IPAddress.Any . Тогда серверу будет назначен наиболее подходящий сетевой адрес (при наличии нескольких сетевых интерфейсов). Кроме того, если номер порта не имеет значения, то в качестве порта можно указать число 0. Тогда серверу будет предоставлен один из доступных портов. При использовании такого подхода точный адрес и порт затем можно будет получить через свойство LocalEndpoint .
using System.Net; using System.Net.Sockets; IPEndPoint ipPoint = new IPEndPoint(IPAddress.Any, 8888); using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(ipPoint); // связываем с локальной точкой ipPoint // получаем конечную точку, с которой связан сокет Console.WriteLine(socket.LocalEndPoint); // 0.0.0.0:8888
В этом примере сокет будет прослушивать подключения по 8888 порту на любых локальных адресах. То есть клиент должен будет подключаться к локальному адресу, например, к 127.0.0.1, и порту 8888.
Прослушивание подключений. Метод Listen
Для запуска прослушивания подключений на выбранной локальной конечной точке применяется метод Listen :
public void Listen (); public void Listen (int backlog);
При обращении к серверу входящие подключения помещаются в очередь для последующей обработки. По умолчанию эта очередь допускает 2147483647 подключений. Вторая версия метода Listen через параметр позволяет переопределить длину очереди.
stem.Net; using System.Net.Sockets; IPEndPoint ipPoint = new IPEndPoint(IPAddress.Any, 8888); using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(ipPoint); // связываем с локальной точкой ipPoint socket.Listen(1000); // запускаем прослушивание // количество входящих подключений, которые можно поместить в очередь, равно 1000
Подключение клиента
После начала прослушивания сокет готов принимать подключения. Для приема подключений применяются методы Accept()/AcceptAsync() . Эти методы имеют ряд перегруженных версий. Отмечу саму простую из них:
Все версии методов Accept()/AcceptAsync() в качестве результа возвращают объект Socket, который инкапсулирует входящее подключение, то есть по сути представляет подключенного клиента.
using System.Net; using System.Net.Sockets; IPEndPoint ipPoint = new IPEndPoint(IPAddress.Any, 8888); using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(ipPoint); socket.Listen(); Console.WriteLine("Сервер запущен. Ожидание подключений. "); // получаем входящее подключение using Socket client = await socket.AcceptAsync(); // получаем адрес клиента Console.WriteLine($"Адрес подключенного клиента: ");
Через свойства Socket можно получить информацию о подключении клиента, в частности, свойство RemoteEndPoint позволяет получить адрес подключенного клиента.
Для такого просто сервера для теста определим клиент. Возьмем новый проект консольного приложения на C# и определим в нем следующий код:
using System.Net.Sockets; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < await socket.ConnectAsync("127.0.0.1", 8888); Console.WriteLine($"Подключение к установлено"); > catch (SocketException) < Console.WriteLine($"Не удалось установить подключение с "); >
Здесь в метод ConnectAsync передаем данные конечной точки сервера и при успешном подключении выводим сообщение.
Запустим сервер, а затем запустим клиент. В итоге после подключения клиента к серверу в консоли сервера мы увидим что-то наподобие:
Сервер запущен. Ожидание подключений. Адрес подключенного клиента: 127.0.0.1:52767
В данном случае мы видим, что в моем случае для подключения к серверу сокет-клиент использует адрес 127.0.0.1:52767. А в консоли клиента отобразится сообщение об успешном подключении:
Подключение к 127.0.0.1:8888 установлено