В рамках изучения сетевого программирования я набрал код программы TcpEchoClient.cs из книги «TCP/IP Sockets in C#», про которую писал в прошлом посте.

Чтобы получить из кода исполняемый файл, я использовал программу csc.exe из командной строки разработчика среды Visual Studio Community 2022. Это компилятор для кода на языке C# под платформу «.NET Framework». Я работаю в операционной системе Windows 10.

Как устроена эта программа

В терминах клиент-серверной архитектуры эту программу называют клиентом. Предполагается, что она будет связываться с другими программами по компьютерной сети по протоколу TCP (Transmission Control Protocol). Программу, с которой будет связываться мой клиент, в терминах клиент-серверной архитектуры называют сервером.

Протокол TCP с точки зрения программиста работает с потоками байтов. Это не непрерывный поток, передача данных по протоколу TCP выполняется кусками, которые называют сегментами. Поэтому в программе получение сегментов производят в цикле, в каждом шаге которого полученный сегмент дописывают в конец предварительно созданного в оперативной памяти массива байтов (буфера). Окончание передачи определяют по факту того, что на очередном шаге ничего не получено, то есть получаемые сегменты закончились.

Программа-клиент умеет отправлять запросы программе-серверу, получать ответы от программы сервера и отображать эти ответы для пользователя программы. Очень часто программисты, начинающие изучать незнакомую для себя предметную область, пишут первые программы в этой области с консольным интерфейсом (разработка для программы графического интерфейса — отдельная задача, ее можно отложить). Мой клиент выводит результаты сетевого общения в окно консоли.

Для проверки успешности соединения между программой-клиентом и программой-сервером нередко действуют по следующей схеме: клиент отправляет серверу какое-либо сообщение, а сервер возвращает клиенту это же сообщение. Возвращаемое сервером клиенту сообщение при такой проверке называют эхом.

Тестирование локально

Поначалу я сунулся со своим TCP-клиентом в интернет, но там многое не получалось. Поэтому у меня зародились сомнения в правильности кода.

Чтобы проверить, работает ли код программы в принципе, я решил написать в пару к моему клиенту программу-сервер. В учебниках по сетевому программированию обычно есть код как программы-клиента, так и программы-сервера. В книге «TCP/IP Sockets in C#» в пару к клиенту есть программа-сервер TcpEchoServer.cs. Если оценивать эти программы с точки зрения сложности, они находятся примерно на одном уровне.

Студенты часто считают, что сервер и клиент — это разные узлы (компьютеры) в компьютерной сети. Для многих становится открытием, что сервер и клиент — это программы, и эти программы могут связываться друг с другом, находясь на одном и том же компьютере. Для этого обычно используют IP-адрес 127.0.0.1, который обычно имеет имя localhost.

Вот как работа описанных выше клиента и сервера выглядит у меня на компьютере:

Программу-сервер я запустил в окне одной консоли, программу-клиент — в окне другой консоли. Сервер постоянно работает, ожидая запросов от клиентов, которых может быть много. Клиент запустился, выполнил свою задачу и тут же закончил работу.

На иллюстрации выше я передал клиенту в качестве параметров IP-адрес сервера и строку, которую нужно передать на сервер. Может быть еще третий, необязательный, параметр, через который можно передать номер порта, но в данном случае я его не задал, по умолчанию этот клиент использует порт 7.

TCPbin

На данный момент в интернете существует ряд сайтов с названием «tcpbin», принадлежащих разным лицам. Насколько я понимаю, эти сайты созданы для тестирования программ-клиентов, работающих по протоколу TCP. Слово «bin» в названии этих сайтов переводится на русский язык как «мусорная корзина». Имеется в виду, что это сайты для приема тестовых запросов от программ-клиентов, сами по себе тестовые запросы не несут полезной нагрузки и могут быть любыми (мусор), они служат только для проверки правильности работы программ. Обычно эти сайты позволяют работать с ними бесплатно (не очень понятно, как они существуют, на чем зарабатывают).

Как оказалось, все сайты с таким названием работают по-разному, у каждого есть какие-то свои особенности, которые следует учитывать.

tcpbin.com

Хороший сайт, на его главной странице имеется почти вся (как позже выяснилось, не вся) необходимая информация. В качестве адреса можно использовать как имя tcpbin.com, так и IP-адреса, указанные на главной странице. В будущем эти IP-адреса могут измениться, на данный момент они следующие:

  • IPv4: 45.79.112.203
  • IPv6: 2600:3c01::f03c:91ff:feab:f98b

Мой клиент может работать как с именем сайта, так и с IP-адресом.

Для сервера на сайте, который может возвращать запрос (эхо) клиенту, в простейшем случае нужно указывать порт 4242 (в будущем может измениться).

Мой клиент успешно создавал соединение с этим сайтом и успешно отправлял запрос, но обратно ответ не получал. Через некоторое время сервер разрывал соединение, так и не отправив в ответ ни байта.

Я бился головой об эту стену несколько недель. Как обычно, нашел ответ на сайте StackOverflow. Оказалось, что сервер сайта tcpbin.com требует обязательного символа новой строки в конце отправляемой ему строки. Об этом нигде не написано, вы должны догадаться сами. Создатели сайта тестируют сервер сайта с помощью программы nc (netcat), которая есть в Unix-подобных системах. При том способе ввода текста отправляемого сообщения, которым пользуются тестеры, символ новой строки вставляется автоматически.

Я немного подправил программу моего клиента следующим способом:

        args[1] += "\n"; // для тестирования на сайте tcpbin.com
        byte[] byteBuffer = Encoding.ASCII.GetBytes(args[1]);

Первую из этих строк я добавил, вторая уже была в коде. Здесь args[1] — это второй параметр программы, в котором пользователь задает строку для отправки на сервер.

После этого изменения мой клиент смог работать с сервером сайта tcpbin.com:

На иллюстрации выше можно заметить, что если при тестировании локально было отправлено 12 байтов (размер строки «My name Ilya» в кодировке ASCII), то теперь для отправки той же строки было использовано 13 байтов, из которых дополнительный байт занял символ новой строки с кодом 0x0A. Также видно, что при выводе полученной обратно строки в консоль вывелась дополнительная пустая строка, то есть сервер возвратил именно то, что получил на входе.

Дополнение от 16.01.2026 г.: на днях тестировали со студентами работу TCP-клиента, написанного на языке C++ (код взяли из документации библиотеки Winsock на сайте компании Microsoft). С этим клиентом добавлять при отправке символ '\n' новой строки в конец сообщения не понадобилось, всё работает успешно и без этого. Я так и не разобрался, почему эти TCP-клиенты по-разному взаимодействуют с сайтом tcpbin.com.

tcpbin.net

Тоже хороший сайт, но на нем всё устроено не так, как на сайте tcpbin.com. Здесь вы создаете для своего тестирования отдельную корзину (bin), нажав на кнопку «Create New Bin» (каждая корзина подразумевает новый номер порта, а IP-адрес у разных корзин один и тот же). При этом в браузере откроется отдельная страница для вашего тестирования, на этой странице можно наблюдать, как ваш клиент создаст соединение, что он пришлет и как это обработает сервер. То есть вы будете видеть картину со стороны сервера, это очень интересно и очень наглядно для студента.

Однако, первоначально у этого сайта не было функции для возвращения запроса (эха) клиенту. Мой клиент успешно подключился к этому серверу и успешно послал ему заданное сообщение. На странице моей корзины было видно, что сервер принял соединение и получил заданное сообщение. Но обратно он его не отправлял, так как авторы сайта не запланировали такую функцию.

Я без особой надежды отправил им сообщение в Issues в их репозиторий на Github. И я был довольно сильно удивлен: они добавили эту функцию в тот же день. Теперь в созданной корзине появилась галка «Echo TCP data back to sender». Ее можно установить или снять в любой момент. Если эта галка установлена, сервер начнет отправлять получаемые сообщения обратно клиенту.

Вот как тестирование на tcpbin.net выглядит для моего клиента:

Как видно на иллюстрации выше, тут не требуется обязательной отправки символа новой строки в конце сообщения. Порт для разных корзин будет разный, корзины после использования на сайте tcpbin.net не хранятся.

Вот картина со стороны сервера tcpbin.net (свой IP-адрес и порт я затер, хотя это не такая уж большая тайна):

На иллюстрации выше видно, что галку «Echo TCP data back to sender» я установил. Также можно рассмотреть полученное сервером сообщение, в том числе в шестнадцатеричных кодах, это бывает полезно.

tcpbin.org

Страница этого сайта у меня открывается в браузере. Но с сервером этого сайта мой клиент связаться не смог. Я пробовал указывать в качестве адреса название сайта tcpbin.org и IP-адрес 34.230.40.69 (указан на главной странице сайта на данный момент). В качестве номера порта указывал 30000, указанный на главной странице сайта. Ничего из этого у меня не сработало.

У владельца сайта тоже есть репозиторий на GitHub. В этом репозитории в разделе Issues люди сообщали владельцу сайта о том, что его сервис не работает, еще в 2019 и 2021 году, но ответа до сих пор не получили.

Тестирование общения по протоколу HTTP

Во многих учебниках еще можно найти примеры общения с сайтами в интернете по протоколу HTTP из TCP-клиента. Но сегодня проделать такие тесты непросто. Причин этому несколько.

Во-первых, если раньше можно было отправить серверу незашифрованный HTTP-запрос через порт 80 и получить незашифрованный HTTP-ответ, то сегодня сайт с такой возможностью чрезвычайно сложно найти. Общение по протоколу HTTP теперь в большинстве случаев шифруется: сервер получает зашифрованный HTTP-запрос через порт 443 и в ответ отправляет зашифрованный HTTP-ответ. Поскольку при этом задействовано асимметричное шифрование, нужно будет разобраться в его работе и в том, что такое открытый и закрытый ключи, что такое сертификат открытого ключа и как их получить. Это возможно, но займет много времени.

Во-вторых, следует учитывать изменение версий протокола HTTP, оно значительное. Сейчас версии протокола HTTP 1.1, 2 и 3 используются примерно равным количеством сайтов (это приблизительный вывод, так как в разных источниках статистика сильно отличается). Простой обмен текстом был возможен только по протоколу HTTP версий 1.0 (давно не используется) и 1.1. Уже в версии 2 протокола данные упаковываются в двоичные куски (фреймы) определенной структуры. Это тоже возможно реализовать, но тоже займет много времени.

Можно создать свой сервер с нужными настройками, который будет работать по старинке: по протоколу HTTP версии 1.1, без шифрования, через порт 80. Но на разворачивание такого сервиса может потребоваться много времени. Кроме этого, скорее всего, понадобится доплачивать провайдеру за статический IP-адрес (сегодня большинству рядовых пользователей провайдеры выдают динамический IP-адрес, что будет неудобно для тестирования).

К общению со сторонними сайтами по протоколу HTTP лучше вернуться при изучении классов, которые специально для этого есть в рамках платформы «.NET», например: HttpClient, WebRequest и так далее.

Я всё же с трудом нашел подходящий сайт (http://web.simmons.edu) и успешно протестировал на нем своего TCP-клиента, предварительно слегка его переписав в том же месте, которое я упоминал выше:

        args[1] = "GET / HTTP/1.1\r\nHost: web.simmons.edu\r\n\r\n"; // тест
        byte[] byteBuffer = Encoding.ASCII.GetBytes(args[1]);

Предположу, что этот сайт завели в этом учебном заведении специально для подобных тестов.

Обратите внимание, что при составлении HTTP-запроса имеет значение каждый символ, в том числе пробелы и специальные символы. При неправильной расстановке символов запрос может быть расценен сервером как некорректный, из-за чего HTTP-ответа может вообще не быть. Последовательность \r\n означает символ новой строки. Правила составления HTTP-запроса по версии протокола 1.1 описаны в RFC 9112.

Вот что у меня получилось:

В качестве второго параметра в данном случае может быть что угодно, так как (это видно в коде выше) я заменяю в программе второй параметр args[1] на фиксированный текст HTTP-запроса. На иллюстрации выше видно, что я успешно получил начальную часть HTTP-ответа; там видно, что код ответа — 200, что означает «ОК» (всё в порядке). HTTP-ответ обрывается, так как этот TCP-клиент предназначался для работы с «эхом», то есть авторы программы рассчитывали, что размер ответа совпадет с размером запроса. Чтобы сделать красиво, нужно переписать данный TCP-клиент, это несложно, но это уже будет другая программа, а мне не хотелось бы сильно отклоняться от изучения книги.