LINUX.ORG.RU

tcp сервер, select и таймаут

 , ,


0

4

Неблокирующий ТСР сервер на select`е. Подключаются писатель и читатель. Сервер принимает от писателя структуру с intами, сохраняет и отдаёт читателю при его подключении. Если писатель не подключается за время t, поля структуры становятся 0xffffffff и читатель различает таймаут писателя.

Если использовать один listen_fd на оба типа, то подключение читателя сбросит таймаут и не узнать о недоступности писателя.

Если listen_fd разные и в readfds внести listen_fd только писателя, то select не обработает подключение читателя. А если в fd_set включить listen_fd обоих, то подключение читателя опять обнулит таймаут.

На ум приходит fork(), где родитель при подключении читателя отправляет SIGUSR1 порождённому, а тот, принявший структуру от писателя или установивший её поля в 0xffffffff при таймауте, передаёт структуру родителю через pipe().

Как обработать ситуацию одним процессом, по возможности без тредов?


Ответ на: комментарий от urxvt

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

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

Для того различать двух клиентов тебе нужно придумать протокол прикладного уровня.

Как обработать ситуацию одним процессом, по возможности без тредов?

Использовать неблокирующее IO.

urxvt ★★★★★
()

Неблокирующий ТСР сервер на select`е.

Не делай так. Просто не делай. select – срань, говно и днище.

     WARNING: select() can monitor only file descriptors numbers that
       are less than FD_SETSIZE (1024)—an unreasonably low limit for
       many modern applications—and this limitation will not change.
       All modern applications should instead use poll(2) or epoll(7),
       which do not suffer this limitation.

Если у тебя номер fd будет 1024 или больше, select насрёт тебе в стэк.

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

Не пиши то о чём не знаешь. У select конечно есть недостатки (целых два), но стек он не испортит, если только ты не подашь ему некорректную чушь на вход. fd больше 1024 им тоже можно следить. В контексте проги автора ни один из недостатков селекта не проявляется.

Мда, твоя цитата оказывается из мана (нового, в debian 11 этого абзаца нет). Ужас, что за нубы ман пишут.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)

Таймаут селекта не для того чтоб с помощью него высокоуровневую логику организовывать, он только для того чтоб селект не повис на вечно если не будет событий. Более того, селект не гарантирует что он будет ждать ровно столько сколько ты указал в таймауте, он может завершиться раньше с возвратом -1 и errno=EINTR. Используй clock_gettime() для подсчёта подключился ли кто-то или нет и настройки правильного таймаута для селекта в очередной раз. А в селект суй все сокеты которыми интересуешься в данный момент.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax

fd больше 1024 им тоже можно следить

Нет, нельзя. Там битовое поле в 1024 бита (128 байт) размером. Если ты попытаешься выставить там fd больше 1024, то выйдешь за границы структуры и насрёшь в соседние структуры в памяти.

https://github.com/bminor/glibc/blob/master/misc/sys/select.h#L59

https://github.com/bminor/glibc/blob/master/bits/select.h#L32

Типичный сишник, не может мануал прочитать и срёт в память прямо на ровном месте.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от i-rinat

Мануалы — для слабаков, которые не могут посмотреть в код ядра.

Тю! Нет, так можно сказать, что в FreeBSD лимита нет, потому что можно сделать #define FD_SETSIZE 12345678 перед подключением <sys/select.h>. Но это всё от лукавого и лучше select() тупо не использовать.

P.S. а в макоси select() сломан вообще напрочь уже лет 20, например.

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

Так можно не только в FreeBSD сделать но и в Linux. FD_SETSIZE это чисто юзерспейсная константа (с возможностью её переопределить), и вообще на самом деле select принимает произвольные битовые массивы, а в первом аргументе - их длину (вот только не помню - округляется она до байта, до int-а или до long-а, но это не суть).

Вот такой вызов например полностью норм:

long a;
select(10, (fd_set*)&a, NULL, NULL, NULL);

Использоваться будут 10 младших битов из a.

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

Так можно не только в FreeBSD сделать но и в Linux. FD_SETSIZE это чисто юзерспейсная константа (с возможностью её переопределить), и вообще на самом деле select принимает произвольные битовые массивы, а в первом аргументе - их длину (вот только не помню - округляется она до байта, до int-а или до long-а, но это не суть).

Как ты думаешь, насколько такая срань переносима и не сломается ли она вот прямо завтра? А потом сишники ноют, что их за программистов не считают. Так и тянет сунуть в glibc коммит с вот таким:

#ifdef FD_SETSIZE
# if FD_SETSIZE > 1024
#  error "HAHAHAHA YOUR CODE IS GAY AND SUCKS COCKS IN HELL"
# endif
#endif

И это всё не считая того, что там всё ещё мать его массив. Если у тебя каким-то сраным хером номера fd будут в районе миллиарда, ты будешь сотни метров выделять просто под select()? А если при таком сценарии твой код случайно этот fd_set на стеке выделит, то ты его, опять же, нахрен просрёшь.

Короче, select() – срань, говно и днище, и его надо выкинуть. Нет ровно ни одной причины использовать этот хтонический ад кутежа и угара, тащемта например.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 3)
Ответ на: комментарий от hateyoufeel

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

Если у тебя каким-то сраным хером номера fd будут в районе миллиарда, ты будешь сотни метров выделять просто под select()?

Не будут, ядро не позволит. В ядре для списка fd процесса тоже массив и весьма ограниченной длины (и ещё больше ограниченной через sysctl вроде). А если ты даже пересоберёшь ядро чтобы оно разрешало такие fd, то способов сделать такой номер ровно два: либо открыть миллиард дескрипторов одновременно, либо специально сделать dup2() на этот номер. Потому как автоматическое выделение fd (везде, кроме винды, там это вообще не индексы) делается строго по очереди (самый маленький из свободных).

Проблемы у селекта две:

  1. действительно, выделять ради одного дескриптора с номером 10000 (это вполне реально уже в отличие от твоего миллиарда) целый массив в 2250 байт, а затем в ядре в цикле искать в нём этот единственный единичный бит - как-то нехорошо, но эта проблема неактуальна если прога использует всего несколько дескрипторов.

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

Зато у него есть два плюса:

  1. его все знают

  2. это единственный полностью кроссплатформенный способ опроса

А так, если нам заранее известна ОС, то наверно для простых применений удобнее poll (сам его использую для них) а для нагруженных - kqueue или epoll, да.

Так и тянет сунуть в glibc коммит с вот таким:

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

#ifndef FD_SETSIZE
#define FD_SETSIZE 1024
#endif

Хм, походу в glibc таки сделали почти как ты хочешь, они затирают кастомные #define своими, а из-за каких-то флагов компилятора это переопределение define (без undef!) не пишет никаких варнингов.

Ну значит ты частично прав - в современном Linux просто переопределить FD_SETSIZE не получится, придётся объявлять свой альтернативный fd_set и свою альтернативную константу для его размера. Но стеку все равно ничего не угрожает.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 5)
Ответ на: комментарий от firkax

С long-ом - переносима везде кроме винды (там не битовые массивы а списки fd). С переопределением FD_SETSIZE - переносима вообще везде (на юниксах оно будет работать как я написал, на винде не влияет т.к. там и без него нет ограничений на номер).

Кто на венде пользуется select() и зачем? Гоните его! Насмехайтесь над ним! На венде есть куда более производительные и удобные API.

Если у тебя каким-то сраным хером номера fd будут в районе миллиарда, ты будешь сотни метров выделять просто под select()?

Не будут, ядро не позволит. В ядре для списка fd процесса тоже массив и весьма ограниченной длины (и ещё больше ограниченной через sysctl вроде). А если ты даже пересоберёшь ядро чтобы оно разрешало такие fd, то способов сделать такой номер ровно два: либо открыть миллиард дескрипторов одновременно, либо специально сделать dup2() на этот номер. Потому как автоматическое выделение fd (везде, кроме винды, там это вообще не индексы) делается строго по очереди (самый маленький из свободных).

Это где-то гарантируется? Нет? Ну и в лес. Ты мне тут сам затираешь про переносимость, а потом рассказываешь про непереносимые костыли, которые могут сломаться буквально завтра.

То, что там массив, это всего лишь незначительная деталь реализации. Завтра там будет дерево, а fd будут начинаться сразу с миллиарда (после 0, 1 и 2).

Проблемы у селекта две:

Нет, проблем у select() куда больше.

Зато у него есть два плюса:

  1. его все знают

Кто все? Пачка 70-летних маразматиков, которые застали select(), но не застали даже poll()? Последний, кстати, тоже входит в POSIX.

  1. это единственный полностью кроссплатформенный способ опроса

Он не кросс-платформенный. В той же macos select() постоянно сломан, потому что на него там срать хотели. На венде твои выкрутасы с FD_SETSIZE тоже будут вертеть на одном месте. Те же перцы из curl используют poll(). И что-то мне подсказывает, они про кросс-платформенность знают больше чем ты.

А так, если нам заранее известна ОС, то наверно для простых применений удобнее poll (сам его использую для них) а для нагруженных - kqueue или epoll, да.

Или можно просто не быть ушлёпком и взять libuv какой-нибудь, где всё уже нормально сделано за нас. Потому что ты всё равно его переизобретаешь в каждом втором своём проекте заново.

Так и тянет сунуть в glibc коммит с вот таким:

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

#ifndef FD_SETSIZE
#define FD_SETSIZE 1024
#endif

Нет, не достаточно. В моём случае код сломается при сборке. В твоём – просто насрёт в память. Я понимаю, что срать в память – это любовь каждого сишника, но пора бы уже приучаться к горшку. Ты большой мальчик, я в тебя верю!

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Я немного подправил сообщение пока ты отвечал.

Кто на венде пользуется select() и зачем? Гоните его! Насмехайтесь над ним! На венде есть куда более производительные и удобные API.

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

Это где-то гарантируется?

Вроде в POSIX где-то указано. Но, что важнее, на такой порядок заполнения дескрипторов рассчитана куча софта начиная с 80х готов и ломать его разумеется не будут.

В той же macos select() постоянно сломан

Что в нём можно сломать?

На венде твои выкрутасы с FD_SETSIZE тоже будут вертеть на одном месте.

На винде FD_SETSIZE не важен вообще и fd_set там не массив.

взять libuv какой-нибудь

Лишние зависимости не нужны.

В твоём – просто насрёт в память

Нет же. Проверку что fd<FD_SETSIZE перед использованием дескриптора в select-е никто не отменял.

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

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

На венде, ага.

Что в нём можно сломать?

Если мне память не изменяет, он тупо не писал про часть ивентов на сокетах. Потому что всем насрать на select().

На винде FD_SETSIZE не важен вообще и fd_set там не массив.

Отлично. Значит, твоя переносимость уже не работает здесь. Всё, select() больше не нужен.

Нет же. Проверку что fd<FD_SETSIZE перед использованием дескриптора в select-е никто не отменял.

Што?

#define __FD_SET(d, s) \
  ((void) (__FDS_BITS (s)[__FD_ELT(d)] |= __FD_MASK(d)))

Где тут проверка? Покажи мне её.

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

Проверка в коде программы. У меня например это так выглядит:

  FD_ZERO(&fds);
#ifdef WIN32
  if(sock<0) { WSASetLastError(WSAEBADF); return -1; }
  FD_SET((unsigned int)sock, &fds);
#else
  if(sock<0 || sock>=(int)FD_SETSIZE) { errno=EBADF; return -1; }
  FD_SET(sock, &fds);
#endif
firkax ★★★★★
()
Ответ на: комментарий от firkax

Проверка в коде программы. У меня например это так выглядит:

 FD_ZERO(&fds);
#ifdef WIN32
 if(sock<0) { WSASetLastError(WSAEBADF); return -1; }
 FD_SET((unsigned int)sock, &fds);
#else
 if(sock<0 || sock>=(int)FD_SETSIZE) { errno=EBADF; return -1; }
 FD_SET(sock, &fds);
#endif

Погоди. А где переносимость-то? Уже лапша из ifdef на ровном месте.

Кстати, ни разу таких проверок нигде не видел. Да и понятно почему. Мало кто настолько упорот, чтобы из-за валидного fd вешать нахрен всю программу, потому что ему именно что всралось использовать ущербный API, который старше большинства живущих сегодня программистов.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от vbr

Проще всего указывать таймаут, например, в секунду. Ну или в миллисекунду. Один фиг процессор не загрузит. А логику проще писать будет.

/me рыдает

Серьезно, г-дь да вам epoll, kqueue и прочие прелести жизни. Но нет, будем страдать.

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

Кому это всё надо? select простой как валенок и работает везде. На винде твой kqueue будет работать? Почему я пишу man epoll на макоси и мне говорит «No manual entry for epoll»? Ты мне предлагаешь ифдефами обмазаться под каждую платформу или что? Правильный совет будет - брать что-то вроде libuv. Но это для каких-то крутых и производительных программ. А если тебе хватает ограничений select-а - зачем усложнять?

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

Типичный сишник, не может мануал прочитать и срёт в память прямо на ровном месте

Кто будет читать маны Перед тем как что-то сломается?)

Средний сишник имеет уровень развития трёхлетнего ребёнка: к горшку до конца ещё не приучен, срёт куда попало, читать не умеет, но зато везде лезет.

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

А если тебе хватает ограничений select-а - зачем усложнять?

Зачем использовать всратый интерфейс, распространяя порочные практики? Я никогда не пойму эту тягу к копролиту.

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

Ну значит ты частично прав - в современном Linux просто переопределить FD_SETSIZE не получится, придётся объявлять свой альтернативный fd_set и свою альтернативную константу для его размера. Но стеку все равно ничего не угрожает.

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

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

А это значит, что мы уже имеем сотни обосранных стеков.

Каким образом? Если FD_SETSIZE не переопределился то просто лимит на количество fd оставался маленьким и прога бы дропала лишние коннекты. Вслепую FD_SET не проверяя перед этим fd<FD_SETSIZE я разумеется не делал.

Впрочем, поскольку select (тем более, линуксовый) для прог где ожидается столько коннектов я не использую, то это всё остаётся в теории.

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

Если FD_SETSIZE не переопределился то просто лимит на количество fd оставался маленьким и прога бы дропала лишние коннекты.

Мы понятия не имеем как именно ты её переопределял, так что шанс обосраться у тебя все ещё есть! И я уверен что ты им воспользовался, учитывая твою страсть делать все поперек документации.

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

ifdef-лапши тут немного. Ну и, если бы это был код в составе цельного приложения, диапазон fd можно проверять ещё при его создании (accept/socket) если не винда.

Кстати, ни разу таких проверок нигде не видел

Если их нет вообще но есть fd_set - очень плохо, прога и правда может испортить стек. Но возможно они там после socket/accept стоят.

Мало кто настолько упорот, чтобы из-за валидного fd вешать нахрен всю программу

Из двух вариантов: выкинуть сокет или испортить стек, выбирать надо первое.

Но вообще повторю - если прога держит одновременно хотя бы сотни коннектов - лучше и правда подумать про другое апи. Но не из-за мифической порчи стека.

И кстати рядом с процитированным кодом есть ещё один ifdef и ветка с poll(). Но т.к. подразумевалась работа на всех плаформах, то надо поддержать и те где poll почему-то недоступен.

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

Из двух вариантов: выкинуть сокет или испортить стек, выбирать надо первое.

Выбирать надо epoll() и kqueue(). Пердолики gonna пердолиться.

И кстати рядом с процитированным кодом есть ещё один ifdef и ветка с poll(). Но т.к. подразумевалась работа на всех плаформах, то надо поддержать и те где poll почему-то недоступен.

Таких нет. poll() есть даже на HPUX.

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

Из двух вариантов: выкинуть сокет или испортить стек, выбирать надо первое.

а из вариантов: долбиться с select и дропать соединения или не долбиться, взять poll и не дропать соединения?

надо поддержать и те где poll почему-то недоступен.

это какие? Ты можешь их хотя бы перечислить?

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от hateyoufeel

это какие? Ты можешь их хотя бы перечислить?

Винда же. Ну и мало ли какие обнаружатся экзотические, а у меня к ним уже поддержка сразу есть. Я нигде не писал что select лучше всех и надо его предпочитать всегда. Но поддержка лишней не будет, если она малой ценой даётся.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от cumvillain
  1. надо ещё догадаться искать аналог среди всяких WSA*() - вот select называется как надо и с ним таких проблем нет

  2. судя по тому что я сейчас поискал, он появился в висте, а значит опять никакой универсальности не будет - его нет даже в до сих пор распространённом winxp, не говоря уже про win9x (хотя признаюсь последняя железка с win98 у меня была году в 2015 наверно, но отказывать в его поддержке из-за чьей-то селектофобии это странно)

Но вообще претензия непонятна, виндовый select() это по сути poll() и есть, только с синтаксисом от селекта.

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

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

Хотя кстати тот код с селектом ещё в 2012 написан, даже по твоим дурацким критериям xp тогда было в поддержке.

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

это какие? Ты можешь их хотя бы перечислить?

Винда же. Ну и мало ли какие обнаружатся экзотические, а у меня к ним уже поддержка сразу есть. Я нигде не писал что select лучше всех и надо его предпочитать всегда. Но поддержка лишней не будет, если она малой ценой даётся.

А теперь подведём итог. В 2024 году использовать select надо, если соблюдаются все условия ниже:

  • Ты пишешь код на C и не знаешь других языков;
  • Код должен работать под лялексом И вендой, последняя обязательно без WSL и прочих cygwin;
  • Тебе абсолютно нельзя использовать сторонние библиотеки, даже самые простые;
  • При этом при всём, к производительности требования вообще нулевые (но зачем тогда тут C?) и код может терять соединения без проблем;
  • Срать мимо стека и засирать всю память, после чего дохнуть в корку можно, к этому вопросов нет (ах, вот зачем тут C!);
  • За лишний ifdef тебя расстреляют, количество ifdef отмерено специальным комитетом и их хватает ровно на WSASetError/WSAGetError/errno, но не на poll/WSAPoll.

Короче, ровно те условия, в которых @firkax пишет код и с которыми никто и никогда больше не сталкивался и никогда не столкнётся.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 5)
Ответ на: комментарий от hateyoufeel

если соблюдаются все условия ниже

На самом деле, всё проще. Достаточно такого: «я знаю select, мне его достаточно, не хочу тратить время на набивание шишек на других API, потому что это ничего не даст».

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

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

Радуйся. Ведь весь остальной мир ударился в крайность в противоположном направлении. И теперь софт устаревает за год.

Проблема в том, что в лялексе ровно та же херня даже с сишным софтом. Я тут пытался одну прогу собрать, которая пару лет назад точно работала, а сейчас библиотеки обновились и хер там. Благо, можно было старый nixpkgs взять и собрать буквально кусок системы двухлетней давности.

Достаточно такого: «я знаю select, мне его достаточно, не хочу тратить время на набивание шишек на других API, потому что это ничего не даст».

Ну да, это и называется быдлокодерством.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)