Light-electric.com

IT Журнал
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Программирование сети c

Полное руководство по сетевому программированию для разработчиков игр. Часть 1 (скучная).

«Вот так. Копишь миллионы, копишь.
А потом БАЦ! . тортом тебе в морду!»

Очень-очень известный и всеми любимый герой IT

Когда я впервые столкнулся с необходимостью написать приложение, которое могло бы взаимодействовать с таким же приложением, запущенным на другом компьютере, я был неприятно удивлен дефицитом полезной русскоязычной документации по этому делу. Конечно, я знал английский, и для меня не составило особого труда разобраться в тонкостях сетевого программирования, но что делать человеку который не знает ничего кроме русского («русский язык велик и могук!» :))? Ответа нет. И даже если он знает английский язык, то поначалу это ему не очень-то поможет. Лично я не видел еще ни одного систематизированного и каталогизированного источника информации о программировании сетевых приложений, который бы в той или иной степени охватывал весь этот огромный хаос.

Итак, уже сделано все, что касается однопользовательских режимов игры, однако было бы неплохо добавить возможность игры по сети. И ты, конечно, даже и не представляешь, с чего начать. С Интернетом ты ранее сталкивался только в двух случаях: форум на gamedev.ru и навязчивые pop-ups от порносайтов. Хорошо, я тебе помогу, вернее, тебе поможет мой CGNP. Для того чтобы не было недоразумений, я сразу оговорюсь, что написанное ниже рассчитано на тех, кто кодит на с/с++ (MSVC++ в Windows-системах и gсс/g++ в никсах). Я также предполагаю, что у читателей есть хотя бы минимальный набор знаний об устройстве и функционировании компьютерных сетей. Необязателен, но желателен справочник по Windows API 32 под рукой или доступ к MSDN (юниксоидам в этом плане повезло — man pages не могут быть «не под рукой» ;)). Еще я хотел бы сделать предупреждение: представленный ниже материал не претендует на полноту освещения затронутых в нем тем, а также на абсолютную точность.

И наконец, перед тем, как мы окунемся в омут с головой, я дам еще один совет: дружище, выучи все-таки английский! Он тебе очень пригодится. Ведь когда ты захочешь стать гуру сетевого программирования, тебе придется прочесть очень много RFC-документов, а ошибки перевода и неправильного толкования технических спецификаций являются «бомбами замедленного действия»!

Модель OSI

Чтобы понять все принципы взаимодействия компьютеров на расстоянии, надо знать так называемую модель OSI (ISO OSI == International Organization for Standardization Open System Interconnection — Взаимодействие Открытых Систем по Стандарту Международной Организации по Стандартизации). Теперь можем сделать перерыв, чтобы ты, уважаемый читатель, смог еще пять раз перечитать предыдущее предложение и понять его смысл, после чего мы разберемся, что такое OSI, и с чем ее едят.

Итак, модель OSI определяет несколько «уровней» взаимодействия компьютеров на расстоянии (я намеренно избегаю словосочетания «по сети», и ты скоро поймешь почему). Вот эти уровни:

Это уровень, максимально приближенный к пользовательскому интерфейсу. Пользователи конечного программно продукта не волнует, как передаются данные, зачем и через какое место. Он сказали «ХОЧУ!» — а мы, программисты, должны им это обеспечить. В качестве примера можно взять на рассмотрение любую сетевую игру: для игрока она работает на этом уровне. Пользователь куда то ткнул, в интерфейсной части программы зафиксирована его команда. Что надо передать? Что то приняли, что произошло в мире игры?

Здесь программист имеет дело с данными, полученными от низших уровней. В основном, это конвертирование и представление данных в удобоваримом для пользователя виде.

Этот уровень позволяет пользователям осуществлять «сеансы связи». То есть именно на этом уровне передача пакетов становится для программиста прозрачной, и он может, не задумываясь о реализации, непосредственно передавать данные, как цельный поток. Здесь на сцену вступают протоколы HTTP, FTP, Telnet, SMTP и т.д.

Осуществляет контроль над передачей данных (сетевых пакетов). То есть, проверяет их целостность при передаче, распределяет нагрузку и т.д. Этот уровень реализует такие протоколы, как TCP, UDP и т.д. Для нас представляет наибольший интерес.

Логически контролирует адресацию в сети, маршрутизацию и т.д. Должен быть интересен разработчикам новых протоколов и стандартов. На этом уровне реализованы протоколы IP, IPX, IGMP, ICMP, ARP. В основном, управляется драйверами и операционными системами. Сюда влезать, конечно, стоит, но только когда ты знаешь, что делаешь, и полностью в себе уверен.

Этот уровень определяет, как биты собираются в пакеты, какую служебную информацию надо передавать и как на неё реагировать, но поле данных каждого пакета максимально сырое и представляет из себя просто биты в навал, нет вообще ни какой последовательности пакетов в длинных информационных потоках. В поле данных просто вложены пакеты сетевых протоколов, всё, что чего не хватает должно быть сделано в них, или ещё выше.

1. Аппаратный (Физический)

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

Итак, подведем небольшой итог к тому, что было представлено. Мы видим, что, чем выше уровень — тем выше степень абстракции от передачи данных, к работе с самими данными. Это и есть смысл всей модели OSI: поднимаясь все выше и выше по ступенькам ее лестницы, мы все меньше и меньше заботимся о том, как данные передаются, мы все больше и больше становимся заинтересованными в самих данных, нежели в средствах для их передачи. Каждый следующий уровень скрывает в себе предыдущий, облегчая жизнь пользователю этого уровня, будь он программист, радиоинженер или твоя подруга, которая не знает, как настроить MS Outlook Express.

Нас, как программистов, интересуют уровни 3, 4 и 5. Мы должны использовать средства, которые они предоставляют, для того чтобы построить 6 и 7 уровни, с которыми смогут работать конечные пользователи.

Сокеты и бла-бла-бла.

У каждой уважающей себя современной операционной системы есть средства для взаимодействия с другими компьютерами. Самым распространенным среди программистов средством для упомянутых целей являются сокеты. Сокеты — это API (Application Programming Interface — Интерфейс Программирования Приложений) для работы с уровнями OSI. Сокеты настолько гибки, что позволяют работать почти с любым из уровней модели OSI. Хочешь — формируй IP-пакеты руками и займись хакингом, отправляя «неправильные» пакеты, которые будут вводить сервера в ступор, хочешь — займись более благоразумным делом и создай новый удобный голосовой чат, хочешь — игрульку по сети гоняй, не хочешь — твое право, но этот случай мы в данном руководстве не рассматриваем. 🙂

Когда мы создаем сокет (socket — гнездо), мы получаем возможность доступа к нужному нам уровню OSI. Ну а дальше мы можем использовать соответствующие вызовы для взаимодействия с ним. Для того чтобы понять сокеты, можно провести аналогию с телефонным аппаратом и телефонной трубкой. Сокеты устроены таким образом, что они могут взаимодействовать с ОС на любом уровне OSI, скрывая ту часть реализации, которой мы не интересуемся (тебя же не волнует, как работает телефон, когда ты набираешь 03). Телефоны и сокеты бывают разные: бывают старые телефоны с дисковым набором и бывают низкоуровневые сокеты для работы с Ethernet-фреймами, бывают супер-модные цифровые телефоны и бывают сокеты для работы с верхними уровнями стека протоколов. и т.д. Причем вызовы для всех типов сокетов одни и те же, что, имхо, очень удобно. Когда мы создаем сокет, мы также заставляем систему организовать два канала: входящий (это как громкоговоритель у телефона) и исходящий (микрофон). Осуществляя чтение и запись в эти каналы, мы приказываем системе взять на себя дальнейшую судьбу данных, т.е. передать и проследить, чтоб данные дошли вовремя, в нужной последовательности, не искаженные и т.п. Система должна давать (и дает) максимум гарантий (для каждого уровня OSI — гарантии свои), что данные будут переданы правильно. Наша задача — поместить их в очередь, а на другом конце — прочитать из входящей очереди и обработать должным образом. Все остальное — нам ни к чему. Еще один плюс — сокеты переносимы. То есть изначально концепция сокетов была разработана в Berkeley, поэтому классическая реализация сокетов называется Berkeley sockets или BSD sockets (BSD == Berkeley Software Distribution). В дальнейшем, почти все ОС тем или иным образом унаследовали эту реализацию. В каждой ОС степень поддержки сокетов разная, но точно могу сказать: в современных операционных системах MS и *nix — сокеты поддерживаются настолько, насколько нам, геймдевелоперам, они могут понадобиться. Больше нам и не нужно, потому что мы не кодим под экзотические ОС, потому что, в свою очередь, геймеры (они наша целевая аудитория) на таковых не сидят. Однако по мере изучения мы будем придерживаться классической реализации BSD sockets, и стараться по минимуму использовать системно-зависимый код.

Читать еще:  Классы в программировании c

Короче, сокеты надо представлять себе так: сокет — это окно в мониторе, которое выходит во внешний мир. Если у кого-то еще открыто такое же окно, то можно контактировать. Ну что? Если вы уже достаточно раскочегарились, уважаемые читатели, то пора к делу.

Блог программистов

Низкоуровневое сетевое программирование. Пишем клиент/серверное приложение на сокетах Беркли

Вступление.
Итак. Свою новую статью я бы хотял посвятить низкоуровневому сетевому программированию. Постараюсь наиболее полно и одновременно сжато изложить основные принципы сетевого программирования, а так же будет рассмотрен пример построение рабочего многопоточного сервера и клиента. Все примеры разрабатывались и комплировались на Unix-подобной операционной системе и все ниже сказанное будет справедливо для любой Unix. Но, т.к. описываемое является стандартом, — данным материалом смогут воспользоваться и программисты, работающие в среде Windows (я на рассматривал конкретно сетевое программирование в этой ОС, т.к. не использую её), изменения коснуться, разве что, заголовочных файлов.
Как было сказано выше — будет рассмотрено низкоуровневое сетевое программирование. Справедливости ради, следует сказать, что оно, на самом деле, не такое уж низкоуровневое, т.к. существуют гораздо более низкие уровни, но все это, как правило, прерогатива ядра ОС/драйверов/железа. Для облегчения работы с сетью, операционной системой предоставляются особые объекты — сокеты (в некоторых книгах их называют «гнезда»), представляющие собой разновидность программных интерфейсов. Они позволяют представить сетевой интерфейс как простое устройство ввода/вывода и работать с ним, почти как с обычным файлом (что истинно, ибо в Unix все устройства представлены как файлы). Для работы с сокетами используются API, разработанные в Калифорнийском университете в городе Беркли (для BSD Unix) в 1983 году. Эти API являются сегодня стандартном де-факто и поддерживаются практически всеми современными операционными системами. Данный программный интерфейс, так же называют сокетами Беркли. В основе сокетов лежат протоколы TCP/IP и UDP. Рассмотрение особенностей каждого из них выходит за пределы данной статьи. Скажу только самое главное: TCP — это протокол, обеспечивающий надежное соединение и гарантированную доставку пакетов. UDP — протокол без установления соединения и без каких либо гарантий доставки пакета. IP — протокол сетевого уровня, служит транспортом для протоколов TCP и UDP.

От теории к действию.
Для работы с функциями сокетов необходимо подключить ряд заголовочных файлов, рассмотрим их:

Самый главный файл, в нем находятся базовые функции сокетов и структуры данных.

Функции для преобразования протокольных имен и имен хостов в числовые адреса.

Функции для работы с числовыми IP-адресами.

Семейства адресов/протоколов PF_INET (для IPv4) и (PF_INET6 для IPv6). Включают в себя IP-адреса, а также номера портов TCP и UDP.

Функции для преобразования протокольных имен и имен хостов в числовые адреса.

Как было написано выше — сокеты схожи с файлами, их (сокеты) аналогично можно представить в виде числового дескриптора, а затем использовать этот дескриптор в стандартных функциях read и write. Для получения нового дескриптора сокета используется функция:
int socket(int domain, int type, int protocol);
Рассмотрим параметры:
int domain — этот параметр задает правила использования именования и формат адреса. Следует указывать PF_INET, если планируется работать с IPv4, либо PF_INET6 для IPv6.
int type — этот параметр задает тип сокета. Следует указывать SOCK_STREAM, если планируется использование протокола TCP, либо SOCK_DGRAM — в случае использования UDP.
int protocol — этот параметр указывает конкретный протокол, который следует использовать с данным сокетом. В качестве параметра следует использовать экземпляр структуры struct protoent. Ниже будет рассмотрено, как с помощью этой структуры и строк «tcp» или «udp» задать необходим протокол. Так же параметр может быть просто равен 0, тогда ядро само выберет соответствующий протокол.
Теперь посмотрим как это выглядит все вместе, написав небольшую функцию sock, которая, в дальнейшем, упростит нашу жизнь

Данная функция может быть полезна как для разработки клиента, так и сервера, данный подход позволяет существенно сократить количество исходного кода и избежать дублирования кода. Здесь были использованы новая структура и функция: protoent — является удобным способом передачи параметров для функции сокета и работы с интернет-протоколами.
Функция sock возвращает дескриптор созданного сокета, либо же отрицательное значение в случае неудачи.
Сервер.
Теперь рассмотрим построение полноценного рабочего сервера, на основе этой функции, отвечающего на запросы клиентов. Почему сервера, а не клиента ? Всегда следует начинать с разработки сервера, это удобнее, т.к. последний всегда можно проверить с помощью готового клиента, имеющегося в ОС — telnet, а клиента уже создавать на готовый сервер. Итак. Выше мы получили дескриптор сокета, что мы с ним должны сделать, что бы получился сервер ? Необходимо связать созданный сокет с определенным сетевым интерфейсом, на котором сервер будет «слушать» входящие подключения. Связывание выполняется с помощью функции bind, рассмотрим ее.
int bind(int sid, struct sockaddr* addr_p, int len);
Аргументы:
int sid — собственно сам дескриптор сокета.
struct sockaddr* addr_p — указатель на структуру адреса, с которым связывается сокет.
int len — длина структуры sockaddr

При успешном выполнении функция возвращает 0, при неудаче возвращает -1.
Перед тем, как рассматривать пример использования функции bind, познакомимся с еще одной важной функций — listen, а затем напишем полноценный код. Функция listen предназначена для «прослушивания» сетевого интерфейса, с которым связан серверный сокет. Т.е. она переводит сокет в режим ожидания входящих подключений.
Рассмотрим детально функцию listen.
int listen(int sid, int size);

Аргументы функции:
int sid — дескриптор сокета.
int size — максимальное число клиентов в очереди. Т.к. сокет не может обработать одновременно сразу все подключения — все запросы выстраиваются в очередь и ожидают своей обработки.

При успешном выполнении возвращается 0, при неуспешном возвращается -1.
Теперь рассмотрим применение функций bind и listen на небольшом примере.

Итак, данная функция выполняет связывания сокета sock, с адресом host и портом port. Для преобразования host и port из строковых значений (например «192.168.1.0» и «21») в корректные бинарные значения используются функции gethostbyname и htons. Значения адреса и порта инициализируют поля структуры sockaddr_in, которая является параметром функции bind. Функция listener возвращает результат вызова функции listen.
На данно этапе мы научились создавать сокет, связывать его с сетевым интерфейсом и переключать сокет в режим прослушивания. Теперь осталось научится обрабатывать входящие подключения. В этом на поможет функция accept. Принцип работы очень прост: когда выполнение кода доходит до этой функции — выполнение останавливается. При входящем подключении выполнение кода продолжается и начинается процесс обмена данным с клиентом. Тут есть один важный момент — после успешного входящего подключения, функция accept возвращает новый дескриптор сокета. Над этим дескриптором и производятся операции чтения/записи, после чего этот дескритор закрывается с помощью функции close (по завершении работы сервера следует закрывать и сокет, созданный в функции sock, как и всякий файловый дескриптор). Для удобства цепочку «accept -> read/write -> close» заключают в бесконечный цикл. Для записи и чтения в сокет используются обычные функции write и read.
Рассмотрим функцию accept.
int accept(int sid, struct sockaddr* addr_p, int len_p);
Аргументы:
int sid — дескриптор сокета.
struct sockaddr — структура адреса, она инициализируется адресом подключившегося клиента
int len_p — размер структуры адреса

Читать еще:  Spa в программировании это

В случае удачи функция возвращает дескриптор нового сокета, в противном случае вовзвращает -1.

Подытожим, мы рассмотрели все функции, необходимые для построения полноценного сервера. Для большего понимания серверной архитектуры я нарисовал эту небольшую диаграмму.

Теперь самое время объеденить все вышеизученное и написать полный исходный код сервера, и протестировать его! Наш сервер будет принимать входящие подключения на всех доступных сетевых интерфейсах и при получении строки «hello» — будет отвечать строкой вида «hello, %computeradress% . », где %computeradress% — адрес удаленной машины.
Следует отметить, что в Unix, сервер, запущенный из под обычного пользователя, имеет право прослушивать на портах не менее 1024, для прослушивания на портах 0-1024 необходимы root права.

Код сервера.

Думаю, что еще какие либо пояснения для кода излишни. Комментарии подробно описывают все происходящее и для человека, внимательно прочитавшего всю информацию выше, нет здесь ничего непонятного. В данном коде отсутсвует функция listener — она объеденена с функцией sock, все остальное возложено на функцию main.
После компиляции (gcc server.c -o server) и запуска сервера (./server) можно пробовать подключаться к нему по telnet:

Как прекрасно видно — сервер отвечает на подключение к localhost:1231 и при получении строки «hello» — отвечает «hello, 127.0.0.1 . », а затем закрывает соединение.
Теперь пришла очеред разработать клиента для нашего сервера.

Клиент.
Как было сказано выше — клиент имеет общую часть с сервером, а именно создание сокета. Но, в отличие от сервера, клиенту не нужно производить связывания сокета с адресами и переходить в режим прослушивания. Клиенту достаточно вызвать функцию connect, которая свяжет его сокет с удаленным сокетом сервера. Дальнейший процесс чтения/записи и закрытия соедиения сходен с таковыми у сервера.
Рассмотрим функцию connect подробно.
int connect(int sid, struct sockaddr* addr_p, int len);
Аргументы функции:
int sid — дескриптор сокета клиента.
struct sockaddr — структура адреса сервера, с которым необходимо соединится
int len — размер структур адреса.
Для наглядности рассмотрим, так же, диаграму работы клиента.

Теперь мы можем объеденить все вышеизученное и функцию connect для создания нашего клиента. Рассмотрим сразу полный исходный код.
Код клиента.

Клиент принимает два аргумента командной строки — адрес и порт сервера, если количество аргументов отличается — выводится подсказка по использованию. Далее происходит попытка подключения, с помощью совмещнной функции создания сокета и connect. В случае успешного подключения — выполняется процедура обмена сообщениями, почти как на сервере. После чего происходит закрытие сокета и завершение программы клиента.
Рабочий клиент:

Заключение.
В этой статье были рассмотрены основы клиент/сервных приложений с использованием сокетов Беркли. Приведенный код лежит в основе практически любого подобного приложения. Все различие сводится в реализации протокола, а именно процедурах обмена сообщениями/пакетами данных с помощью read/write. Так же, для одновременной обработки множества запросов — сервера выполняют многопоточными. Принцип прост, после того как произошло входящее подключение — происходит запуск нового потока, с передачей ему сокета. В новом потоке происходит процесс обмена данными с клиентом, а основной поток в это время снова ожидает входящих подключений и процесс повторяется для нового клиента, но все это уже тема отдельной статьи.
Разумеется своей статьей я донес лишь основы сетевого программирования, т.к. это очень сложная и интересная тема. Если у Вас возникли какие либо вопросы или трудности — спрашивайте.
Очень рекомендую прочесть: «Стивенс Р. Unix. Разработка сетевых приложений.» и «Камер Д. Разработка приложений типа клиент/сервер» .
Эти книги являются бестселлерами в своей области.

Программирование сетевых приложений (TCP/IP) на C/C++

Простейшие примеры

TCP/IP

Что следует иметь ввиду при разработке с TCP

  1. TCP не выполняет опрос соединения (не поможет даже keep alive — он нужен для уборки мусора, а не контроля за состоянием соединения). Исправляется на прикладном уровне, например, реализацией пульсации. Причем на дополнительном соединении.
  2. Задержки при падении хостов, разрыве связи.
  3. Необходимо следить за порядком получения сообщений.
  4. Заранее неизвестно сколько данных будет прочитано из сокета. Может быть прочитано несколько пакетов сразу!
  5. Надо быть готовым ко всем внештатным ситуациям:
    • постоянный или временный сбой сети
    • отказ принимающего приложения
    • аварийный сбой самого хоста на принимающей стороне
    • неверное поведение хоста на принимающей стороне
    • учитывать особенности сети функционирования приложения (глобальная или локальная)

OSI и TCP/IP

Порты

Полный список зарегистрированных портов расположен по адресу: http://www.isi.edu/in-notes/iana/assignment/port-numbers. Подать заявку на получение хорошо известного или зарегистрированного номера порта можно по адресу http://www.isi.edu/cgi-bin/iana/port-numbers.pl.

Состояние TIME-WAIT

После активного закрытия для данного конкретного соединения стек входит в состояние TIME-WAIT на время 2MSL (максимальное время жизни пакета) для того, чтобы

  1. заблудившийся пакет не попал в новое соединение с такими же параметрами.
  2. если потерялся ACK, подтверждающий закрытие соединения, с активной стороны, пассивная снова пощлёт FIN, активная, игнорируя TIME-WAIT уже закрыла соединение, поэтому пассивная сторона получит RST.

Отключение состояния TIME-WAIT крайне не рекомендуется, так как это нарушает безопасность TCP соединения, тем не менее существует возможность сделать это — опция сокета SO_LINGER.

Штатная ситуация — перезагрузка сервера может пострадать из-за наличия TIME-WAIT. Эта проблема решается заданием опции SO_REUSEADDR.

Отложенное подтверждение и алгоритм Нейгла.

Алгоритм Нейгла используется для предотвращения забивания сети мелкими пакетами и имеет очень простую формулировку — запрещено посылать второй маленький пакет до тех пор, пока не придет подтверждение на первый. Тем не менее данные отправляются при выполнении хотя бы одного из следующих условий:

  • можно послать полный сегмент размером MSS (максимальный размер сегмента)
  • соединение простаивает, и можно опустошить буфер передачи
  • алгоритм Нейгла отключен, и можно опустошить буфер передачи
  • есть срочные данные для отправки
  • есть маленьки сегмент, но его отправка уже задержана на достаточно длительное время (таймер терпения persist timer на тайм-аут ретрансмиссии RTO )
  • окно приема, объявленное хостом на другом конце, открыто не менее чем на половину
  • необходимо повторно передать сегмент
  • требуется послать ACK на принятые данные
  • нужно объявить об обновлении окна

Кроме того, существует отложенное подтверждение. При этом хост, получивший сегмент, старается задержать отправку ACK, чтобы послать её вместе с данными.

Алгоритм Нейгла в купе с отложенным подтверждением в резонансе дают нежелательные задержки. Поэтому часто его отключают. Отключение алгоритма Нейгла производится заданием опции TCP_NODELAY

Но более правильным было бы проектировать приложение таким образом, чтобы было как можно меньше маленьких блоков. Лучше писать большие. Для этого можно объединять данные самостоятельно, а можно пользоваться аналогом write, работающим с несколькими буферами:

Программирование сокетов на C / C ++

Что такое сокет программирования?
Сокетное программирование — это способ соединения двух узлов в сети для связи друг с другом. Один сокет (узел) прослушивает определенный порт с IP-адреса, а другой сокет обращается к другому, чтобы сформировать соединение. Сервер формирует сокет слушателя, в то время как клиент обращается к серверу.

Диаграмма состояний для модели сервера и клиента

Этапы для сервера

    Создание сокета:

sockfd: дескриптор сокета, целое число (как дескриптор файла)
домен: целое число, домен связи, например, AF_INET (протокол IPv4), AF_INET6 (протокол IPv6)
тип: тип связи
SOCK_STREAM: TCP (надежный, ориентированный на соединение)
SOCK_DGRAM: UDP (ненадежный, без установления соединения)
protocol: значение протокола для интернет-протокола (IP), равное 0. Это то же число, которое указывается в поле протокола в заголовке IP пакета.

Setsockopt:

Это помогает в манипулировании опциями для сокета, на который ссылается дескриптор файла sockfd. Это совершенно необязательно, но помогает в повторном использовании адреса и порта. Предотвращает ошибку, такую как: «адрес уже используется».

Bind:

После создания сокета функция bind связывает сокет с адресом и номером порта, указанными в addr (пользовательская структура данных). В примере кода мы привязываем сервер к локальному узлу, поэтому мы используем INADDR_ANY для указания IP-адреса.

Слушать:

Он переводит сокет сервера в пассивный режим, в котором он ждет, пока клиент подойдет к серверу, чтобы установить соединение. Журнал ожидания определяет максимальную длину, до которой может увеличиваться очередь ожидающих соединений для sockfd. Если запрос на подключение приходит, когда очередь заполнена, клиент может получить ошибку с указанием ECONNREFUSED.

Читать еще:  Наследование в программировании это

Accept:

Он извлекает первый запрос на соединение в очереди ожидающих соединений для прослушивающего сокета, sockfd, создает новый подключенный сокет и возвращает новый дескриптор файла, ссылающийся на этот сокет. В этот момент устанавливается соединение между клиентом и сервером, и они готовы к передаче данных.

Этапы для клиента

  • Сокетное соединение: точно так же, как и при создании сокета сервера
  • Connect:

Системный вызов connect () соединяет сокет, указанный дескриптором файла sockfd, с адресом, указанным в addr. Адрес и порт сервера указаны в addr.

Реализация
Здесь мы обмениваемся одним приветственным сообщением между сервером и клиентом, чтобы продемонстрировать модель клиент / сервер.

// Серверная программа на C / C ++ для демонстрации программирования Socket
#include
#include
#include
#include
#include
#include
#define PORT 8080

int main( int argc, char const *argv[])

int server_fd, new_socket, valread;

struct sockaddr_in address;

int addrlen = sizeof (address);

char *hello = «Hello from server» ;

// Создание дескриптора файла сокета

if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)

perror ( «socket failed» );

// Принудительное подключение сокета к порту 8080

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,

address.sin_port = htons( PORT );

// Принудительное подключение сокета к порту 8080

if (bind(server_fd, ( struct sockaddr *)&address,

perror ( «bind failed» );

if (listen(server_fd, 3)

if ((new_socket = accept(server_fd, ( struct sockaddr *)&address,

valread = read( new_socket , buffer, 1024);

printf ( «%sn» ,buffer );

send(new_socket , hello , strlen (hello) , 0 );

printf ( «Hello message sentn» );

// Клиентская программа C / C ++ для демонстрации программирования Socket
#include
#include
#include
#include
#include
#define PORT 8080

int main( int argc, char const *argv[])

int sock = 0, valread;

struct sockaddr_in serv_addr;

char *hello = «Hello from client» ;

if ((sock = socket(AF_INET, SOCK_STREAM, 0))

printf ( «n Socket creation error n» );

// Преобразование адресов IPv4 и IPv6 из текста в двоичную форму

if (inet_pton(AF_INET, «127.0.0.1» , &serv_addr.sin_addr)

printf ( «nInvalid address/ Address not supported n» );

if (connect(sock, ( struct sockaddr *)&serv_addr, sizeof (serv_addr))

printf ( «nConnection Failed n» );

send(sock , hello , strlen (hello) , 0 );

printf ( «Hello message sentn» );

valread = read( sock , buffer, 1024);

printf ( «%sn» ,buffer );

Компиляция:
gcc client.c -o клиент
gcc server.c -o сервер

Выход:

Next: Программирование на языке сокетов на C / C ++: обработка нескольких клиентов на сервере без многопоточности
Эта статья предоставлена Акшат Синха . Если вы как GeeksforGeeks и хотели бы внести свой вклад, вы также можете написать статью с помощью contribute.geeksforgeeks.org или по почте статьи contribute@geeksforgeeks.org. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.

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

Веб-сервер на C++ и сокетах

Создадим HTTP-сервер, который обрабатывает запросы браузера и возвращает ответ в виде HTML-страницы.

Введение в HTTP

Для начала разберемся, что из себя представляет HTTP. Это текстовый протокол для обмена данными между браузером и веб-сервером.

Пример HTTP-запроса:

Первая строка передает метод запроса, идентификатор ресурса (URI) и версию HTTP-протокола. Затем перечисляются заголовки запроса, в которых браузер передает имя хоста, поддерживаемые кодировки, cookie и другие служебные параметры. После каждого заголовка ставится символ переноса строки rn .

У некоторых запросов есть тело. Когда отправляется форма методом POST, в теле запроса передаются значения полей этой формы.

Тело запроса отделяется от заголовков одной пустой строкой. Заголовок «Content-Type» говорит серверу, в каком формате закодировано тело запроса. По умолчанию, в HTML-форме данные кодируются методом «application/x-www-form-urlencoded».

Иногда необходимо передать данные в другом формате. Например, при загрузке файлов на сервер, бинарные данные кодируются методом «multipart/form-data».

Сервер обрабатывает запрос клиента и возвращает ответ.

Пример ответа сервера:

В первой строке ответа передается версия протокола и статус ответа. Для успешных запросов обычно используется статус «200 OK». Если ресурс не найден на сервере, возвращается «404 Not Found».

Тело ответа так же, как и у запроса, отделяется от заголовков одной пустой строкой.

Полная спецификации протокола HTTP описывается в стандарте rfc-2068. По понятным причинам, мы не будем реализовывать все возможности протокола в рамках этого материала. Достаточно реализовать поддержку работы с заголовками запроса и ответа, получение метода запроса, версии протокола и URL-адреса.

Что будет делать сервер?

Сервер будет принимать запросы клиентов, парсить заголовки и тело запроса, и возвращать тестовую HTML-страничку, на которой отображены данные запроса клиента (запрошенный URL, метод запроса, cookie и другие заголовки).

О сокетах

Для работы с сетью на низком уровне традиционно используют сокеты. Сокет — это абстракция, которая позволяет работать с сетевыми ресурсами, как с файлами. Мы можем писать и читать данные из сокета почти так же, как из обычного файла.

В этом материале мы будем работать с виндовой реализацией сокетов, которая находится в заголовочном файле . В Unix-подобных ОС принцип работы с сокетами такой же, только отличается API. Вы можете подробнее почитать о сокетах Беркли, которые используются в GNU/Linux.

Создание сокета

Создадим сокет с помощью функции socket , которая находится в заголовочном файле . Для работы с IP-адресами нам понадобится заголовочный файл .

Мы подготовили все данные, которые необходимо для создания сокета и создали сам сокет. Функция socket возвращает целочисленное значение файлового дескриптора, который выделен операционной системой под сокет.

Привязка сокета к адресу (bind)

Следующим шагом, нам необходимо привязать IP-адрес к сокету, чтобы он мог принимать входящие соединения. Для привязки конкретного адреса к сокету используется фукнция bind . Она принимает целочисленный идентификатор файлового дескриптора сокета, адрес (поле ai_addr из структуры addrinfo ) и размер адреса в байтах (используется для поддержки IPv6).

Подготовка сокета к принятию входящих соединений (listen)

Подготовим сокет к принятию входящих соединений от клиентов. Это делается с помощью функции listen . Она принимает дескриптор слушающего сокета и максимальное количество одновременных соединений.

В случае ошибки, функция listen возращает значение константы SOCKET_ERROR . При успешном выполнении она вернет 0.

В константе SOMAXCONN хранится максимально возможное число одновременных TCP-соединений. Это ограничение работает на уровне ядра ОС.

Ожидание входящего соединения (accept)

Функция accept ожидает запрос на установку TCP-соединения от удаленного хоста. В качестве аргумента ей передается дескриптор слушающего сокета.

При успешной установке TCP-соединения, для него создается новый сокет. Функция accept возвращает дескриптор этого сокета. Если произошла ошибка соединения, то возвращается значение INVALID_SOCKET .

Получение запроса и отправка ответа

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

При успешном выполнении функция recv вернет размер полученных данных. В случае ошибки возвращается значение SOCKET_ERROR . Если соединение было закрыто клиентом, то возвращается 0.

Мы создадим буфер размером 1024 байта для сохранения HTTP-запроса.

После получения запроса мы сразу же отправили ответ клиенту с помощью функции send . Она принимает дескриптор сокета, строку с данными ответа и размер ответа в байтах.

В случае ошибки, функция возвращает значение SOCKET_ERROR . В случае успеха — количество переданных байт.

Попробуем скомпилировать программу, не забыв предварительно завершить функцию main .

Если скомпилировать и запустить программу, то окно консоли «подвиснет» в ожидании запроса на установление TCP-соединения. Откройте в браузере адрес http://127.0.0.1:8000/. Сервер вернет ответ, как на рисунке ниже и завершит работу.

Последовательная обработка запросов

Чтобы сервер не завершал работу после обработки первого запроса, а продолжал обрабатывать новые соединения, нужно зациклить ту часть кода, которая принимает запрос на установку соединения и возвращает ответ.

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

Во второй части этой статьи мы напишем парсер HTTP-заголовков и создадим нормальное API для управления HTTP-запросами и ответами.

Примечание: если вы используете MinGW в Windows, то библиотеку Ws2_32.lib нужно вручную прописать в настройках линковщика.

Ссылка на основную публикацию
Adblock
detector