Как правильно поддерживать порт прослушивания в течение длительного времени?

Я написал это небольшое серверное приложение в чистом C, которое слушает входящие соединения в данном порту, очень простые вещи.

Он идет с обычной процедурой инициализации сокета, создает в порт socket(), затем bind(), сообщает его listen(), и ifinitely проходит через select(), ожидая входящих соединений с accept().

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

Итак, вопрос: почему, черт возьми, порт закрыт без моего заявления и что я могу сделать, чтобы это не произошло?

Это ожидаемое поведение? Должен ли я проверить какие-то исключения или сделать "проверку работоспособности" на прослушивающем сокете, чтобы при необходимости повторно открыть его?

Код: https://gist.github.com/Havenard/e930be035a3bee75c018 (да, я понимаю, что использую 0 как подсказку для ошибок, и это плохая оценка и прочее, но это не относится к вопросу, как я объяснил в комментариях, когда я установил дескриптор файла сокета на 0, чтобы остановить цикл и закрыть приложение).

Ответ 1

Я бы начал с очистки:

  • разрезать его на более мелкие, читаемые, проверяемые, проверяемые функции.
  • использование связанных списков выглядит беспорядочно; его можно было бы упростить, возможно, введя некоторые общие функции.
  • заменить все глупые "\ x20" символьные константы на более читаемые "эквиваленты"
  • избегать манифестных магических констант, подобных здесь if (n_case > 0) memcpy(nick, node->nick, (n_case > 32 ? 32 : n_case));; sizeof - ваш друг.
  • не использовать нуль в качестве контрольного значения для неиспользуемого дескриптора файла; вместо этого используйте -1. ​​
  • использовать типы unsigned для размеров и индексов; отрицательные индексы будут повреждать память, сбрасываемые неподписанные типы будут работать быстро. (failfast - ваш друг)

Это всего лишь несколько часов редактирования.

Моя догадка заключается в том, что после очистки/рефакторинга ваша "ошибка" наступает волшебным образом.

Сноска: Нет, я не буду делать твою работу за тебя. Не для 100 очков, не для 1000. Пожалуйста, уберите свой беспорядок.

Ответ 2

Этот ответ является главным образом обзором кода мест, где вы вызываете close().

Строка 330: вы закрываете сокет, но не продолжаете сразу, как в других местах вашего кода. Это может привести к странному поведению.

Линия 928: В большинстве мест вы устанавливаете клиентский или серверный сокет 0 после вызова close(). После этого вызова вы не выполняете.

Строка 1193: тот же комментарий, что и строка 928.

Строка 1195: тот же комментарий, что и строка 928.

Строка 1218: Тот же комментарий, что и строка 928.

Строка 1234: Тот же комментарий, что и строка 928.

Строка 1236: тот же комментарий, что и строка 928.

Когда я скомпилировал код с полными предупреждениями, я увидел несколько мест, где компилятор отметил функции, объявленные для возврата значения, но не возвращается значение.

x.c:582: warning: no return statement in function returning non-void
x.c:591: warning: no return statement in function returning non-void
x.c:598: warning: no return statement in function returning non-void
x.c:609: warning: no return statement in function returning non-void
x.c:620: warning: no return statement in function returning non-void
x.c:728: warning: no return statement in function returning non-void
x.c:779: warning: no return statement in function returning non-void

Есть много других проблем, как указано в других сообщениях.

Что касается отладки этой проблемы, если я подозревал, что сокет привязки закрывается раньше, я бы перехватил вызов close() с моей собственной версией, которая утверждает, что дескриптор, закрываемый, не должен соответствовать сокет привязки.

Однако, как отметил wildplasser, select() вернет ошибку о недопустимом дескрипторе, если он был закрыт.

Ответ 3

Ошибка заключается в том, что вы используете 0 как недопустимый дескриптор файла. 0 отлично действует и обычно является stdin. Затем слушатель устанавливается в 0 в обработчике сигнала. Затем вы используете 0 как нет fd, и в какой-то момент вы закрываете (0) на каком-то сокете, есть ветки, которые закрываются (fd), не проверяя его на 0, и это фактически закрывает слушателя. Другой возможной возможностью остановить прослушиватель от работы является переполнение отставания.

И еще одна проблема - использование unsigned int для fds. системные вызовы возвращают -1 при ошибке... и эта ошибка не будет обнаружена с если присвоено значение unsigned int struct identd_node → unsigned int handle; struct thread_node → unsigned int skt_clnt, skt_serv;

Ответ 4

Похоже, ваш код должен иметь 2 последовательных ошибки, чтобы вызвать сбой.

Если вы получаете ошибку от выбора, почему бы не распечатать, почему сразу?

В строке 281, printf errno/perror, чтобы узнать, в чем проблема?

Ответ 5

Хотя система не должна вести себя так, как описано, она иногда делает это. Для серверных систем обычно вам необходимо выполнить вызов healthcheck, либо извне (от script), либо из специального потока в вашем коде.

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