Модераторы: feodorv
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> POLLHUP и SHUT_RD, Определить дисконнект "немого" клиента 
:(
    Опции темы
NightGoblin
Дата 8.9.2008, 23:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1021
Регистрация: 24.11.2002
Где: 127.0.0.1

Репутация: нет
Всего: 11



Даже не знаю с чего начать, но всё же попробую как-то упорядочить многомерную проблему smile

Общая ситуация:

Есть TCP мультиплексор соединений на C. Клиенты сервера посылают запрос и дальше по долговременному соединению получают данные.

Основной вопрос:

Как, игнорируя данные от клиентов, отследить их "корректное" отключение от сервера (TCP FIN)? О "вываливании", когда связь с клиентом просто потеряна (пропадание электричества, перегрызенный провод, упавший раутер, и т. п.) речи не идёт -- тут нужен другой механизм, и с ним я буду разбираться отдельно.

Проблема:

Зависит от того, с какой стороны смотреть. С одной стороны, poll(2) не возвращает POLLHUP когда отваливаются соединения. С другой стороны, посылаемые клиентами данные забивают буфер ввода или заставляют сервер читать ненужный мусор. С третьей, если сокет "прикрыть" по shutdown(fd, SHUT_RD), poll с events=POLLIN возвращается сразу, а read(2)/recv(2) читают 0 байт и, естественно, мы получаем все признаки закрытого соединения.

Более литературно:

Как уже, надеюсь, понятно, сокеты клиентов опрашиваются при помощи poll(2). Проблема в том, что хочется получить запрос от клиента и дальше игнорировать все данные -- тут вроде помогает shutdown на SHUT_RD. Пока всё хорошо. Читаем мануал к poll(2), в котором значится POLLHUP, который якобы возвращается при закрытии соединения. Так вот, он почему-то не возвращается. Гугление данного вопроса привело меня к тому, что разные ОС обрабатывают это событие по-разному: одни возвращают POLLHUP, другие POLLIN, который нужно проверять read(2)/recv(2) на наличие входных данных: если данных нет, то и толку от такого сокета -- тоже. Пока ожидается запрос от клиента, всё хорошо; только вот сокет, закрытый на чтение, заведомо вернёт POLLIN (ибо чтение из такого сокета блокировано не будет). Возвращаемся к прежней проблеме. Как сделать так, чтобы данные, приходящие от клиентов, игнорировались? Пока вижу три варианта -- ловить POLLIN и:
  •  читать всё что накопилось в буфере и сливать в /dev/null;
  •  отрубать клиентов нафиг, ибо протокол надо соблюдать;
  •  ничего не читать, пусть висит в буфере.

Первый вариант мне не нравится: почему процесс должен вообще дёргаться по поводу данных, приходящих от клиентов, когда они не нужны? Неужели нет способа их просто игнорировать? Второй какой-то "фошысский", хотя на данный момент самый симпатичный. Ну и третий тоже не подходит, потому что если буфер сокета -- пара килобайт, а клиентов -- пара тысяч, совершенно ненужный мусор съест 4 мегабайта памяти!

Код:

Естественно, весь код программы (хоть она и не дописана пока) приводить не буду, ниже с сокращениями:

Код

void
mux_monitor(Multiplexer *mux)
{
    size_t i;

    assert(mux != NULL);

    while (poll(mux->pool->pool, mux->pool->count, 5) >= 0)
    {
        for (i = 0; i < mux->pool->count; ++i)
        {
            if (mux->pool->pool[i].revents != 0)
                printf("Events recvd: %x\n", mux->pool->pool[i].revents);
            if ((mux->pool->pool[i].revents &
                 (POLLERR | POLLHUP | POLLNVAL)) != 0)
            {
                printf("POLLERR, POLLHUP, or POLLNVAL rcvd\n");
                mux_hangup(mux, i--);
            }
            else if ((mux->pool->pool[i].revents & POLLIN) != 0)
            {
                printf("POLLIN rcvd\n");
                if (mux->pool->pool[i].fd == mux->sockCtl ||
                    mux->pool->pool[i].fd == mux->sockClient)
                {
                    mux_accept(mux, i);
                }
                else
                {
                    mux_read(mux, i);
                }
            }
        }
        puts(".");
    }
}

void
mux_accept(Multiplexer *mux, size_t idx)
{
    int sock;
    int newIdx;

    assert(mux != NULL);

    puts("accept");
    sock = accept(mux->pool->pool[idx].fd, NULL, 0);

    newIdx = pool_slotReserve(mux->pool);
    if (newIdx >= 0)
    {
        mux->pool->pool[newIdx].fd = sock;
        mux->pool->pool[newIdx].events = POLLIN;
        mux->pool->pool[newIdx].revents = 0;
    }
}

void
mux_read(Multiplexer *mux, size_t idx)
{
    ssize_t rd;
    char buf[BUFSIZ];
    Connection conn;

    rd = read(mux->pool->pool[idx].fd, buf, sizeof(buf) - 1);
    buf[rd] = '\0';

    if (rd == 0)
    {
        /* socket closed */
        mux_hangup(mux, idx);
        return 0;
    }

    puts(buf);

    conn.fd = mux->pool->pool[idx].fd;

    /* We've read everything we wanted */
    shutdown(conn->fd, SHUT_RD);
    mux->pool->pool[conn->poolIdx].events = 0;
    mux->pool->pool[conn->poolIdx].revents = 0;
}

void
mux_hangup(Multiplexer *mux, size_t idx)
{
    assert(mux != NULL);

    printf("hangup: %u\n", (unsigned int)idx);
    shutdown(mux->pool->pool[idx].fd, SHUT_RDWR);
    close(mux->pool->pool[idx].fd);
    pool_slotFree(mux->pool, idx);
}


Если нужна ещё какая-то информация о коде (или сам код) -- приведу с радостью по первому требованию.

sockCtl и sockClient на данном этапе не имеют значения (кроме того, что они находятся в состоянии LISTEN), в дальнейшем их пути разойдутся.

Сценарий:

Запускаем сервер, телнетимся на нужный порт. Вводим данные -- всё хорошо; тыкаем ^], вводим q -- на стороне сервера видим:

Код

Events recvd: 1
POLLIN rcvd


после чего (поскольку read(sock)==0 обрабатывается в mux_read()) сокет захлопывается сервером. Если же порт клиента после первого mux_read() был подвергнут shutdown(SHUT_RD), а соответствующий poll.events установлен в 0, сервер вообще ничего не сообщает и продолжает радостно выдавать по точке в 5 секунд.

Да, по идее mux_read() должен возвращать значение, по которому i будет декрементироваться в случае, если сокет был удалён, но это не критично (в крайнем случае, poll() уйдёт на второй круг), и расправа над этой багой запланирована на сегодняшний вечер.

Большое спасибо за внимание!


--------------------
Kernel panic: /dev/null overflow!
GCS/IT/MU/O d-@ s: a- C++$>++++$ ULSB(+++) P+++ L+++>++++ !E W++(-) N o? K w-- O? M>+ V? PS+ PE Y+ PGP+>+++ t- 5 X+ R- !tv b+ DI+ D+ G e++ h--- r++ y?
B4F1 54B6 8738 26CD 5125 0581 B923 9273 FE59 1981
PM MAIL WWW ICQ   Вверх
GrayCardinal
Дата 10.9.2008, 16:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Фигасе
****


Профиль
Группа: Завсегдатай
Сообщений: 3039
Регистрация: 9.11.2003

Репутация: 3
Всего: 58



NightGoblin
Если данные идут не по протоколу, имеешь полное право убить нафиг smile
Если у тебя "строковый" сервер, буфер сокета можно уменьшить байт до 256 smile Как не скажу, но знаю что можно. Вообще я не до конца понял проблему smile

Это сообщение отредактировал(а) GrayCardinal - 10.9.2008, 16:45


--------------------
PM MAIL WWW   Вверх
NightGoblin
Дата 10.9.2008, 17:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1021
Регистрация: 24.11.2002
Где: 127.0.0.1

Репутация: нет
Всего: 11



GrayCardinal,
Размер буфера можно установить до приёма соединения через SO_RCVBUF -- но, насколько мне удалось выяснить, после установки соединения этот буфер можно только увеличить (http://docs.hp.com/en/B2355-90136/ch03s01.html).

Проблема, собственно, в том, что нужно всё и сразу -- а существующие варианты работают только наполовину smile

Что конкретно непонятно? Если нужно -- объясню подробнее.

А вообще я сам всё больше склоняюсь к варианту "не умеешь вежливо разговаривать по протоколу -- ступай лесом" smile


--------------------
Kernel panic: /dev/null overflow!
GCS/IT/MU/O d-@ s: a- C++$>++++$ ULSB(+++) P+++ L+++>++++ !E W++(-) N o? K w-- O? M>+ V? PS+ PE Y+ PGP+>+++ t- 5 X+ R- !tv b+ DI+ D+ G e++ h--- r++ y?
B4F1 54B6 8738 26CD 5125 0581 B923 9273 FE59 1981
PM MAIL WWW ICQ   Вверх
MAKCim
Дата 10.9.2008, 21:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Воін дZэна
****


Профиль
Группа: Экс. модератор
Сообщений: 5644
Регистрация: 10.12.2005
Где: Менск, РБ

Репутация: 6
Всего: 207



NightGoblin
сформулируй одним предложением
что надо сделать?  smile 


--------------------
Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

PM MAIL   Вверх
MAKCim
Дата 10.9.2008, 21:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Воін дZэна
****


Профиль
Группа: Экс. модератор
Сообщений: 5644
Регистрация: 10.12.2005
Где: Менск, РБ

Репутация: 6
Всего: 207



Код

shutdown(conn->fd, SHUT_RD);
    mux->pool->pool[conn->poolIdx].events = 0;
    mux->pool->pool[conn->poolIdx].revents = 0;

после этого, я так понимаю, сервер не принимает данных от клиента, а может только возвратить результат?


--------------------
Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

PM MAIL   Вверх
NightGoblin
Дата 10.9.2008, 21:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1021
Регистрация: 24.11.2002
Где: 127.0.0.1

Репутация: нет
Всего: 11



Цитата(MAKCim @  10.9.2008,  13:14 Найти цитируемый пост)
сформулируй одним предложением
что надо сделать?


Определить, когда отключился клиент, сокет которого закрыт на чтение. Это один из вариантов, но это будет достаточным решением проблемы.

Цитата(MAKCim @  10.9.2008,  13:39 Найти цитируемый пост)
после этого, я так понимаю, сервер не принимает данных от клиента, а может только возвратить результат? 


Именно так.


--------------------
Kernel panic: /dev/null overflow!
GCS/IT/MU/O d-@ s: a- C++$>++++$ ULSB(+++) P+++ L+++>++++ !E W++(-) N o? K w-- O? M>+ V? PS+ PE Y+ PGP+>+++ t- 5 X+ R- !tv b+ DI+ D+ G e++ h--- r++ y?
B4F1 54B6 8738 26CD 5125 0581 B923 9273 FE59 1981
PM MAIL WWW ICQ   Вверх
MAKCim
Дата 10.9.2008, 22:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Воін дZэна
****


Профиль
Группа: Экс. модератор
Сообщений: 5644
Регистрация: 10.12.2005
Где: Менск, РБ

Репутация: 6
Всего: 207



Цитата(NightGoblin @  10.9.2008,  21:44 Найти цитируемый пост)
Определить, когда отключился клиент, сокет которого закрыт на чтение.

проверить состояние TIME_WAIT на сокете



--------------------
Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

PM MAIL   Вверх
NightGoblin
Дата 11.9.2008, 18:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1021
Регистрация: 24.11.2002
Где: 127.0.0.1

Репутация: нет
Всего: 11



MAKCim, есть ли переносимый способ проверить состояние сокета на TIME_WAIT? В линуксовом tcp(7) есть TCP_INFO, но там написано "This option should not be used in code intended to be portable." Сервер должен работать как минимум на FreeBSD (и, желательно, на Linux, на котором ведётся разработка smile ). То же касается и POLLRDHUP (пришло что-то на мыло, тут не вижу -- удалено?) -- насколько всё это портабельно?


--------------------
Kernel panic: /dev/null overflow!
GCS/IT/MU/O d-@ s: a- C++$>++++$ ULSB(+++) P+++ L+++>++++ !E W++(-) N o? K w-- O? M>+ V? PS+ PE Y+ PGP+>+++ t- 5 X+ R- !tv b+ DI+ D+ G e++ h--- r++ y?
B4F1 54B6 8738 26CD 5125 0581 B923 9273 FE59 1981
PM MAIL WWW ICQ   Вверх
MAKCim
Дата 11.9.2008, 21:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Воін дZэна
****


Профиль
Группа: Экс. модератор
Сообщений: 5644
Регистрация: 10.12.2005
Где: Менск, РБ

Репутация: 6
Всего: 207



NightGoblin
да, TCP_INFO и POLLRDHUP не переносимы

Цитата(NightGoblin @  10.9.2008,  21:44 Найти цитируемый пост)
Определить, когда отключился клиент, сокет которого закрыт на чтение.

так тут как бы POLLHUP'а достаточно

ведь что просходит
когда мы делаем shutdown(SHUT_RD), то клиенту посылается FIN сегмент, он отвечает на него ACK'ом
на стороне сервера сокет переходит в состояние FIN_WAIT_2
после close() на сокете на стороне клиента серверу посылается FIN сегмент, сокет на сервере переходит в состояние TIME_WAIT
через 2*MSL секунд сокет на сервере переходит в состояние TCP_CLOSE и poll() на нем возвращает POLLHUP
так что POLLHUP достаточен для определения момента закрытия соединения


--------------------
Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

PM MAIL   Вверх
NightGoblin
Дата 16.9.2008, 17:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1021
Регистрация: 24.11.2002
Где: 127.0.0.1

Репутация: нет
Всего: 11



MAKCim, в общем, пока что я не пробовал разбираться. Проблема получилась в том, что я написал короткий тест, и там вроде бы всё работало. Потом переписал это под рабочую версию (вроде бы, ничего не поменял) -- и всё, POLLHUP перестал приходить. Пока что сделал poll на POLLIN и отрубаю клиентов при получении новых данных (которые мне не нужны). Тоже не оптимальный способ так делать, но до поры - до времени сойдёт. В ближайшее время покопаюсь, а пока другие проблемы свалились на голову (не относящиеся к теме).


--------------------
Kernel panic: /dev/null overflow!
GCS/IT/MU/O d-@ s: a- C++$>++++$ ULSB(+++) P+++ L+++>++++ !E W++(-) N o? K w-- O? M>+ V? PS+ PE Y+ PGP+>+++ t- 5 X+ R- !tv b+ DI+ D+ G e++ h--- r++ y?
B4F1 54B6 8738 26CD 5125 0581 B923 9273 FE59 1981
PM MAIL WWW ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Сети | Следующая тема »


 




[ Время генерации скрипта: 0.1013 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.