КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Проблема очередности действий и ее решение.У нас имеется 2 дескриптора (как минимум). На одном из дескрипторов может ожидаться запрос на соединение нового клиента (accept). Если выполним accept, а никакого запроса на соединение не было, наша прога зависнет внутри этого вызова accept сколько угодно долго. В это время на любой из клиентских сокетов могут придти данные, требующие обработки, но пока мы внутри accept никакой обработки не будет. С другой стороны если произвести чтение с клиентского сокета есть риск что клиент не примет никаких данных в течение длительного времени. Все это время наша прога зависнет внутри вызова read. Самое простое решение: перевести все сокеты в неблокирующий режим при котором вызовы read и accept всегда возвращают управление немедленно ничего не ожидая. Можно начать их опрашивать по очереди в бесконечном цикле, т.е. режим активного ожидания. В многозадачным системах это не приемлимо, т.к. процесс бесконечно опрашивающий набор сокетов будет вхолостую тратить ресурсы. Другое решение: 1. При написании сервер. проги можно создать отдельный процесс для обслуживания каждого клиента. 2. Если жалко, что созданные процессы много простаивают, можно сделать иначе: это решение на основе обслуживающих процессов. Наш главный процесс будет бОльшую часть времени находится в режиме accept. При принятии запроса от клиента наш процесс породит дочерний процесс для обслуживания клиента. После порождения родит процесс закрывает клиент. сокет, а дочерний процесс закрывает слушающий сокет, т.о. все обязанности по обслуживанию клиента ложатся на дочерний процесс. Все это время родит. процесс продолжает использовать слушающие обязанности вызывая accept. Программа: int ls; struct sockaddr_cr addr; ls = socket (AF_INET, SOCK_STREAM, 0); if (ls==-1) {perror (“Error socket”);} addr.sin_family = AF_INET; addr.sin_port = htons (port); addr.sin_addr.s_addr = INADDR_ANY; // Заданная инструкция в системе принимает соединение по заданному порту при любом IP-адресе. if (bind(ls, &addr, sizeof(addr))==-1) {perror (“error bind”);} // Сформируем 3й параметр для accept – это будет размер адрес. структуры, куда будет записан адрес сокета с которого будет устанавливаться соединение. for ( ; ; ) { sock_len_t slen = sizeof (addr); int cls = accept (ls, %addr, &slen); if (fork()==0) {close(ls); anet(0);} // Дальше работа с клиентом идет через сокет cls. Клиент пойдет с адреса, который хранится в addr. // Дальше действия в родит. процессе: close(cls); // Далее проверим не завершился ли какой-нить дочерний процесс: while (wait4(-1,NULL); //wait4 ждет любого заверш. процесса и убирает «зомби» }
Мультиплексирование ввода/вывода. Событийно-управляемое программирование: Когда порождение процесса по каждому клиент. соединению не приемлимо (например, сервер перегружен или между сеансами обслуживания разных клиентов происходит активное взаимодействие) надо оставить обслуживание всех клиентов в рамках одного процесса, но активное ожидание не приемлемо. Пусть имеем некоторое количество типов событий, каждое из которых требует своей обработки. Многие систем. вызовы, предназначенные для обработки имеют след. свойство: будучи вызванными дл наступления события они это событие ожидают, блокируют вызвавший процесс и делают невозможным обработку других событий. Но возможно, что длительное время не наступит ни одного события =>надо исключить холостой расход процессорного времени, т.е. нужно иметь возможность отдать ОС процессорное время до наступления события. Вызов select() позволяет обрабатывать события 3 типов: 1. Изменение состояния ФД (появление данных на чтение/запись на соединении; освобождение места в буфере; исключительная ситуация). 2. Истечение заданного количества времени с момента входа в вызов. 3. Получение процессом не игнорируемого сигнала. int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); readfds, writefds, exceptfds – множество ФД. n – какое количетсво элементов в этих множествах является значимым. (n = max_d+1 – рекомендуемое значение, где max_d – максимальный номер дескриптора) timeout – параметр задает промежуток времени, спустя который требует вернуть управление процессу, даже если не произошло событие. Тип fd_set можно представить как битовую строку, где каждому дескриптору соотв. 1 бит. Для работы с переменный такого типа UNIX предоставляет макросы: FD_ZERO (fd_set *set); FD_CLR (int fd, fd_set *set); FD_SET (int fd, fd_set *set); FD_ISSET (int fd, fd_set *set); Тип timeval – структура которая имеет 2 поля (оба типа long int). 1ое поле – количество секунд, 2ое поле – количество vrc/ timeout = 5.3 c struct timeval t t.tv_sec = 5; t.tv_usec = 300 000; Вызов select() возвращает управление в следующих случаях: 1. Ошибка, возврат -1. 2. Получен не игнорируемый сигнал =>возврат тоже -1, далее надо сделать проверку errno = EINTR. 3. Истек timeout, возврат 0. 4. Примем данные на какой-либо из дескрипторов, а дескриптор определен в множестве readfds. 5. Какой-либо из дескрипторов writefds готов на записью 6. На каком-либо дескрипторе exceptfds возникло исключение. В случаях 4,5,6 select возвращает количество дескрипторов изменивших статус, при этом все множество изменяется и в них остаются только те дескрипторы, которые изменили свой статус. Способ построения программ при котором прога имеет главный цикл, 1 итерация этого цикла соответствует наступлению события из определенного множества, а все остальные действия проги построены как реакция на событие, вот это событийно-ориентированное программирование.
|