|
|
Регистрация Восстановить пароль |
Регистрация | Задать вопрос |
Заплачу за решение |
Новые сообщения |
Сообщения за день |
Расширенный поиск |
Правила |
Всё прочитано |
|
|
Опции темы | Поиск в этой теме |
01.11.2009, 19:30 | #1 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Статья: Низкоуровневое сетевое программирование. Пишем клиент/серверное приложение на сокетах Беркли
Вступление.
Итак. Свою новую статью я бы хотял посвятить низкоуровневому сетевому программированию. Постараюсь наиболее полно и одновременно сжато изложить основные принципы сетевого программирования, а так же будет рассмотрен пример построение рабочего многопоточного сервера и клиента. Все примеры разрабатывались и комплировались на Unix-подобной операционной системе и все ниже сказанное будет справедливо для любой Unix. Но, т.к. описываемое является стандартом, - данным материалом смогут воспользоваться и программисты, работающие в среде Windows (я на рассматривал конкретно сетевое программирование в этой ОС, т.к. не использую её), изменения коснуться, разве что, заголовочных файлов. Как было сказано выше - будет рассмотрено низкоуровневое сетевое программирование. Справедливости ради, следует сказать, что оно, на самом деле, не такое уж низкоуровневое, т.к. существуют гораздо более низкие уровни, но все это, как правило, прерогатива ядра ОС/драйверов/железа. Для облегчения работы с сетью, операционной системой предоставляются особые объекты - сокеты (в некоторых книгах их называют "гнезда"), представляющие собой разновидность программных интерфейсов. Они позволяют представить сетевой интерфейс как простое устройство ввода/вывода и работать с ним, почти как с обычным файлом (что истинно, ибо в Unix все устройства представлены как файлы). Для работы с сокетами используются API, разработанные в Калифорнийском университете в городе Беркли (для BSD Unix) в 1983 году. Эти API являются сегодня стандартном де-факто и поддерживаются практически всеми современными операционными системами. Данный программный интерфейс, так же называют сокетами Беркли. В основе сокетов лежат протоколы TCP/IP и UDP. Рассмотрение особенностей каждого из них выходит за пределы данной статьи. Скажу только самое главное: TCP - это протокол, обеспечивающий надежное соединение и гарантированную доставку пакетов. UDP - протокол без установления соединения и без каких либо гарантий доставки пакета. IP - протокол сетевого уровня, служит транспортом для протоколов TCP и UDP. От теории к действию. Для работы с функциями сокетов необходимо подключить ряд заголовочных файлов, рассмотрим их: <sys/socket.h> Самый главный файл, в нем находятся базовые функции сокетов и структуры данных. <netdb.h> Функции для преобразования протокольных имен и имен хостов в числовые адреса. <arpa/inet.h> Функции для работы с числовыми IP-адресами. <netinet/in.h> Семейства адресов/протоколов PF_INET (для IPv4) и (PF_INET6 для IPv6). Включают в себя IP-адреса, а также номера портов TCP и UDP. <netdb.h> Функции для преобразования протокольных имен и имен хостов в числовые адреса. Как было написано выше - сокеты схожи с файлами, их (сокеты) аналогично можно представить в виде числового дескриптора, а затем использовать этот дескриптор в стандартных функциях 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, которая, в дальнейшем, упростит нашу жизнь Код:
Последний раз редактировалось oleg kutkov; 01.11.2009 в 19:38. |
01.11.2009, 19:30 | #2 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Данная функция может быть полезна как для разработки клиента, так и сервера, данный подход позволяет существенно сократить количество исходного кода и избежать дублирования кода. Здесь были использованы новая структура и функция: protoent - является удобным способом передачи параметров для функции сокета и работы с интернет-протоколами.
Функция sock возвращает дескриптор созданного сокета, либо же отрицательное значение в случае неудачи. |
01.11.2009, 19:32 | #3 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Сервер.
Теперь рассмотрим построение полноценного рабочего сервера, на основе этой функции, отвечающего на запросы клиентов. Почему сервера, а не клиента ? Всегда следует начинать с разработки сервера, это удобнее, т.к. последний всегда можно проверить с помощью готового клиента, имеющегося в ОС - 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 на небольшом примере. Код:
|
01.11.2009, 19:34 | #4 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Итак, данная функция выполняет связывания сокета 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 права. |
01.11.2009, 19:35 | #5 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Код сервера.
Код:
|
01.11.2009, 19:35 | #6 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Продолжение.
Код:
Последний раз редактировалось oleg kutkov; 01.11.2009 в 19:44. |
01.11.2009, 19:36 | #7 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Думаю, что еще какие либо пояснения для кода излишни. Комментарии подробно описывают все происходящее и для человека, внимательно прочитавшего всю информацию выше, нет здесь ничего непонятного. В данном коде отсутсвует функция listener - она объеденена с функцией sock, все остальное возложено на функцию main.
После компиляции (gcc server.c -o server) и запуска сервера (./server) можно пробовать подключаться к нему по telnet: Как прекрасно видно - сервер отвечает на подключение к localhost:1231 и при получении строки "hello" - отвечает "hello, 127.0.0.1 !!!", а затем закрывает соединение. Теперь пришла очеред разработать клиента для нашего сервера. |
01.11.2009, 19:37 | #8 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Клиент.
Как было сказано выше - клиент имеет общую часть с сервером, а именно создание сокета. Но, в отличие от сервера, клиенту не нужно производить связывания сокета с адресами и переходить в режим прослушивания. Клиенту достаточно вызвать функцию connect, которая свяжет его сокет с удаленным сокетом сервера. Дальнейший процесс чтения/записи и закрытия соедиения сходен с таковыми у сервера. Рассмотрим функцию connect подробно. int connect(int sid, struct sockaddr* addr_p, int len); Аргументы функции: int sid - дескриптор сокета клиента. struct sockaddr - структура адреса сервера, с которым необходимо соединится int len - размер структур адреса. Для наглядности рассмотрим, так же, диаграму работы клиента. Теперь мы можем объеденить все вышеизученное и функцию connect для создания нашего клиента. Рассмотрим сразу полный исходный код. |
01.11.2009, 19:38 | #9 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Код клиента.
Код:
|
01.11.2009, 19:39 | #10 |
Unix C++ developer
Форумчанин
Регистрация: 16.04.2007
Сообщений: 651
|
Продолжение.
Код:
Последний раз редактировалось oleg kutkov; 01.11.2009 в 19:51. |
|
Похожие темы | ||||
Тема | Автор | Раздел | Ответов | Последнее сообщение |
Простейшее клиент-серверное приложение в Delphi 2009 | RNT | Работа с сетью в Delphi | 15 | 21.11.2010 19:08 |
Возможно-ли Клиент-серверное приложение типа Клиент(Pascal) а сервер(CGI)? | Demol | Работа с сетью в Delphi | 1 | 21.04.2009 16:18 |
Язык низкоуровневое программирование Assembler | jackpatriot | Assembler - Ассемблер (FASM, MASM, WASM, NASM, GoASM, Gas, RosAsm, HLA) и не рекомендуем TASM | 1 | 03.01.2009 19:05 |
Клиент-серверное приложение: Callback | MaTBeu | Общие вопросы C/C++ | 13 | 02.06.2008 20:27 |
Клиент-серверное приложение | veryseldom | Работа с сетью в Delphi | 8 | 20.08.2007 19:57 |