LINUX.ORG.RU

[СИ] Простой HTTP-сервер.


0

1

[СИ] Простой HTTP-сервер.

Язык СИ
ОС UNIX

Имеется простой самодельный HTTP-сервр с пулом
рабочих процессов (каждому клиенту свой процесс).
Он якобы работает, но есть сомнения.

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

Ниже приведена схема рабочего процесса с
куском доподлинного нового кода.


//--- глобальные ---
int volatile fl_alarm=0;
pid_t  pid1;

void timer(int n)
{
    kill(pid1, 9);  //--- сигнал CGI-скрипту ---
    fl_alarm=1;
    return;
}

    //--- СХЕМА РАБОЧЕГО ПРОЦЕССА ---

start:
    sd=accept();

k_alive:
    k=read(sd);      //--- ждем запрос (с тайм-аутом) ---
    pid1=fork();

    //--- дочерний процесс -> CGI-скрипт ---
    if(pid1==0){
        close(sd);
        execl();
        exit(0);
    }

    //--- родительский процесс ---
    signal(SIGALRM, timer);
    alarm(tm_out);   //--- общий тайм-аут запроса ---
    k=read(sd);      //--- POST-запрос клиента (с тайм-аутом) ---
    k=write(pipe0);  //--- на ввод CGI (блокирующий) ---
    k=read(pipe1);   //--- с вывода CGI (блокирующий) ---
    k=write(sd);     //--- ответ клиенту (с тайм-аутом) ---

    //--- ДОПОДЛИННЫЙ КОД ---
    tm=alarm(0);
    if(fl_alarm>0){
        pid3=wait(&status);  //--- борьба с зомби, блокирующий ---
        fl_alarm = 0;
    }
    else{
        pid3=-1;
        tm1=0;
        while(tm1 < tm){
            pid3=waitpid(pid1, &status, WNOHANG);   //--- неблокирующий ---
            if(pid3>0) break;
            sleep(TM);  //--- TM -период опроса 1 сек. ---
            tm1=tm1 + TM;
        }
        if(pid3<=0){
            kill(pid1, 9);
            pid=wait(&status);   //--- блокирующий ---
        }
    }
    //--- КОНЕЦ ДОПОДЛИННОГО КОДА ---

    if(fl_aliv > 0) goto k_alive;
    close(sd);
    goto start;

1.
kill() в обработчике сомнителен.
Я не вижу иного способа вынести его оттуда,
кроме как сделать чтение/запись в pipe-канал
с тайм-аутом подобно сокету. Иначе можно зависнуть
на read(pipe), если CGI-скрипт, например, зациклится.
Или даже все три (сокет, ввод и вывод скрипта) завести
на центральный select процесса, время вычислять, и
совсем отказаться от сигнала SIGALRM.
Это, наверно, возможно. Но простой поначалу сервер
всё больше утяжеляется.

2.
Глобальный, фигурирующий в обработчике сигнала
pid_t pid1;
Как его и можно ли правильно определить? volatile?
В статье http://www.opennet.ru/base/dev/unix_signals.txt.html
написано
«Правило 3

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

В этой статье, однако, есть противоречия.

3.
Цикл опроса waitpid().
А как по другому?
alarm() уже выключен. Нужно выдержать тайм-аут.
В других местах вызываем wait() в расчете
на то, что SIGKILL сделает свое дело.
Если код заменить на этот (старый):

    //--- ДОПОДЛИННЫЙ КОД ---

    pid3=wait(&status);  //--- борьба с зомби, блокирующий ---
    //--- здесь сработает обработчик и пошлет сигнал другому процессу ---
    tm=alarm(0);
    fl_alarm = 0;

    //--- КОНЕЦ ДОПОДЛИННОГО КОДА ---

то есть опасность прибить ни в чем неповинный процесс.

Получается, что теоретически сервер работать не может.

Как сделать правильно?

Кто знает прошу ответить.


>kill() в обработчике сомнителен.

Я не вижу иного способа вынести его оттуда,

read() должен прерываться сигналом. Главное это правильно обработать.

А, относительно «плодить зомби». Если у вас рабочий процесс завершается, то его потомок (зомби) уйдёт initd, и тот обработает waitpid(). Или делайте, допустим, 10 раз неблокирующийся waitpid() с паузами в 0,1-0,5 с и хватит. Если скрипт после «kill -9» не умер за 1 с, значит он в «uninterruptible state».

mky ★★★★★ ()
Ответ на: комментарий от mky

mky
Спасибо за ответ в теме «[СИ] Прервать процесс».
(http://www.linux.org.ru/forum/development/5833957?lastmod=1296162836128#comme...)

«read() должен прерываться сигналом».
Уберем kill() из обработчика:

while(1){
//--- здесь придет сигнал SIGALRM ---
k=read(pipe1);
if(k<0){обработать}
if(k==0) break;
//--- или здесь придет сигнал SIGALRM ---
n=write(sd);
if(n<0){обработать}
}

зависаем на read().

Рабочий процесс не завершается, крутится в цикле всегда.

oleg_2 ()
Ответ на: комментарий от oleg_2

Во втором случае прихода сигнала нормально, т. к.
n=write(sd);
ограничен тайм-аутом сокета.
Опасно после проверки но до read();

oleg_2 ()
Ответ на: комментарий от oleg_2

Понятно. Могу предложить такой вариант. SIGALARM обрабатываем через sigaction(), ставим таймер, setitimer(ITIMER_REAL, ...) первое значение через заданный таймаут, остальные через 0,1 с (или ещё чаще).

mky ★★★★★ ()
Ответ на: комментарий от mky

Прикинул, что получится, если вместо alarm()
применить неблокирующий pipe-канал с тайм-аутом
через select(), как с сокетами.
И появились трудности.

Первое, может ли быть так, чтобы один конец канала
был не блокирующий, а другой блокирующий? На стороне
CGI-скрипта канал должен быть блокирующий. Ответа в
явном виде нет.

Второе, запись в неблокирующий канал имеет особенности,
о которых в книге «UNIX Взаимодействие процессов» в таблице
4.1 и пояснении к ней (стр. 79). Похоже, select() будет
давать готовность к записи, а write() - EAGAIN. Зациклится,
пока канал не освободится.

Сомнительно. Везде подвохи.
А может вместо pipe UNIX-сокеты?

mky
Я попробую и UNIX-сокеты, хотя бы для ознакомления, т. к.
с ними я еще не работал.

Но Ваше решение предпочтительнее. С setitimer() я тоже еще не
работал. Думаю разберусь. Если не возникнет каких-то
новых трудностей, то так и сделаю. Меньше утяжелит сервер, даже
совсем не утяжелит т. к. kill() по тайм-ауту случай редкий.
И не сложно. Лучшего не придумать. Спасибо.

oleg_2 ()
Ответ на: комментарий от oleg_2

>Похоже, select() будет давать готовность к записи, а write() - EAGAIN. Зациклится, пока канал не освободится

Я такого не помню. Но книги у меня нет. Попробуйте написать тестовую программу с select() на этот самый «неблокирующий канал» и проверить, так ли это. Заодно, получите опыт.

Прикинул, что получится, если вместо alarm() применить неблокирующий pipe-канал с тайм-аутом через select(), как с сокетами.

alarm()/setitimer() убирать не надо, но вот вместо select() можно использовать pselect() с нужной маской, что гарантирует, что SIGALARM будет обработан только при вызове pselect().

И ещё, если ваша программа работает с сигналами, то нужно активно использовать sigsetmask(), тогда у вас будут гарантированно не прерывающиеся участки и там, допустим, можно будет сделать и не блокирующийся waitpid() и установку нужных флагов для обработчика сигнала.

mky ★★★★★ ()
Ответ на: комментарий от mky

Тест показал, что
канал блокирующий на чтение и неблокирующий
на запись возможен.
Запись в неблокирующий канал не происходит
малыми порциями, а только не меньше, чем
гарантированная атомарность. Вернее select()
дает готовность записи, когда можно записать
максимальную атомарную запись.
Повидимому так же работает блокирующий pipe,
только мы этого не видим.
Выходит Стивенс не наврал про pipe.

А про select() ни Стивенс, ни кто другой не пишет.
По здравому смыслу так и должен работать, иначе что
получится?. Пусть канал мог бы принять один байт,
селект дал бы готовность записи. Я туда попытаюсь
записать 10 байт, и получу EAGAIN, т. к. атомарно
не получится.
socketpair() точно так, как pipe.

Выходит это тоже решение, хоть и не лучшее.
А вот setitimer() хорошо. Как специально для этого
и придуман. Особенно то, что можно дать большую
начальную паузу. Тест работает хорошо. Остаётся
приладить к серверу.

oleg_2 ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.