Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ОСиС_2008

.pdf
Скачиваний:
96
Добавлен:
29.05.2015
Размер:
2.65 Mб
Скачать

9. Лабораторный курс

375

#include <ctype.h> #include <sys/socket.h> #include <netinet/in.h>

char *msg = “С новым годом!\n”; #define LEN sizeof(struct sockaddr_in) main()

{

. . . . . . . . . . . .

/* Задание адреса сокета сервера */

struct sockaddr_in server = (AF_INET, 5000); server.sin_addr.s_addr = inet_addr(“225.80.13.7”);

/* Отправление запроса на обслуживание к серверу*/ msglen = strlen(msg);

if (sendto(sock, msg, msglen, 0, (struct sockaddr *)&server, LEN) != msglen)

{ printf(“Клиент: ошибка передачи\n”); exit(1);}

. . . . . . . . . . . .

}

Вданном примере используется функция inet_addr, выполняющая преобразование IP-адреса сервера из «человеческой» формы (225.80.13.7) в программную форму, требуемую для передачи ядру ОС.

Вследующем примере сервер принимает датаграмму от клиента и возвращает «эхо» принятого сообщения обратно клиенту:

#include <sys/types.h> #include <ctype.h> #include <sys/socket.h> #include <netinet/in.h> #define LBUF 100

#define LEN sizeof(struct sockaddr_un) char bufer[LBUF];

main ()

{

/* Объявления локальных переменных и структур данных*/ struct sockaddr_in server, client;

int n, sock, s_len; int c_len = LEN;

. . . . . . . . . . . .

/* Бесконечный цикл приема и обработки датаграмм клиентов */

for ( ; ; ){

n= recvfrom(sock, bufer, LBUF, 0,(struct sockaddr *)&client,

376

Одиноков В.В., Коцубинский В.П.

&c_len);

if (n < 0) { printf(“Сервер: ошибка приема\n”); continue;} /* В результате системного вызова recvfrom был получен

адрес клиента. На него и возвращается “эхо” сообщения */ if (sendto(sock, bufer, n, 0, (struct sockaddr *)&client, c_len) !=n) { printf(“Сервер: ошибка передачи\n”); continue;}

}

}

9.10.5. Задание

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

Функции серверной части:

1) запись датаграммы, поступившей от какого-то клиента, а также текущего времени в качестве строки текстового файла, расположенного на своем хосте;

2) отправка клиенту ответной датаграммы, имеющей длину не более 100 байтов, которая содержит имя и фамилию владельца сервера, а также, возможно, другую информацию по желанию владельца.

Функции клиентской части:

1)отправка одному из «известных» серверов (список серверов на доске) датаграммы-запроса, имеющей длину не более 40 байтов, содержащей имя и фамилию владельца клиентской части;

2)прием ответной датаграммы от сервера и вывод ее содержимого на экран.

Порядок выполнения работы:

1)выяснение у преподавателя IP-адреса своего хоста;

2)написание программ клиентской и серверной частей;

3)совместная отладка своих клиентской и серверной частей

влокальном режиме. В процессе данной отладки клиентская и серверная части располагаются на одном и том же хосте, хотя и используют сетевой UDP-канал. При этом UDP-модуль (входит в состав ОС), обнаружив в качестве адреса назначения свой собственный

9. Лабораторный курс

377

IP-адрес, никакой отправки датаграммы по сети

не выпол-

няет, а направляет ее в указанный локальный сокет;

 

4)отлаженный в локальном режиме сервер запускается в бесконечном цикле до конца занятия в фоновом режиме. При этом владелец сервера записывает на доске адрес сокета сервера в виде пары чисел: IP-адрес, номер порта;

5)клиентская часть запускается несколько раз в оперативном режиме с целью получения обслуживания от соответствующего числа «известных» серверов.

В процессе защиты лабораторной работы студент должен, в частности, продемонстрировать обслуживание своей клиентской части со стороны чужого сервера, а также вывести на экран (например, с помощью команды shell cat) результирующий файл своей серверной части.

9.11. Лабораторная работа № 11. Локальные виртуальные соединения

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

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

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

9.11.1. Порядок выдачи системных вызовов

В качестве простейшего примера создания и использования локального виртуального соединения опять рассмотрим получение двумя процессами «эха» символьной строки. В этом примере процесс-клиент посылает другому процессу-серверу любую символьную строку. Далее сервер посылает эту строку обратно клиенту. Получив символьную строку, процесс-клиент выводит ее на экран, сопроводив этот вывод пояснительной надписью. На рис. 70 приведен возможный порядок выдачи системных вызовов во времени.

При выполнении сервером системных вызовов socket (создать сокет) и bind (связать сокет с адресом), а также при выполнении клиентом вызова socket не существует отличий от применения этих вызовов при работе с локальным датаграммным каналом, за исключением того, что в вызове socket в качестве параметра type

378

Одиноков В.В., Коцубинский В.П.

(определяет устойчивость создаваемого канала) задается значение SOCK_STREAM — виртуальное соединение.

СЕРВЕР

socket

(СОЗДАТЬ СОКЕТ)

bind

(СВЯЗАТЬ СОКЕТ_АДРЕС)

listen

(СОЗДАТЬ ОЧЕРЕДЬ)

accept

 

 

 

(ПОЛУЧИТЬ

 

 

 

 

 

 

ЗАПРОС)

 

 

Запрос на

Ожидание

 

запроса

соединение

 

 

read

 

 

Подтверждение

 

 

соединения

(ЧТЕНИЕ

 

 

 

 

 

Ожидание

 

СОКЕТА)

 

данных

 

Данные (строка)

write

Данные («эхо строки»)

(ЗАПИСЬ

 

СОКЕТ)

 

close

 

(ЗАКРЫТЬ

 

СОКЕТ)

 

КЛИЕНТ

socket

(СОЗДАТЬ СОКЕТ)

connect

(СОЕДИНИТЬ СОКЕТЫ)

Ожидание

соединения

write

(ЗАПИСЬ СОКЕТ)

read

(ЧТЕНИЕ СОКЕТА)

Ожидание

данных

close

(ЗАКРЫТЬ СОКЕТ)

Рис. 70. Пример создания и использования виртуального соединения

9.11.2. Создание очереди запросов на соединение и работа с ней

9. Лабораторный курс

379

В результате выполнения серверной частью приложения вызовов socket и bind создается сокет, имеющий адрес (имя-путь сокета). Для того чтобы данный сокет стал главным сокетом сервера, необходимо снабдить данный сокет очередью запросов на создание виртуальных соединений. Это делает системный вызов listen:

#include <sys/socket.h>

int listen (int sockfd, int gueue_size)

где sockfd — номер главного сокета; gueue_size — максимальное число запросов на соединение, которые могут находиться в очереди (это число не должно быть менее пяти).

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

#include <sys/types.h> #include <sys/socket.h>

int accept (int sockfd, struct sockaddr *address, int *add_len)

где sockfd — номер главного сокета; address — указатель на структуру, в которую системный вызов accept поместит адрес сокета того клиента, который выдал запрос на соединение. Если серверу не нужен адрес сокета клиента (сервер будет использовать для связи виртуальное соединение), то в качестве данного параметра следует записать NULL; add_len — длина адресной структуры, заданной предыдущим параметром. Если эта структура не используется, то записывается NULL.

Если системный вызов accept завершился с ошибкой, то он возвращает значение (–1). Иначе в результате своего выполнения accept возвращает номер нового сокета сервера, который является окончанием нового виртуального соединения со стороны сервера. Этот номер сокета используется далее серверной частью приложения подобно номеру открытого файла для выполнения операций чтения и записи.

Что касается клиента, то для того чтобы его запрос на создание виртуального соединения поступил к серверу в очередь главного сокета, клиент должен выдать системный вызов connect (соеди-

нить сокеты):

#include <sys/types.h> #include <sys/socket.h>

380

Одиноков В.В., Коцубинский В.П.

int connect (int csockfd, const struct sockaddr *address, int *add_len)

где csockfd — номер сокета клиента, который предполагается использовать в качестве клиентского окончания создаваемого виртуального соединения; address — указатель на структуру, которая содержит адрес главного сокета сервера; add_len — длина адресной структуры, заданной предыдущим параметром.

Если вызов connect завершился с ошибкой, то он возвращает значение (–1).

9.11.3. Работа с виртуальным соединением

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

ssize_t read(int sockfd, void *buffer, size_t n);

где sockfd — номер сокета, являющегося окончанием виртуального соединения; buffer — указатель на буфер в ОП, в который считываются байты из виртуального соединения; n — длина буфера.

В случае ошибки read возвращает (–1). Если выполнение read завершилось успешно, то он возвращает число байтов, считанных из виртуального соединения. Это число равно n или меньше его. Второе выполняется тогда, когда заканчиваются байты, записанные

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

вподобном случае выдачу вызова read, находится исключительно

введении прикладного протокола. При этом конец передаваемых данных может быть определен по специальному байту (байтам), предусмотренному в прикладном протоколе.

Для записи в виртуальное соединение может использоваться системный вызов write:

ssize_t write(int sockfd, void *buffer, size_t n);

где sockfd — номер сокета, являющегося окончанием виртуального соединения; buffer — указатель на буфер в ОП, из которого

9. Лабораторный курс

381

записываются байты в виртуальное соединение; n — число байтов, записываемых в виртуальное соединение.

Если выполнение write завершилось успешно, то он возвращает число байтов, записанных в виртуальное соединение. Почти всегда это число равно n. В случае ошибки write возвращает (–1).

9.11.4. Закрытие виртуального соединения

Созданное виртуальное соединение существует до тех пор, пока один из партнеров (клиент или сервер) не уничтожит его, используя тот же самый системный вызов close, который используется для закрытия файлов:

int close(int sockfd);

где sockfd — номер сокета, являющегося окончанием виртуального соединения.

Интересно отметить, что если закрываемый сокет содержит данные, предназначенные для передачи по виртуальному соединению, то операция закрытия будет заблокирована (то есть выдавший ее процесс переводится в состояние «Сон») до тех пор, пока данные не будут переданы.

Логично предположить, что после того как один из партнеров закроет виртуальное соединение, второй партнер также должен завершить использование этого информационного канала. При этом возможны следующие варианты:

1)второй партнер выполняет «плановое» закрытие виртуального соединения, предусмотренное прикладным протоколом.

Вэтом случае процессу достаточно выдать системный вызов close;

2)второй партнер выполняет чтение данных из виртуального соединения (например, с помощью вызова read) в такой момент времени, когда это соединение уже не существует. В этом случае read возвратит 0 в качестве числа считанных байтов. Поэтому для избежания зацикливания в программе процесса результат, возвращаемый вызовом read, всегда следует сравнивать с нулем;

3)второй партнер выполняет запись в виртуальное соединение (например, с помощью вызова write) в такой момент времени, когда это соединение уже не существует. В этом случае процесс получит сигнал SIGPIPE. Стандартный обработчик этого сигнала выполняет завершение процесса. Во избежание такого завершения программа процесса должна содержать свой обработчик сигнала SIGPIPE.

382

Одиноков В.В., Коцубинский В.П.

9.11.5. Программы взаимодействующих процессов

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

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

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

Вернемся к примеру с выводом «эха» и запишем программы многопроцессного сервера и клиента.

Сервер:

/* Подсоединение заголовочных файлов*/ #include <sys/types.h>

#include <ctype.h> #include <sys/socket.h> #include <sys/un.h> #include <signal.h>

/* Объявления констант и глобальных переменных*/ #define LBUF 100

#define LEN sizeof(struct sockaddr_un) char bufer[LBUF];

int nsock;

/* Функция – обработчик сигнала SIGPIPE в сервере */ void s_action (int sig)

{

close(nsock);

printf(“Сервер: вирт. соединение оказалось оборвано при

9. Лабораторный курс

383

записи в него\n”); exit(0);

}

main ()

{

/* Объявления локальных переменных и структур данных*/ struct sockaddr_un server;

int n, sock, s_len;

static struct sigaction sact;

/* Изменение диспозиции сигнала SIGPIPE во всем сервере */ sact.sa_handler = s_action;

sigfillset(&(sact.sa_mask)); sigaction(SIGPIPE, &sact, NULL);

/* Создание главного сокета сервера*/

if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)

{

printf(“Сервер: невозможно создать сокет\n”); exit(1);

}

/* Задание адреса главного сокета */ unlink(“/home/vlad/abc.server”); bzero(&server, LEN); server.sun_family = AF_UNIX;

strcpy(server.sun_path, “/home/vlad/abc.server”); s_len=sizeof(server.sun_family)+strlen(server.sun_path);

/* Связывание главного сокета сервера с его адресом */ if (bind(sock, (struct sockaddr *) &server, s_len) < 0)

{

printf(“Сервер: ошибка связывания сокета с адресом\n”); exit(1);

}

/* Создание очереди запросов на создание соединений */ if (listen (sock, 5) < 0)

{

printf(“Сервер: ошибка создания очереди запросов\n”); exit(1);

}

/*Бесконечный цикл приема запросов клиентов на создание соединений*/

for ( ; ; ){

/* Выборка из очереди запроса на создание соединения */ if (nsock = accept(sock, NULL, NULL)) < 0)

{

384

Одиноков В.В., Коцубинский В.П.

printf(“Сервер: ошибка выборки запроса из очереди запросов\n”);

continue;

}

/* Создание дочернего процесса для обслуживания соединения */

if (fork() == 0)

{

n = read(nsock, bufer, LBUF);

if (n < 0) { printf(“Сервер: ошибка приема\n”) } if (n = 0) { printf(“Сервер: вирт. соед. оборвано

при чтении\n”) } if (n > 0) {

if (write(nsock, bufer, n) !=n) { printf(“Сервер: ошибка передачи\n”)

}

/* Завершение дочернего процесса */ close(nsock);

exit(0);

}

/* В главном процессе новый сокет не нужен */ close(nsock);

}

}

Клиент:

/* Включение заголовочных файлов*/ #include <sys/types.h>

#include <ctype.h> #include <sys/socket.h> #include <sys/un.h>

/* Объявления констант и глобальных переменных*/ char *msg = “С новым годом!\n”;

#define LBUF 100

#define LEN sizeof(struct sockaddr_un) char bufer[LBUF];

main ()

{

/* Объявления локальных переменных и структур данных*/ struct sockaddr_un server;

int n, sock, s_len, msglen;

/* Создание сокета клиента*/