(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Интерфейс прикладного программирования Socket API, Часть 1: Создание собственного сервера

Источник: rus-linux
Н.Ромоданов

Оригинал: "Creating Your Own Server: The Socket API, Part 1" 
Автор: Pankaj Tanwar 
Дата публикации: August 1, 2011 
Перевод: Н.Ромоданов 
Дата перевода: июль 2012 г.

В этой серии статей, передназначенных для новичков сетевого программирования (знание языка C является обязательным условием), мы узнаем, как с помощью интерфейса прикладного программирования Socket API системы UNIX создавать сервера и сетевые клиентские программы. Мы начнем с создания простых программ типа "клиент-сервер", а затем попробуем сделать что-нибудь более сложное. Мы также попытаемся понять, как работают различные сервера. Я постарался включить в опиание множество подробностей, но если вы обнаружите, что некоторая информация отсутствует, то, пожалуйста, не стесняйтесь сообщить мне об этом в комментариях.

Поскольку мы при сетевом программировании рассматриваем сокеты, новички должны сначала разобраться с уровнями модели OSI и с протоколами, используемыми на этих уровнях. Каждый уровень в этой модели отвечает за выполнение определенной работы, что в результате делает возможным передачу данных по сети. В каждом уровне происходит абстрагирование работы, выполняемой на более низких уровнях, и представление этой работы на уровень, находящийся выше. Если вы не знакомы с эталонной моделью взаимодействия открытых систем ISO OSI (Open Systems Interconnection) Reference Model, я рекомендую о ней почитать. Хорошей отправной точкой является Википедия.

Здесь мы сосредоточимся на сессионном уровне (в котором происходит создание сессий и поддержка работы с ними) и транспортном уровне, на котором обеспечивает надежная или ненадежная передача данных от отправителя к получателю. Есть несколько протоколов - TCP (для надежных соединений), UDP (для ненадежных соединений) и SCTP (расширенный протокол с возможностью множественного подключения). Информацию о протоколах TCP / IP, пожалуйста, смотрите здесь и здесь.

Протокол Transmission Control Protocol (TCP)

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

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

В протоколе TCP также определяется, как долго ждать подтверждения, - для этого используется оценочное значение RTT (Round Trip Time - время прохождения маршрута), задаваемое между сервером и клиентом. В протоколе также происходит назначение сегментам порядковых номеров, так что если сегменты принимаются в неправильной последовательности, их можно будет переупорядочить на принимающей стороне. Благодаря этому можно игнорировать дублирующие сегменты (передаваемые повторно из-за задержек). В протоколе TCP осуществляется управление потоком данных: принимающая сторона может сообщить отправителю, сколько байтов данных будет принято, так что медленно работающий приемник не будет выведен из строя слишком большим количеством данных.

Соединения TCP являются полнодуплексными - приложение может одновременно отправлять и получать данные.

Простые сервера

Теперь для того, чтобы разобраться с сокетами, используемыми в интернете, давайте создадим простой сервер (server.c). Первоначально наш код будет создан для версии протокола IPv4, но в последующих статьях мы рассмотрим версию протокола IPv6, а затем перейдем к коду, не зависящему от версии протокола.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main()
{
        int sfd, cfd;
        int ch='k';
        struct sockaddr_in saddr, caddr;
 
        sfd= socket(AF_INET, SOCK_STREAM, 0);
        saddr.sin_family=AF_INET;           /* Set Address Family to Internet */
        saddr.sin_addr.s_addr=htonl(INADDR_ANY);    /* Any Internet address */
        saddr.sin_port=htons(29008);            /* Set server port to 29008 */
                            /* select any arbitrary Port >1024 */
        bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr));
        listen(sfd, 1);
        while(1) {
                printf("Server waitingn");
                cfd=accept(sfd, (struct sockaddr *)NULL, NULL);
                if(read(cfd, &ch, 1)<0) perror("read");
                ch++;
                if(write(cfd, &ch, 1)<0) perror("write");
                close(cfd);
        }
}

О коде

Первое, на что нужно взглянуть, это структура сstruct sockaddr_in. Эта структура используется для хранения интернет адреса (IP) в поле sin_addr, которое является структурой типа struct in_addr и в которой может храниться 32-битное беззнаковое целочисленное значение. Номер порта хранится в полеsin_port, это беззнаковое 16-битное целое (посольку номер порта должен быть меньше 65536).

Далее, давайте посмотрим на вызов функции socket()  includes требуется указать sys/types.h и sys/socket.h):

int socket(int domain, int type, int protocol);

Если обращение к socket() будет успешным, то будет возвращен дескриптор, который будет использован на завершающей стадии соединения. В первом аргументе, domain, определяется домен соединения - семейство протоколов, которые будут использоваться при соединении. Согласно sys/sockets.h, это следующие протоколы:

Имя Назначение
AF_UNIX, AF_LOCAL Локальное соединение
AF_INET Интернет протокол IPv4
AF_INET6 Интернет протокол IPv6
AF_IPX Протоколы IPX - Novell
AF_NETLINK Пользовательский интерфейс с ядром
AF_X25 Протокол ITU-T X.25 / ISO-820
AF_AX25 Радиолюбительский протокол AX.25
AF_ATMPVC Доступ к данным пластиковой карточки (ATM PVC)
AF_APPLETALK Протокол AppleTalk
AF_PACKET Низкоуровневый пакетный интерфейс

AF является сокращением от Address Family - семейство адресов. Здесь мы используем AF_INET - интернет-протокол IPv4. В следующем аргументе, type, указывается тип соединения; может использоваться один из следующих вариантов:

SOCK_STREAM Последовательный, надежный, двусторонний, с использованием потока байтов (TCP, SCTP и т.д.)
SOCK_DGRAM Датаграммы - без соединений, ненадежный (UDP)
SOCK_SEQPACKET Последовательный, надежный, двусторонний, с использованием при передаче датаграмм с фиксированной максимальной длиной (SCTP)
SOCK_RAW Непосредственный доступ к сетевому протоколу (протоколы транспортного уровня не требуются)

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

Далее давайте поместим адрес в поле sin_addr, так как это было показано выше. Когда происходит обращение к функции socket(), то создается сокет, но ему адрес не назначается. Поэтому нам нужна функция bind():

Эта функция используется для того, чтобы связать дескриптор сокета sockfd с адресом addr; а в addrlen указывается длина адреса. Эта операция называется назначением имени сокету. Затем будем слушать сокет с помощью listen():

int listen(int sockfd, int backlog);

С помощью вызова listen() соответствующий сокет помечается демоном sockfd как пассивный сокет - т. е. такой, который будет использоваться для приема входящих подключений. В качестве типа сокета должен быть SOCK_STREAM или SOCK_SEQPACKET, т.е. должно обеспечиваться надежное соединение. В аргументеbacklog определяется максимальная длина очереди ожидающих соединений с sockfd. Если очередь превысит указанное значение, то клиентской программе будет отказано в соединении.

Далее, давайте войдем в бесконечный цикл, который используется для обслуживания запросов клиентов. Здесь мы должны создать еще один дескриптор сокета для клиента, вызвав для этого функцию accept. Теперь, все, что будет записано в этот дескриптор, передается клиенту, а все, что читается из этого дескриптора, является данными, которые клиент отправляет на сервер:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Системный вызов accept() используется с типами сокетов SOCK_STREAM и SOCK_SEQPACKET. Он извлекает первый запрос на соединение из очереди запросов, ожидающих соединений с демоном sockfd, слушающим сокеты, создает сокет нового соединения и возвращает новый дескриптор, относящийся к этому сокету - в нашей программе это cfd.

Новый сокет не находится в состоянии прослушивания. Исходный сокет sockfd не оказывает влияние на этот вызов. В аргументе addr находится адрес удаленного компьютера, с которым мы связываемся, но т. к. заранее мы не знаем адрес клиента, здесь это значение равно NULL.

Затем давайте с помощью операции read() прочитаем из дескриптора символ (отправленный клиентом на сервер), увеличим его значение на единицу и с помощью команды write() запишем в дескриптор его новое значение, которое будет отправлено клиенту. Затем закроем дескриптор с помощью вызоваclose().

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

Клиентская программа

И теперь клиентская программа (client.c). Этот клиент посылает символ на сервер, работающий на порту 29008 (или любом другом произвольном порту):

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char* argv[])
{
    int cfd;
    struct sockaddr_in addr;
    char ch='r';
    cfd=socket(AF_INET, SOCK_STREAM, 0);
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=inet_addr("127.0.0.1"); /* Check for server on loopback */
    addr.sin_port=htons(29008);
    if(connect(cfd, (struct sockaddr *)&addr,
    sizeof(addr))<0) {
        perror("connect error");
        return -1;
    }
    if(write(cfd, &ch, 1)<0) perror("write");
    if(read(cfd, &ch, 1)<0) perror("read");
    printf("nReply from Server: %cnn",ch);
    close(cfd);
    return 0;
}

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

Далее, вместо прослушивания вызывается системный вызов connect(), с помощью которого выполняется подключение sockfd по адресу, указанному в addr. Возвращаемый дескриптор будет использоваться для связи с указанным адресом.

Затем в программе мы для того, чтобы отправить символ на сервер и получить символ, используем команды write() и read(), а затем - закрываем дескриптор.

Запуск программ

Компиляция программы осуществляется следующим образом:

cc server.c -o server
cc client.c -o client

Затем, запускаем программы:

./server &
./client

Чтобы было проще следить за работой каждой из программ, запускайте их в разных терминалах. На рис.1 показаны данные, выдаваемые на терминал сервером, а на рис.2 - клиентской программой.

Рис.1: Работающий сервер

Рис.2: Данные, выдаваемые клиентской программой

Хорошее начало, не прада ли? В следующей статье мы рассмотрим, как переписать обе эти программы для протокола IPv6 и будем двигаться дальше к UDP. И да, FOSS - это круто!

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 20.07.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
SAP CRYSTAL Reports 2013 WIN INTL NUL
ABViewer Standart пользовательская
IBM Domino Messaging Client Access License Authorized User License + SW Subscription & Support 12 Months
Business Studio 4.2 Enterprise. Конкурентная лицензия + Business Studio Portal 4.2. Пользовательская именная лицензия.
TeeBI for RAD Studio Suite with source code single license
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Реестр Windows. Секреты работы на компьютере
Один день системного администратора
Работа в Windows и новости компании Microsoft
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100