Tw-city.info

IT Новости
2 просмотров
Рейтинг статьи
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, и стараться по минимуму использовать системно-зависимый код.

Читать еще:  Уроки программирования бейсик

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

Программирование сетевых приложений (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, работающим с несколькими буферами:

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

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

Вступление.
Итак. Свою новую статью я бы хотял посвятить низкоуровневому сетевому программированию. Постараюсь наиболее полно и одновременно сжато изложить основные принципы сетевого программирования, а так же будет рассмотрен пример построение рабочего многопоточного сервера и клиента. Все примеры разрабатывались и комплировались на 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 — размер структуры адреса

В случае удачи функция возвращает дескриптор нового сокета, в противном случае вовзвращает -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. Разработка сетевых приложений.» и «Камер Д. Разработка приложений типа клиент/сервер» .
Эти книги являются бестселлерами в своей области.

C сетевое программирование

В данной заметке показано писать сетевые приложения (программы). Приложение состоит из двух программ-частей: сервера и клиента. В данной заметке показано как каждую их частей писать. Программы основаны на TCP/IP архитектуре.

Детали того что такое сетевое программирование является предметом отдельной заметки. В данной даны детали именно кодинга.

1 Сетевое программирование

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

Читать еще:  Win 10 загрузиться в безопасном режиме

1.1 Клиентская часть

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

Дабы не зависеть от серверной части будем подключатся к существующим серверам, а именно – к HTTP серверам.

Исходный код данной части выложен на сервере.

Создание сокета Сокет обеспечивает пересылку данных от данного компьютера на другой. Аналог файлового дескриптора (FILE), но для взаимодействия по сети.

AF_INET указывает, что речь идет о современных IP сетях по протоколу версии 4. SOCK_STREAM указывает на потоковое подсоединение, т.е. применительно к IP это подразумевает протокол TCP.

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

Формирование адреса сервера Клиент должен подключится к серверу, а не наоборот. Поэтому у сервера есть адрес (ip адрес плюс номер порта), который необходимо знать для подключения к нему.

Для задания данных параметров используется структура struct sockaddr_in . В поле sin_addr.s_addr указывается ip адрес сервера в шестнадцатеричной системе, а в поле sin_port номер порта. Ввиду того, что данные поля должны быть указаны в совместимом с другими системами формате, для заполнения этих полей принято использовать вспомогательные функции. Для заполнения поля sin_addr.s_addr используется функция inet_addr , а – функция htons . Данные функции обеспечивают, что порядок следования байт в этих числах один и тот же. Функция inet_addr естественно делает ещё и некий разбор текстовой строчки и элементарные преобразования.

Цель нашего тестового примера подключится к серверу машинноезрение.рф и считать страницу html по протоколу http . IP адрес сервера машинноезрение.рф является 178.140.230.40 , а порт для взаимодействия по протоколу http является 80 . В таком случае код будет таким:

Подключение После того как структура с полным адресом (ip4 и порт) задана можно попытаться выполнить подсоединение к серверу. Делается это посредством функции connect ,

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

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

1.2 Серверная часть

Серверная часть ожидает подключения клиентов. После успешного подключения клиента сервер отвечает на его запросы.

Исходный код данной части выложен на сервере.

Создание сокета Создает по полной аналогии с клиентской частью. Единственное, что следует подчеркнуть, что сначала создается сокет самого сервера:

Данный сокет отвечает за ожидание подключения от клиентов. При выполнении подключения для каждого клиента будет создан отдельный сокет.

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

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

Наиболее значимое поле является srv.sin_port , которое и задает порт, к которому будут подключатся клиенты. Поле srv.sin_addr.s_addr задает наш внутренний адрес. В общем случае его можно настраивать (если больше одного сетевого интерфейса). В нашем случае нам все-равно, поэтому указывается параметр INADDR_ANY , который обозначает соединение с любого сетевого интерфейса.

После того как структура заполнена привязка осуществляется так:

Программирование сокетов на 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, и помогите другим вундеркиндам.

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

Ссылка на основную публикацию
ВсеИнструменты 220 Вольт
Adblock
detector
×
×