
5ый семестр / 1. Производственная практика_1 / стащил с работы / Начинающему сетевому программисту _ Хабр
.pdf
Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
КАК СТАТЬ АВТОРОМ |
Питчи недели фронтендеров |
Как меняется оплата проезда |
Все потоки Разработка Администрирование Дизайн Менеджмент Маркетинг Научпоп
Mr_Dezz 8 октября 2021 в 12:29
Начинающему сетевому программисту
C++ *, Visual Studio *, API *
Из песочницы
Тема сетевого программирования является для разработчиков одной из важнейших в современном цифровом мире. Правда, надо признать, что большая часть сетевого программирования сосредоточена в области написания скриптов исполнения для web-серверов на языках PHP, Python и им подобных. Как следствие - по тематике взаимодействия клиент-сервер при работе с webсерверами написаны терабайты текстов в Интернете. Однако когда я решил посмотреть, что же имеется в Интернете по вопросу программирования сетевых приложений с использованием голых сокетов, то обнаружил интересную вещь: да, такие примеры конечно же есть, но подавляющее большинство написано под *nixсистемы с использованием стандартных библиотек (что понятно – в области сетевого программирования Microsoft играет роль сильно отстающего и менее надежного «собрата» *nix-ов). Другими словами все эти примеры просто не будут работать под Windows. При определенных танцах с бубнами код сетевого приложения под Linux можно запустить и под Windows, однако это еще более запутает начинающего программиста, на которого и нацелены большинство статей в Интернете с примерами использования сокетов.
ЧИТАЮТ СЕЙЧАС
Позиция Хабра по происходящему
34K |
207 +207 |
Отрасль IT в России поставили на паузу
62K |
408 +408 |
Кризис в России и финансовые вопросы: как не потерять то, что есть
35K |
68 +68 |
«Яндекс» переходит на удаленку, чтобы поддержать эмоциональное состояние сотрудников, Дуров не будет никого блокировать
2.7K |
6 +6 |
Утечка базы данных клиентов СДЭК
Ну а что же с документацией по работе с сетевыми сокетами в Windows от самой Microsoft? Парадоксальность ситуации заключается в том, что непосредственно в самой документации приведено очень беглое описание функций и их использования, а в примерах имеются ошибки и вызовы старых «запрещенных» современными компиляторами функций (к примеру, функция inet_addr() - https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen ) -
такие функции конечно же можно вызывать, заглушив бдительность компилятора через #define-директивы, однако такой подход является полным зашкваром для любого даже начинающего программиста и категорически не рекомендуется к использованию. Более того, фрагмент кода в примере от Microsoft по ссылке выше:
service.sin_addr.s_addr = inet_addr( "127.0.0.1" );
вообще не заработает, т.к. полю Service.sin_addr.s_addr невозможно присвоить значение целого типа, которое возвращает функция inet_addr (возвращает unsigned long). То есть это ни много, ни мало - ошибка! Можно себе представить, сколько пытливых бойцов полегло на этом месте кода.
В общем, посмотрев на всё это, я решил написать базовую статью по созданию простейшего клиент-сервер приложения на С++ под Windows с детальным описанием всех используемых функций. Это приложение будет использовать Win32API и делать незамысловатую вещь, а именно: передавать сообщения от клиента к серверу и обратно, или, иначе говоря – напишем программу по реализации чата для двух пользователей.
Сразу оговорюсь, что статья рассчитана на начинающих программистов, которые только входят в сетевое программирование под Windows. Необходимые навыки – базовое знание С++, а также теоретическая подготовка по теме сетевых сокетов и стека технологии TCP/IP.
12K |
19 +19 |
Почему программисты пишут статьи?
Мегапост
Стр. 1 из 27 |
28.02.2022, 11:29 |
Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
Теория сокетов за 30 секунд для "dummies"
Начну всё-таки немного с теории в стиле «for dummies». В любой современной операционной системе, все процессы инкапсулируются, т.е. скрываются друг от друга, и не имеют доступа к ресурсам друг друга. Однако существуют специальные разрешенные способы взаимодействия процессов между собой. Все эти способы взаимодействия процессов можно разделить на 3 группы: (1) сигнальные, (2) канальные и (3) разделяемая память.
Когда мы говорим про работу сетевого приложения, то всегда подразумеваем взаимодействие процессов: процесс 1 (клиент) пытается что-то послать или получить от Процесса 2 (сервер). Наиболее простым и понятным способом организации сетевого взаимодействия процессов является построение канала между этими процессами. Именно таким путём и пошли разработчики первых сетевых протоколов. Получившийся способ взаимодействия сетевых процессов в итоге оказался многоуровневым: основной программный уровень - стек сетевой технологии TCP/IP, который позволяет организовать эффективную доставку пакетов информации между различными машинами в сети, а уже на прикладном уровне тот самый «сокет» позволяет разобраться какой пакет какому процессу доставить на конкретной машине.
Иными словами «сокет» - это «розетка» конкретного процесса, в которую надо подключиться, чтобы этому процессу передать какую-либо информацию. Договорились, что эта «розетка» в Сети описывается двумя параметрами – IPадресом (для нахождения машины в сети) и Портом подключения (для нахождения процесса-адресата на конкретной машине).
Для того, чтобы сокеты заработали под Windows, необходимо при написании программы пройти следующие Этапы:
1.Инициализация сокетных интерфейсов Win32API.
2.Инициализация сокета, т.е. создание специальной структуры данных и её инициализация вызовом функции.
3.«Привязка» созданного сокета к конкретной паре IP-адрес/Порт – с этого момента данный сокет (его имя) будет ассоциироваться с конкретным процессом, который «висит» по указанному адресу и порту.
4.Для серверной части приложения: запуск процедуры «прослушки» подключений на привязанный сокет.
Для клиентской части приложения: запуск процедуры подключения к серверному сокету (должны знать его IP-адрес/Порт).
5.Акцепт / Подтверждение подключения (обычно на стороне сервера).
6.Обмен данными между процессами через установленное сокетное соединение.
7.Закрытие сокетного соединения.
Итак, попытаемся реализовать последовательность Этапов, указанных выше, для организации простейшего чата между клиентом и сервером. Запускаем Visual Studio, выбираем создание консольного проекта на С++ и поехали.
Этап 0: Подключение всех необходимых библиотек Win32API для работы с сокетами
Сокеты не являются «стандартными» инструментами разработки, поэтому для их активизации необходимо подключить ряд библиотек через заголовочные файлы, а именно:
WinSock2.h – заголовочный файл, содержащий актуальные реализации
Стр. 2 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
функций для работы с сокетами.
WS2tcpip.h – заголовочный файл, который содержит различные программные интерфейсы, связанные с работой протокола TCP/IP (переводы различных данных в формат, понимаемый протоколом и т.д.).
Также нам потребуется прилинковать к приложению динамическую библиотеку ядра ОС: ws2_32.dll. Делаем это через директиву компилятору: #pragma comment(lib, “ws2_32.lib”)
Ну и в конце Этапа 0 подключаем стандартные заголовочные файлы iostream
и stdio.h
Итого по завершению Этапа 0 в Серверной и Клиентской частях приложения имеем:
#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <vector>
#pragma comment(lib, "Ws2_32.lib" )
Обратите внимание: имя системной библиотеки ws2_32.lib именно такое, как это указано выше. В Сети есть различные варианты написания имени данной библиотеки, что, возможно, связано иным написанием в более ранних версиях ОС Windows. Если вы используете Windows 10, то данная библиотека называется именно ws2_32.lib и находится в стандартной папке ОС: C:/Windows/System32 (проверьте наличие библиотеки у себя, заменив расширение с “lib” на “dll”).
Этап 1: Инициализация сокетных интерфейсов Win32API
Прежде чем непосредственно создать объект сокет, необходимо «запустить» программные интерфейсы для работы с ними. Под Windows это делается в два шага следующим образом:
Нужно определить с какой версией сокетов мы работаем (какую версию понимает наша ОС) и
Запустить программный интерфейс сокетов в Win32API. Ну либо расстроить пользователя тем, что ему не удастся поработать с сокетами до обновления системных библиотек
Первый шаг делается с помощью создания структуры типа WSADATA , в которую автоматически в момент создания загружаются данные о версии сокетов, используемых ОС, а также иная связанная системная информация: WSADATA wsData;
Второй шаг – непосредственный вызов функции запуска сокетов с помощью WSAStartup() . Упрощённый прототип данной функции выглядит так:
int WSAStartup (WORD <запрашиваемая версия сокетов>, WSADATA* <указатель на структуру, хранящую текущую версию реализации сокетов>)
Первый аргумент функции – указание диапазона версий реализации сокетов, которые мы хотим использовать и которые должны быть типа WORD . Этот тип данных является внутренним типом Win32API и представляет собой двухбайтовое слово (аналог в С++: unsigned short ). Функция WSAStartup() просит вас передать ей именно WORD , а она уже разложит значение переменной
Стр. 3 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
внутри по следующему алгоритму: функция считает, что в старшем байте слова указана минимальная версия реализации сокетов, которую хочет использовать пользователь, а в младшем – максимальная. По состоянию на дату написания этой статьи (октябрь 2021 г.) актуальная версия реализации сокетов в Windows – 2. Соответственно, желательно передать и в старшем, и в младшем байте число 2. Для того, чтобы создать такую переменную типа WORD и передать в её старший и младший байты число 2, можно воспользоваться Win32API функцией
MAKEWORD(2,2) .
Можно немного повыёживаться и вспомнить (или полистать MSDN), что функция MAKEWORD(x,y) строит слово по правилу y << 8 | x .Нетрудно посчитать, что при x=y=2 значение функции MAKEWORD в десятичном виде будет 514 . Можешь смело передать в WSAStartup() это значение, и всё будет работать.
Второй аргумент функции – просто указатель на структуру WSADATA , которую мы создали ранее и в которую подгрузилась информация о текущей версии реализации сокетов на данной машине.
WSAStartup() в случае успеха возвращает 0, а в случае каких-то проблем возвращает код ошибки, который можно расшифровать последующим вызовом функции WSAGetLastError() .
Важное замечание: поскольку сетевые каналы связи и протоколы в теории считаются ненадежными (это отдельный большой разговор), то критически важно для сетевого приложения анализировать все возможные ошибки, которые возникают в процессе вызовов сокетных функций. По этой причине каждый вызов таких функций мы будем анализировать на ошибки и в случае их обнаружения завершать сетевые сеансы и закрывать открытые сокеты. Используем для этого переменную erStat типа int .
Также важно после работы приложения обязательно закрыть использовавшиеся сокеты с помощью функции closesocket(SOCKET <имя сокета>) и деинициализировать сокеты Win32API через вызов метода WSACleanup() .
Итого код Этапа 1 следующий:
WSADATA wsData;
int erStat = WSAStartup(MAKEWORD(2 2 ), &wsData);
if ( erStat != 0 ) {
cout << "Error WinSock version initializaion #" cout << WSAGetLastError();
return 1
}
else
cout << "WinSock initialization is OK" << endl
Да, кода мало, а описания много. Так обычно и бывает, когда хочешь глубоко в чем-то разобраться. Так что на лабе будешь в первых рядах.
Этап 2: Создание сокета и его инициализация
Сокет в С++ – это структура данных (не класс) типа SOCKET. Её инициализация проводится через вызов функции socket() , которая привязывает созданный сокет к заданной параметрами транспортной инфраструктуре сети. Выглядит прототип данной функции следующим образом:
SOCKET socket(int <семейство используемых адресов>, int <тип
Стр. 4 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
сокета>, int <тип протокола>)
Семейство адресов: сокеты могут работать с большим семейством адресов. Наиболее частое семейство – IPv4. Указывается как AF_INET .
Тип сокета: обычно задается тип транспортного протокола TCP
( SOCK_STREAM ) или UDP ( SOCK_DGRAM ). Но бывают и так называемые "сырые" сокеты, функционал которых сам программист определяет в процессе использования. Тип обозначается SOCK_RAW
Тип протокола: необязательный параметр, если тип сокета указан как TCP или UDP – можно передать значение 0. Тут более детально останавливаться не будем, т.к. в 95% случаев используются типы сокетов TCP/UDP.
При необходимости подробно почитать про функцию socket() можно здесь.
Функция socket() возвращает дескриптор с номером сокета, под которым он зарегистрирован в ОС. Если же инициализировать сокет по каким-то причинам не удалось – возвращается значение INVALID_SOCKET .
Код Этапа 2 будет выглядеть так:
SOCKET ServSock = socket(AF_INET, SOCK_STREAM, 0 );
if (ServSock == INVALID_SOCKET) {
cout << "Error initialization socket # " << WSAGetLastError closesocket(ServSock);
WSACleanup();
return 1
}
else
cout << "Server socket initialization is OK" << endl
Этап 3: Привязка сокета к паре IP-адрес/Порт
Сокет уже существует, но еще неполноценный, т.к. ему не назначен внешний адрес, по которому его будут находить транспортные протоколы по заданию подключающихся процессов, а также не назначен порт, по которому эти подключающиеся процессы будут идентифицировать процесс-получатель.
Такое назначение делается с помощью функции bind() , имеющей следующий прототип:
int bind(SOCKET <имя сокета, к которому необходимо привязать адрес и порт>, sockaddr* <указатель на структуру, содержащую детальную информацию по адресу и порту, к которому надо привязать сокет>, int <размер структуры, содержащей адрес и порт>)
Функция bind() возвращает 0 , если удалось успешно привязать сокет к адресу и порту, и код ошибки в ином случае, который можно расшифровать вызовом WSAGetLastError() - см. итоговый код Этапа 3 далее.
Тут надо немножно притормозить и разобраться в том, что за такая структура типа sockaddr передается вторым аргументом в функцию bind() . Она очень важна, но достаточно запутанная.
Итак, если посмотреть в её внутренности, то выглядят они очень просто: в ней всего два поля – (1) первое поле хранит семейство адресов, с которыми мы уже встречались выше при инициализации сокета, а (2) второе поле хранит некие упакованные последовательно и упорядоченные данные в размере 14-ти байт.
Стр. 5 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
Бессмысленно разбираться детально как именно эти данные упакованы, достаточно лишь понимать, что в этих 14-ти байтах указан и адрес, и порт, а также дополнительная служебная информация для других системных функций
Win32API .
Но как же явно указать адрес и порт для привязки сокета? Для этого нужно воспользоваться другой структурой, родственной sockaddr , которая легко приводится к этому типу - структурой типа sockaddr_in .
В ней уже более понятные пользователю поля, а именно:
Семейство адресов - опять оно ( sin_family )
Порт ( sin_port )
Вложенная структура типа in_addr , в которой будет храниться сам сетевой адрес ( sin_addr )
Технический массив на 8 байт ( sin_zero[8] )
При приведении типа sockaddr_in к нужному нам типу sockaddr для использования в функции bind() поля Порт (2 байта), Сетевой адрес (4 байта) и Технический массив (8 байт) как раз в сумме дают нам 14 байт, помещающихся в 14 байт, находящихся во втором поле структуры sockaddr . Первые поля у указанных типов совпадают – это семейство адресов сокетов (указываем AF_INET ). Из этого видно, что структуры данных типа sockaddr и sockaddr_in тождественны, содержат одну и ту же информацию, но в разной форме для разных целей.
Соответственно, ввод данных для структуры типа sockaddr_in выглядит следующим образом:
1. Создание структуры типа sockaddr_in : sockaddr_in servInfo;
2. Заполнение полей созданной структуры servInfo
servInfo.sin_family = AF_INET;
servInfo.sin_port = htons(<указать номер порта как unsigned short>); порт всегда указывается через вызов функции htons() , которая переупаковывает привычное цифровое значение порта типа unsigned short в побайтовый порядок понятный для протокола TCP/IP (протоколом установлен порядок указания портов от старшего к младшему байту или «bigendian»).
Далее нам надо указать сетевой адрес для сокета. Тип этого поля – структура типа in_addr , которая по своей сути представляет просто особый «удобный» системным функциям вид обычного строчного IPv4 адреса. Таким образом, чтобы указать этому полю обычный IPv4 адрес, его нужно сначала преобразовать в особый числовой вид и поместить в структуру типа in_addr .
Благо существует функция, которая переводит обычную строку типа char[] , содержащую IPv4 адрес в привычном виде с точками-разделителями в структуру типа in_addr – функция inet_pton() . Прототип функции следующий:
int inet_pton(int <семейство адресов>, char[] <строка,
содержащая IP-адрес в обычном виде с точкой-разделителем>, in_addr* <указатель на структуру типа in_addr, в которую нужно поместить результат приведения строчного адреса в численный>).
Стр. 6 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
В случае ошибки функция возвращает значение меньше 0.
Соответственно, если мы хотим привязать сокет к локальному серверу, то наш код по преобразованию IPv4 адреса будет выглядеть так:
in_addr ip_to_num;
erStat = inet_pton(AF_INET, “127.0.0.1”, &ip_to_num);
if (erStat <= 0) {
cout << "Error in IP translation to special numeric format" << endl;
return 1;
}
Результат перевода IP-адреса содержится в структуре ip_to_num. И далее мы передаем уже в нашу переменную типа sockaddr_in значение преобразованного адреса:
servInfo.sin_addr = ip_to_num;
Вся нужная информация для привязки сокета теперь у нас есть, и она хранится в структуре servInfo . Можно смело вызывать функцию bind() , не забыв при этом привести servInfo из типа sockaddr_in в требуемый
функции sockaddr* . Тогда итоговый код Этапа 3 (слава богу закончили) выглядит так:
in_addr ip_to_num;
erStat = inet_pton(AF_INET, “127.0 0 1”, &ip_to_num); if (erStat <= 0) {
cout << "Error in IP translation to special numeric format"
return 1
}
sockaddr_in servInfo;
ZeroMemory(&servInfo, sizeof (servInfo));
servInfo.sin_family = AF_INET; servInfo.sin_addr = ip_to_num; servInfo.sin_port = htons( 1234 );
erStat = bind(ServSock, (sockaddr*)&servInfo, sizeof (servInfo)); if ( erStat != 0 ) {
cout << "Error Socket binding to server info. Error # " << closesocket(ServSock);
WSACleanup();
return 1
}
else
cout << "Binding socket to Server info is OK" << endl
Этап 4 (для сервера): «Прослушивание» привязанного порта для идентификации подключений
Серверная часть готова к прослушке подключающихся «Клиентов». Для того, чтобы реализовать данный этап, нужно вызвать функцию listen() , прототип которой:
Стр. 7 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
int listen(SOCKET <«слушающий» сокет, который мы создавали на предыдущих этапах>, int <максимальное количество процессов, разрешенных к подключению>)
Второй аргумент: максимально возможное число подключений устанавливается через передачу параметр SOMAXCONN (рекомендуется). Если нужно установить ограничения на количество подключений – нужно указать SOMAXCONN_HINT(N) , где N – кол-во подключений. Если будет подключаться больше пользователей, то они будут сброшены.
После вызова данной функции исполнение программы приостанавливается до тех пор, пока не будет соединения с Клиентом, либо пока не будет возвращена ошибка прослушивания порта. Код Этапа 4 для Сервера:
erStat = listen(ServSock, SOMAXCONN);
if ( erStat != 0 ) {
cout << "Can't start to listen to. Error # " << WSAGetLastE closesocket(ServSock);
WSACleanup();
return 1
}
else {
cout << "Listening..." << endl
}
Этап 4 (для Клиента). Организация подключения к серверу
Код для Клиента до текущего этапа выглядит даже проще: необходимо исполнение Этапов 0, 1 и 2. Привязка сокета к конкретному процессу ( bind() ) не требуется, т.к. сокет будет привязан к серверному Адресу и Порту через вызов функции connect() (по сути аналог bind() для Клиента). Собственно, после создания и инициализации сокета на клиентской стороне, нужно вызвать указанную
функцию connect() . Её прототип:
int connect(SOCKET <инициализированный сокет>, sockaddr* <указатель на структуру, содержащую IP-адрес и Порт сервера>, int <размер структуры sockaddr>)
Функция возвращает 0 в случае успешного подключения и код ошибки в ином случае.
Процедура по добавлению данных в структуру sockaddr аналогична тому, как это делалось на Этапе 3 для Сервера при вызове функции bind() . Принципиально важный момент – в эту структуру для клиента должна заноситься информация о сервере, т.е. IPv4-адрес сервера и номер «слушающего» порта на сервере.
sockaddr_in servInfo;
ZeroMemory(&servInfo, sizeof (servInfo));
servInfo.sin_family servInfo.sin_addr = servInfo.sin_port =
= AF_INET; |
|
ip_to_num; |
// Server's IPv4 after inet_pton( |
htons( 1234 ); |
|
erStat = connect(ClientSock, (sockaddr*)&servInfo, sizeof(servInfo)
if (erStat != 0) {
Стр. 8 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
cout << "Connection to Server is FAILED. Error # " << WSAGe
closesocket(ClientSock);
WSACleanup();
return 1
}
else
cout << "Connection established SUCCESSFULLY. Ready to send
<< endl
Этап 5 (только для Сервера). Подтверждение подключения
После начала прослушивания (вызов функции listen() ) следующей функцией должна идти функция accept() , которую будет искать программа после того, как установится соединение с Клиентом. Прототип функции accept() :
SOCKET accept(SOCKET <"слушающий" сокет на стороне Сервера>, sockaddr* <указатель на пустую структуру sockaddr, в которую будет записана информация по подключившемуся Клиенту>, int* <указатель на размер структуры типа sockaddr>)
Функция accept() возвращает номер дескриптора, под которым зарегистрирован сокет в ОС. Если произошла ошибка, то возвращается значение
INVALID_SOCKET .
Если подключение подтверждено, то вся информация по текущему соединению передаётся на новый сокет, который будет отвечать со стороны Сервера за конкретное соединение с конкретным Клиентом. Перед вызовом accept() нам надо создать пустую структуру типа sockaddr_in , куда запишутся данные подключившегося Клиента после вызова accept() . Пример кода:
sockaddr_in clientInfo;
ZeroMemory(&clientInfo, sizeof (clientInfo));
int clientInfo_size = sizeof (clientInfo);
SOCKET ClientConn = accept(ServSock, (sockaddr*)&clientInfo, &clien
if (ClientConn == INVALID_SOCKET) {
cout << "Client detected, but can't connect to a client. Er closesocket(ServSock);
closesocket(ClientConn);
WSACleanup();
return 1
}
else
cout << "Connection to a client established successfully" <
Всё, соединение между Клиентом и Сервером установлено! Самое время попробовать передать информацию от Клиента к Серверу и обратно. Как мы в начале и договорились, мы будет реализовывать простейший чат между ними.
Этап 6: Передача данных между Клиентом и Сервером
Принимать информацию на любой стороне можно с помощью функции recv() , которая при своём вызове блокирует исполнение кода программы до того момента, пока она не получит информацию от другой стороны, либо пока не произойдет ошибка в передаче или соединении.
Стр. 9 из 27 |
28.02.2022, 11:29 |

Начинающему сетевому программисту / Хабр |
https://habr.com/ru/post/582370/ |
Отправлять информацию с любой стороны можно с помощью функции send() . При вызове данной функции обычно никакого ожидания и блокировки не происходит, а переданные в неё данные сразу же отправляются другой стороне.
Рассмотрим прототипы функций recv() и send() :
int recv(SOCKET <сокет акцептованного соединения>, char[] <буфер для приёма информации с другой стороны>, int <размер буфера>, <флаги>)
int send(SOCKET <сокет акцептованного соединения>, char[] <буфер хранящий отсылаемую информацию>, int <размер буфера>, <флаги>)
Флаги в большинстве случаев игнорируются – передается значение 0.
Функции возвращают количество переданных/полученных по факту байт.
Как видно из прототипов, по своей структуре и параметрам эти функции совершенно одинаковые. Что важно знать:
и та, и другая функции не гарантируют целостности отправленной/полученной информации. Это значит, что при реализации прикладных задач по взаимодействию Клиента и Сервера с их использованием требуется принимать дополнительные меры для контроля того, что все посланные байты действительно посланы и, что еще более важно, получены в том же объеме на другой стороне
предельно внимательно надо относиться к параметру "размер буфера". Он должен в точности равняться реальному количеству передаваемых байт. Если он будет отличаться, то есть риск потери части информации или «замусориванию» отправляемой порции данных, что ведет к автоматической поломке данных в процессе отправки/приёма. И совсем замечательно будет, если размер буфера по итогу работы функции равен возвращаемому значению функции – размеру принятых/отправленных байт.
В качестве буфера рекомендую использовать не классические массивы в С-стиле, а стандартный класс С++ <vector> типа char, т.к. он показал себя как более надежный и гибкий механизм при передаче данных, в особенности при передаче текстовых строк, где важен терминальный символ и «чистота» передаваемого массива.
Сама по себе упаковка и отправка данных делается элементарным использованием функций чтения всей строки до нажатия кнопки Ввода - fgets() с последующим вызовом функции send() , а на другой стороне - приёмом информации через recv() и выводом буфера на экран через cout <<.
Процесс непрерывного перехода от send() к recv() и обратно реализуется через бесконечный цикл, из которого совершается выход по вводу особой комбинации клавиш. Пример блока кода для Серверной части:
vector <char> servBuff(BUFF_SIZE), clientBuff(BUFF_SIZE); short packet_size = 0
while (true) {
packet_size = recv(ClientConn, servBuff.data(), servBuff.si cout << "Client's message: " << servBuff.data() << endl;
cout << "Your (host) message: " fgets(clientBuff.data(), clientBuff.size(), stdin );
// Check whether server would like to stop chatting
Стр. 10 из 27 |
28.02.2022, 11:29 |