Как правильно поддерживать порт прослушивания в течение длительного времени?
Я написал это небольшое серверное приложение в чистом 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), либо из специального потока в вашем коде.
Итак, если вы обнаружите, что не можете подключиться к серверу в несколько последовательных попыток (требуется несколько из-за возможного состояния перегрузки), вы можете рассмотреть сокет сломанный и воссоздать его или перезапустить сервер.