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

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

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

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

Начало серии статей о Socket API

Ранее мы создали простой сервер и простую клиентскую программу, в которых использовался интерфейс прикладного программирования Socket API. На этот раз мы сначала начнем с программы, а затем объясним, что было сделано. Итак, запустите свою систему и приготовьтесь углубиться в программировании сокетов.

Как уже было сказано, давайте сразу начнем с кода.

Версия сервера для IPv6

Ниже показана версия сервера, созданного нами в предыдущей статье, для протокола IPv6. Существенных изменений нет, за исключением появления в коде цифры "6". Давайте назовем этот файл serverin6.c:

#include <stdio.h%gt;
#include <unistd.h%gt;
#include <sys/types.h%gt;
#include <sys/socket.h%gt;
#include <netinet/in.h%gt;
int main()
{
    int sfd, cfd;
    char ch;
    socklen_t len;
    struct sockaddr_in6 saddr, caddr;
    sfd= socket(AF_INET6, SOCK_STREAM, 0);
    saddr.sin6_family=AF_INET6;
    saddr.sin6_addr=in6addr_any;
    saddr.sin6_port=htons(1205);
    bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr));
    listen(sfd, 5);
    while(1) {
        printf("Waiting...n");
        len=sizeof(cfd);
        cfd=accept(sfd, (struct sockaddr *)&caddr, &len);
        if(read(cfd, &ch, 1)<0) perror("read");
        ch++;
        if(write(cfd, &ch, 1)<0) perror("write");
        close(cfd);
    }
}

Теперь давайте рассмотрим различия. Первое находится в строке 6 (sockaddr_in6) и оно понятно; для адресов IPv6 нам нужно в этой адресной структуре хранить адрес, порт и тип адресов так, как мы это делали в строках 13, 14 и 15. В строке 14 находится универсальный символ in6addr_any, используемый для всех адресов IPv6, такой же, как и INADDR_ANY для IPv4. Есть также изменения и в accept(), с которыми можно легко разобраться. И вот наш сервер работает с версией протокола IPv6. Вы можете с правами пользователя root настроить протокол IPv6 с помощью следующей команды:

ip -f inet6 addr add face:1f::ea54:a dev eth0

Здесь -f указывает семейство протоколов (inet6), а addr предназначен для хранения адреса - мы добавили face:1f::ea54:a (когда мы записываем адреса IPv6, лидирующие нули можно опустить, указанный выше адрес, в действительности, является адресом face:001f::ea54:000a). Вы можете задать любой адрес, и чтобы он ни с чем не совпадал, вы можете использовать свой MAC-адрес. В параметре dev указывается устройство, для которого мы устанавливаем адрес, в данном случае - eth0. Вы можете проверить результаты с помощью команды ifconfig.

Скомпилируйте и запустите сервер следующим образом:

cc serverin6.c -o serverin6
./serverin6

Прежде, чем писать клиентскую программу, вы можете увидеть, что сервер работает даже с нашим клиентом для версии IPv4, а адрес IPv4, также указывает на ту же самую машину. Мы можем для того, чтобы проверить, работает ли сервер так, как ожидалось, подключаться к каждому из серверов с помощью telnet, например:

telnet localhost 1205

Введите символ, который вы хотите отправить на сервер, и нажмите клавишу Enter, и сервер ответит следующим символом ASCII, а затем закроет соединение. Помните, что для версии IPv4 localhost будет равен 127.0.0.1, а для IPv6 - будет ::1.

Клиентская программа (версия для IPv6)

Давайте теперь напишем версию клиентской программы для IPv6 и назовем ее clientin6.c:

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_in6 addr;
    char ch;
    if(argc!=3) {
        printf("Usage: %s in6addr charactern", argv[0]);
        return -1;
    }
    if( ! inet_pton(AF_INET6, argv[1], &(addr.sin6_addr))) { /* returns 0 on error */
        printf("Invalid Addressn");
        return -1;
    }
    ch=argv[2][0];      /* Set the character to second argument */
    cfd=socket(AF_INET6, SOCK_STREAM, 0);
    addr.sin6_family=AF_INET6;
    addr.sin6_port=htons(1205);
    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("Server sent: %cn", ch);
    close(cfd);
    return 0;
}

Здесь изменения снова незначительны и понятны - просто добавляется символ "6". Клиентская программа получает в качестве аргументов адрес IP и символы, так что для того, чтобы запустить эту программу, просто введите следующую команду:

cc clientin6.c -o clientin6
./clientin6  ::1  d

Когда посылается символ d, сервер отвечает символом e. Результат будет такой же, как и в последних примерах, показанных на рисунках в предыдущей статье. Чтобы обработать адрес, мы должны использовать функцию inet_pton(), которую мы рассмотрим более подробно в следующем разделе.

Погружаемся глубже

Во-первых, давайте взглянем на функцию, которую мы использовали в нашей клиентской программе для преобразования адреса из строки в число. В двоичном виде адрес 192.168.1.23 будет равен 11000000 10101000 00000001 00010111 (32-битная строка из единиц и нулей). Для версии IPv6 это будет 128-битная строка (64-битный адрес). Адреса 192.168.1.23 или face:1f::ea54:a, которые более удобны для восприятия человеком, являются "презентационной" формой (функцияp), а n является числовой (двоичной) формой. Прототип функции представлен в <arpa/inet.h> в виде:

int inet_pton (int family, const char *strptr, void *addrptr);

С помощью этой функции можно в машине в случае необходимости конвертировать как адреса IPv4, так и адреса IPv 6, из строки в двоичную форму. Первым аргументом, конечно, является семейство протоколов: AF_INET или AF_INET6. Аргумент *strptr является адресом в строковом виде, а *addrptr будет тем местом, где будет сохранен адрес в числовом формате; это должна быть структура (sin_addr - для IPv4, и sin6_addr - для IPv6). Функция в случае успеха возвращает 1 и 0 - в случае ошибки. Следующей функцией будет:

const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);

Эта функция делает ровно обратное по сравнению с предыдущей функцией; она преобразует числовое представление (addrptr) и сохраняет его в презентационной форме (strptr). Последним аргументом является len, размер/длина целевой переменной, которая используется для того, чтобы избежать переполнения. Для удобства в netinet/in.h определены константы, задающие размер, а именно:

#define INET_ADDRSTRLEN     16
#define INET6_ADDRSTRLEN    46

Есть и другие функции, используемых для той же самой цели - inet_aton() и inet_ntoa(), но только для версии IPv4; поэтому мы их не используем.

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

Например, если у нас есть 2-байтовое целое, хранящее значение 0x1234 по адресу 0×0200, оно будет занимать два байта: 0x0200 и 0x0201. Если у нас в 0x0200находится значение 0x12, а в 0x0201 - значение 0x34, то машина с прямым порядком хранения байтов. Если значения хранятся в обратном порядке, то машина с обратным порядком хранения байтов. В книге "Сетевое программирование для Unix" Ричарда Стивенса (Unix Network Programming, W Richard Stevens) приводится программа, которая определяет, какая у вас машина. Давайте теперь взглянем на функции, которые определены в netinet/in.h:

uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);

Эти две функции возвращают 16-битное и 32-битноее значения соответственно; htons() является сокращением от host to network short и htonl()- host to network long. Они преобразуют байты из порядка, используемого на хосте, в порядок, используемый в сетевом стеке. Функции ntohs() и ntohl() делают обратное (преобразуют порядок байтов, используемый в сети, в порядок байтов, используемый в хосте).

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

char address[INET6_ADDRSTRLEN]; /* at declaration section */
printf("Connected to client %s at port %dn", inet_ntop(AF_INET6, &caddr.sin6_addr,
buff, sizeof(buff)), ntohs(caddr.sin6_port)); /* after the call to accept() */

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

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

Небольшое упражнение

Теперь для того, чтобы сделать что-нибудь более полезное, чем то, что мы делали ранее, поместите в соответствующую часть программы-сервера следующий код:

if(read(cfd, &ch, 1)<0) perror("read");
while( ch != EOF) {
        if((ch>='a' && ch<='z') // (ch>='A' && ch<='Z'))
        ch^=0x20;    /* EXORing 6th bit will result in change in case */
        if(write(cfd, &ch, 1)<0) perror("write");
        if(read(cfd, &ch, 1)<0) perror("read");
}

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

Да, прежде, чем закрывать соединение, давайте взглянем на данные, выданные в сессии telnet. Запустите в вашей командной оболочке telnet ::1 1205 и начните набирать текст. На рисунке 2 приведен пример выдаваемых данных.

Рис.2: Используем telnet

При нажатии клавиш Ctrl+D сервер закроет соединение и будет ждать новое соединение. А для тех лентяев, кому неохота писать свою собственную клиентскую программу, нужно просто поместить в программу следующий код, а не только вызовы функций write() и read(); результат показан на рис.3:

while(1) {
      ch=getchar();
      if(write(cfd, &ch, 1)<0) perror("write");
      if(read(cfd, &ch, 1)<0) perror("read");
      printf("%c", ch);
}

Рис.3: Работа клиентской программы

Скомпилируйте и запустите вашу клиентскую программу.

Чтобы закрыть клиентскую программу, используйте сочетание клавиш Ctrl+D, которая пошлет символ EOF на сервер, а сервер закроет соединение с клиентом. Не нужно закрывать сервер с помощью нажатия клавиш Ctrl-C. Либо вы также можете закрывать соединение из клиентской программы; мы решим эту проблему позже с помощью обработчики сигналов. Теперь, я думаю, что нужно немного отдохнуть. Спокойной ночи и FOSS - это круто!

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


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

Магазин программного обеспечения   WWW.ITSHOP.RU
ZBrush 4R6 Win Commercial Single License ESD
ABBYY Lingvo x6 Многоязычная Профессиональная версия, электронный ключ
ABViewer Professional пользовательская
Stimulsoft Reports.Ultimate Single License Includes one year subscription
Allround Automation PL/SQL Developer - Unlimited license
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Реестр Windows. Секреты работы на компьютере
Один день системного администратора
Программирование на Visual С++
Краткие описания программ и ссылки на них
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100