
ОСиС_2008
.pdf9. Лабораторный курс |
355 |
но символ intr соответствует нажатию клавиши <Delete> или
<Ctrl>&<C>;
quit — приводит к посылке всем процессам оперативной группы сеанса, управляемого данным терминалом, сигнала SIGQUIT. Обычно символ quit соответствует нажатию клавиш
<Ctrl>&<\>;
susp — символ терминального останова. Ввод данного символа пользователем приводит к посылке всем процессам оперативной группы сеанса, управляемого данным терминалом, сигнала SIGTSTP. Стандартная реакция процесса на этот сигнал — переход процесса в состояние «Останов», в которое процесс может попасть из состояний «Задача», «Готов» и «Сон». («Останов» — дополнительное состояние процесса, существующее не во всех версиях UNIX.) Кроме того, оперативная группа процессов переводится в фоновый режим. Обычно символ susp соответствует нажатию клавиш <Ctrl>&<Z>.
С помощью утилиты stty пользователь может заменять клавиши (и соответствующие им коды), используемые для реализации управляющих символов: erase, kill, eof, intr, quit, susp. Пример:
$ stty erase “^f”
Здесь в качестве символа erase задается комбинация клавиш <Ctrl>&<f>. Обратите внимание, что и в других случаях часто вместо нажатия<Ctrl>&<f> можно набрать строку "^f”.
З а м е н и т е с помощью утилиты stty некоторые символы редактирования и выдачи сигналов. Проверьте результат такой замены.
9.8.3. Управляющая структура termios
Утилита stty, изменяющая кодировку управляющих символов, вносит изменения в управляющую структуру (дескриптор) дисциплины линии — termios. Аналогичные изменения можно выполнять и из программы процесса.
Для запоминания текущего состояния termios и для записи ее нового содержимого могут использоваться системные вызовы tcgetattr и tcsetattr:
#include <termios.h>
int tcgetattr(int ttyfd, struct termios *tsaved);
int tcsetattr(int ttyfd, int actions, const struct termios *tnev);
356 |
Одиноков В.В., Коцубинский В.П. |
Первый вызов сохраняет текущее состояние терминала, которому соответствует номер файла ttyfd, в структуре tsaved. Второй вызов установит новое состояние терминала, заданное структурой tnev. При этом второй параметр этого вызова actions уточняет момент изменения атрибутов терминала. В файле <termios.h> определены возможные варианты этого параметра:
1)TCSANOW — немедленное изменение атрибутов терминала;
2)TCSADRAIN — изменение атрибутов сразу же после опустошения очереди вывода;
3)TCSAFLUSH — изменение атрибутов после опустошения очередей вывода и ввода.
Структура termios определена в файле <termios.h> и содержит:
tcflag_t c_iflag; |
/* Режим ввода */ |
|
tcflag_t c_oflag; |
/* Режим вывода */ |
|
tcflag_t c_cflag; |
/* Управляющий режим */ |
|
tcflag_t c_lflag; |
/* Режим дисциплины линии */ |
|
cc_t |
c_cc[NCCS]; |
/* Управляющие символы */ |
c_iflag — поле, которое служит для общего управления вводом с терминала. Каждый бит этого поля представляет собой флаг, задающий какую-то особенность этого ввода.
Три флаговые константы управляют обработкой входного символа cr (возврат каретки):
INLCR — преобразовать символ lf (перевод строки) в cr; IGNCR — игнорировать cr;
ICRNL — преобразовать символ cr в символ lf.
Заметим, что сама UNIX ожидает в качестве последнего символа строки символ lf. Поэтому обычно используется константа
ICRNL.
Следуюшие три константы позволяют «тормозить» символьный обмен с терминалом в том случае, если пользователь (при выводе) или программный процесс (при вводе) не успевает обработать поступающую информацию:
IXON — разрешить старт-стопное управление выводом. То есть для временной приостановки вывода на терминал пользователь должен ввести символ stop (комбинация клавиш <Ctrl>&<S>). Для продолжения вывода вводится символ start — комбинация клавиш <Ctrl>&<Q>;
IXANY — продолжать вывод при нажатии любого символа. Эта константа дополняет предыдущую, позволяя использовать для продолжения вывода вместо символа start любой символ;
9. Лабораторный курс |
357 |
IXOFF — разрешить старт-стопное управление вводом. Если установлен соответствующий флаг, система сама посылает терминалу символ stop в том случае, если заполнен буфер ввода. Когда в этом буфере появится место, терминалу будет передан символ start.
Каждая из перечисленных флаговых констант имеет такую же длину, как и поле c_iflag. Причем только один бит (флаг) установлен в 1. Все остальные биты флаговой константы сброшены в 0. Поэтому для установки какого-либо флага в поле c_iflag достаточно выполнить побитовое логическое сложение этого поля с соответствующей флаговой константой. Например, пусть tdes — структура типа termios. Тогда для игнорирования символа cr достаточно записать оператор:
tdes.c_iflag |= IGNCR
Для сброса требуемого флага в поле c_iflag достаточно выполнить побитовое логическое умножение этого поля с инверсией соответствующей флаговой константой. Например, для отмены игнорирования символа cr достаточно записать оператор:
tdes.c_iflag &= ~IGNCR
c_oflag — поле, которое служит для общего управления выводом на терминал. Каждый бит этого поля представляет собой флаг, задающий какую-то особенность этого вывода. Некоторые из флаговых констант:
OPOST — содержит наиболее важный флаг в этом поле. Если он сброшен, то выводимые символы передаются без изменений. Иначе символы подвергаются преобразованию, заданному остальными флагами этого поля;
ONLCR — преобразовать символ перевода строки lf в символ возврата каретки cr и символ lf;
OCRNL – преобразовать символ cr в символ lf.
c_cflag — поле, содержащее параметры, управляющие портом терминала: скорость ввода-вывода, контроль четности и т.д. Обычно эти параметры задаются самой UNIX.
c_lflag — поле, задающее режим дисциплины линии. Оно содержит флаги, задаваемые следующими константами:
ICANON — канонический построчный ввод. Если флаг сброшен, то неканонический ввод;
ISIG —– разрешить обработку символов прерываний (intr и quit). Если флаг сброшен, то при получении этих символов сигналы
358 |
Одиноков В.В., Коцубинский В.П. |
процессам не посылаются, а сами символы intr и quit передаются в программу без изменений;
IEXTEN — разрешить дополнительную, зависящую от реализации обработку вводимых символов;
ECHO — разрешить отображение вводимых символов на экране;
ECHOE — отображать символ удаления erase как «erase– пробел–erase». В результате курсор сначала переместится на одну позицию влево, во-вторых, стоящий в этой позиции символ будет затерт пробелом (при этом курсор вернется на соседнюю позицию вправо), а в-третьих, курсор опять переместится на соседнюю позицию слева, указывая таким образом на первую свободную позицию;
TOSTOP — посылать сигнал SIGTTOU при попытке вывода фонового процесса.
c_cc — массив, содержащий специальные символы редактирования и выдачи сигналов, рассмотренные ранее. Каждый символ занимает в массиве c_cc позицию, задаваемую соответствующей константой из файла <termios.h>:
VERASE — символ стирания erase; VKILL — символ удаления строки kill;
VSTOP — символ остановки передачи данных stop; VSTART — символ продолжения передачи данных start; VEOF — символ конца файла eof ;
VINTR — символ прерывания intr; VQUIT — символ завершения quit;
VSUSP — символ временной приостановки выполнения susp.
Пример
Следующий фрагмент программы изменяет значение символа quit для терминала, выполняющего стандартный ввод (номер фай-
ла — 0):
struct termios tdes;
/* Получение настроек терминала */
tcgetattr(0, &tdes); |
|
tdes.c_cc[VQUIT] = “^y”; |
/* CTRL–Y */ |
/* Изменение настроек терминала */ tcsetattr(0, TCSAFLUSH, &tdes);
9.8.4. Задание
9. Лабораторный курс |
359 |
Требуется разработать программу, которая выполняет следующие действия:
1)задает старт-стопное управление выводом на экран. Причем для продолжения вывода может использоваться любой символ;
2)задает новые значения (по вашему выбору) символов стирания erase, удаления строки kill, прерывания intr, временной приостановки выполнения susp.
Проверить произведенные установки: а) с помощью утилиты cat выполнить вывод на экран достаточно большого текстового файла; б) выполнить редактирование строки символов, используя символы erase и kill; в) из командной строки запустить один оперативный и один фоновый процесс, выполняющие бесконечные циклы, а затем проверить на них действие сигналов intr и susp.
Примечание. После выполнения перечисленных действий требуется восстановить прежнее состояние терминала, так как иначе другие процессы «не поймут» новые настройки терминала.
9.9.Лабораторная работа № 9. Датаграммные локальные каналы
Целью выполнения настоящей лабораторной работы является получение навыков создания и использования локальных датаграммных информационных каналов между процессами при использовании метода сокетов.
9.9.1. Подготовка к выполнению работы
В начале выполнения данной работы следует ознакомиться со следующими вопросами из теоретической части пособия:
1)понятие информационного канала и его характеристики
(п. 2.5.1);
2)сокеты (п. 4.6.3) — понятие сокета; имена сокетов; системные вызовы для реализации датаграммного канала;
3)понятие протокола (подразд. 3.4).
9.9.2. Порядок выдачи системных вызовов
В качестве простейшего примера создания и использования локального датаграммного канала рассмотрим получение двумя процессами «эха» символьной строки. В этом примере процесс-
360 |
Одиноков В.В., Коцубинский В.П. |
клиент посылает другому процессу-серверу любую символьную строку. Далее сервер посылает эту строку обратно клиенту. Получив символьную строку, процесс-клиент выводит ее на экран, сопроводив этот вывод пояснительной надписью. На рис. 69 приведен возможный порядок выдачи системных вызовов во времени.

|
9. Лабораторный курс |
|
361 |
||
|
СЕРВЕР |
|
|
||
|
socket |
|
КЛИЕНТ |
||
|
|
|
|||
|
(СОЗДАТЬ |
socket |
|
||
|
СОКЕТ) |
|
|||
|
(СОЗДАТЬ |
|
|||
|
|
|
|
СОКЕТ) |
|
|
bind |
|
|
||
|
(СВЯЗАТЬ |
bind |
|
||
СОКЕТ_АДРЕС) |
(СВЯЗАТЬ |
|
|||
|
|
|
|
СОКЕТ_АДРЕС) |
|
|
recvfrom |
sendto |
|
||
|
(ПОЛУЧИТЬ |
|
|||
|
ДАТАГРАММУ) |
|
|
(ПОСЛАТЬ |
|
|
|
||||
|
|
|
Ожидание |
Данные ДАТАГРАММУ) |
|
|
|
||||
|
датаграммы |
(строка) |
|
||
|
|
|
|
recvfrom |
|
|
|
|
|
(ПОЛУЧИТЬ |
|
|
|
|
|
ДАТАГРАММУ) |
|
|
sendto |
|
Ожидание |
||
|
(ПОСЛАТЬ |
Данные |
датаграммы |
||
|
ДАТАГРАММУ) |
(«эхо» строки) |
|
||
) |
|
|
|
|
close
(ЗАКРЫТЬ СОКЕТ)
Рис. 69. Пример создания и использования датаграммного канала
9.9.3. Создание сокета
Создание сокета выполняет процесс-«владелец», используя системный вызов socket. Его определение:
#include <sys/types.h> #include <sys/socket.h>
int socket(int domain, int type, int protocol);
Все три параметра этого системного вызова определяют характеристики будущего информационного канала, при образовании которого будет использоваться создаваемый сокет:
362 |
Одиноков В.В., Коцубинский В.П. |
domain — определяет область применения создаваемого канала: AF_UNIX — процессы, соединяемые каналом, выполняются на одной ЭВМ; AF_INET — соединяемые процессы выполняются на удаленных ЭВМ, подключенных к сети Internet;
type — определяет устойчивость создаваемого канала: SOCK_ STREAM — виртуальное соединение; SOCK_DGRAM — передача датаграмм;
protocol — тип протокола, которому должны следовать подпрограммы ядра, обслуживающие сокеты, расположенные по обоим концам информационного канала. Обычно в качестве данного параметра задают 0, что эквивалентно выбору протокола самим ядром.
Если системный вызов socket завершился с ошибкой, то он возвращает значение (–1). Иначе в результате своего выполнения socket возвращает номер нового сокета, который может использоваться далее процессом-«владельцем» во всех операциях с данным сокетом (и с соответствующим информационным каналом).
Пример создания сокета:
int |
sock; |
|
if |
((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) |
|
{ |
printf(“Невозможно создать сокет\n”); exit(1); |
|
} |
||
|
9.9.4. Связывание сокета со своим адресом
Только что созданный сокет еще не имеет адреса (внешнего имени). Для получения такого адреса необходимо выполнить системный вызов bind:
#include <sys/types.h> #include <sys/socket.h>
int bind(int sockfd, struct sockaddr *address, int add_len);
где sockfd — номер сокета, созданного с помощью вызова socket; address — адрес (указатель) структуры типа sockaddr; add_len — длина структуры типа sockaddr.
В случае успешного завершения вызова bind он возвращает значение 0, иначе (–1).
Структура sockaddr — структура обобщенного адреса сокета, которая определяется в файле <sys/socket.h>:
9. Лабораторный курс |
363 |
|
struct |
sockaddr { |
|
sa_family_t sa_family; |
/* Область применения канала */ |
|
char |
sa_data[ ]; |
/* Внешнее имя сокета */ |
}; |
|
|
При выполнении программой процесса конкретного вызова bind структура sockaddr заменяется или структурой sockaddr_un, используемой при внутримашинном обмене, или структурой sockaddr_in, используемой при взаимодействии удаленных процессов. Структура sockaddr_un определяется в файле <sys/un.h>:
struct |
sockaddr_un { |
|
sa_family_t sun_family; |
/* ==AF_UNIX */ |
|
char |
sun_path[108]; |
/* Имя-путь файла-сокета */ |
}; |
|
|
Применение вызова bind в программе зависит от роли, которую играет данная программа среди взаимодействующих программ. Адрес сокета, принадлежащего программе-серверу, всегда известен заранее, для того чтобы программы-клиенты могли посылать свои запросы по этому адресу. Пример выполнения вызова bind в сервере:
#define LEN sizeof(struct sockaddr_un) struct sockaddr_un server;
int sock, s_len;
. . . . . . . . . . . . /* Создание сокета с номером sock*/
/* Задание адреса своего сокета */ 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);
}
В приведенном фрагменте программы производится связывание адреса (имени-пути /home/vlad/abc.server) сокета с номером этого сокета sock. Возможно, ранее это имя-путь уже использовалось для создания файла или сокета. Поэтому в начале данного фрагмента производится уничтожение файла (сокета), созданного
364 |
Одиноков В.В., Коцубинский В.П. |
прежде. Далее структура server адреса сокета сначала обнуляется, а затем заполняется. После этого определяется длина этой структуры s_len и выполняется вызов bind.
В отличие от сервера, имя-путь сокета клиента может быть заранее не известно и может быть выбрано клиентом в известной степени произвольно. Единственное требование — в пределах системы имя-путь сокета должно быть уникальным. Для получения уникального внешнего имени сокета удобно использовать библиотечную функцию mktemp, которая, получая на входе шаблон имени-пути файла, получает уникальное имя-путь на основе номера текущего процесса. Например, для шаблона /tmp/clnt.XXX производится замена трех символов «X». Соответствующий фрагмент программы-клиента:
#define LEN sizeof(struct sockaddr_un) struct sockaddr_un client;
int sock, c_len;
. . . . . . . . . . . .
/* Создание сокета с номером sock*/ /* Задание адреса своего сокета */ bzero(&client, LEN);
client.sun_family = AF_UNIX; strcpy(client.sun_path, “/tmp/clnt.XXX”); mktemp(client.sun_path);
c_len= sizeof(client.sun_family) + strlen(client.sun_path); /* Связывание сокета клиента со своим адресом */ if (bind(sock, (struct sockaddr *) &client, c_len) < 0)
{
printf(“Ошибка связывания сокета с адресом\n”); exit(1);
}
9.9.5. Прием и передача датаграмм
Выполнение вызовов socket и bind каждым из двух взаимодействующих процессов приведет к созданию двух сокетов, каждый из которых имеет как локальное, так и внешнее имя (адрес). Этого достаточно для создания датаграммного информационного канала. Пользуясь этим каналом, процесс может отправлять свои датаграммы другому процессу, а также принимать ответные датаграммы.