LINUX.ORG.RU

fork, sockets & zombie

 , ,


0

1

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

	while (1) {
		struct sockaddr_in remote;
		socklen_t sockaddr_len = sizeof(remote);
		int clientsocket = accept(listensocket, (struct sockaddr*)&remote, &sockaddr_len);
		if (clientsocket < 0) {
			perror("Accept failed");
			return EXIT_FAILURE;
		}
		printf("Connected %s:%d\n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port));
		switch(fork()){
		case -1:
			perror("fork");
			break;
		case 0:
			close(listensocket);
			close(0); close(1); close(2);
			client_worker(clientsocket);
			exit(0);
		default:
			close(clientsocket);
		}
	}
Однако, помимо открытого на свободном порту сокета, я вижу в /proc/ChildPID/fd симлинк на какую-то трубу [pipe:39128]. В результате чего после завершения дочернего процесса получается зомби, который висит, пока не помрет родительский.

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

Дополнительный вопрос: при работе в «приватном» режиме мне не нужны процессы — я хочу лишь запускать отдельные потоки подсоединяющимся «неприватным» клиентам и только отсылать им нужную часть информации, игнорируя прием (закрывать принимающий канал нельзя, а то клиент отвалится). Не будет ли здесь проблем с невесть откуда берущимися трубами или еще какой фигней?

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

Там ничего про эту чертову трубу не сказано. Слушающий сокет в дочернем закрыл, приемопередающий — закрыл в родительском. В дочернем закрыл stdin, stdout и stderr. А вот как эту трубу-то закрыть? Она соединяет дочерний процесс и родительский и не дает дочернему спокойно помереть.

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

Что бы зомби не было. Пока родитель не спросит «статус смерти» дитя, дитё будет зомби.

Кстати fork тут imho лишний. Тоже самое можно добиться (и гораздо крассивее) через select()/poll().

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

Иногда и stdbool использую. Просто не вижу смысла. Все равно переменная bool занимает столько же места, сколько и int.

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

Тоже самое можно добиться (и гораздо крассивее) через select()/poll().

Т.е. завести пул сокетов и опрашивать все, а потом разруливать по номеру активного сокета, что с ним делать? Не, лень. Проще pthreads или fork (смотря какая задача).

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

while (1)

у меня одного глаза режет от использования чисел вместо #include <stdbool.h>?

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

for (;;) { /* whatever */ }
beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)
Ответ на: комментарий от beastie

Да пофиг. Это можно вообще записать в более кошерном виде:

label:
    // code
    goto label;

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

Просто когда у тебя fork/pthreads, состояния каждого клиента хранятся раздельно «сами по себе». А так придется писать менеджер списков; придумывать структуры для хранения статусов клиентов; следить, чтобы память не текла с этими списками... Да и параллелизации же никакой: ты сам по сути занимаешься тем, что ядро явно лучше сделает!

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

Матерые программисты, привыкшие к ассемблеру, все эти ваши while/for вообще не используют ☺

Правда, не вижу в этом смысла: если лаконичнее написать for, лучше так и сделать, нежели натыкивать уйму меток. Пофиг, что в итоге оно так и будет выглядеть — зато код красивше и понятней.

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

За детями тоже следить надо. Но, за ноги ведь никто не держит. ☺ Просто как идея. Обкатать сначала одно, а уж потом и за форки браться.

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

Матерые программисты, привыкшие к ассемблеру, все эти ваши while/for вообще не используют ☺

«Программист на Фортране может написать программу на Фортране на любом языке программирования.»

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

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

А по поводу параллелизации: не забываем, что “Premature optimization is the root of all evil.”

Тут взвешивать надо: один процесс с select() vs. накладные расходы на fork().

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

А ты не смотрел, как Стивенс про это дело рассказывает:

в русском издании «Unix: разработка сетевых приложений», стр.787 и дальше по тексту.

или в оригинале «Unix Network Programming», стр.732 и дальше по тексту.

Там, как мне кажется, варианты решения твоей задачи как раз и рассматриваются.

Но я бы всё же предпочёл select(), как тут уже посоветовали.

DeVliegendeHollander ★★
()

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

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

Тут взвешивать надо: один процесс с select() vs. накладные расходы на fork().

А можно fork и не делать, а запускать поток.

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

Да у меня Стивенс на работе просто. Сегодня заберу домой — положу в сортир.

Что-то я не думал, что wait() помимо ожидания смерти дитятки еще и трупик закапывает…

Eddy_Em ☆☆☆☆☆
() автор топика
Последнее исправление: Eddy_Em (всего исправлений: 1)
Ответ на: комментарий от beastie
while("YOU DO MY JOB BITCH") {
    /* do something here */
}
anonymous
()
Ответ на: комментарий от beastie

Откуда грабли-то будут?

fork — для разнородных действий, потоки — если основной процесс выполняет что-то, общаясь с «приватным клиентом», а остальные клиенты только объедки с барского стола доедают.

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

Мутексы, гонки, разделяемые рессурсы и прочие плюшки. ☺ (Не забываем, что потоки работают в одном адрессном пространстве в отличае от процессов)

Скилы это конечно прокачает, но вот в 90% случаев все эти пляски с бубном себя не оправдывают.

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

Никогда не проверял, но не захлебнется ли ядро на переключении контекстов и разруливании синхронизаций при числе клиентов в несколько тысяч? Причем большинство потоков 99% времени будут бездействовать.

Я обычно придерживаюсь следующей очень простой в реализации схемы: сокеты слушаются в одном потоке через select/poll, как только от клиента приходит «полная» команда, она обрабатывается. Для повышения производительности и отзывчивости сервера часто завожу пул потоков-обработчиков команд.

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

Параллелизация на высоте, а ядро явно очень хорошо делает poll :)

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

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

Ты про потоки? В этом случае синхронизация не нужна. Я уже дважды выше говорил, почему.

как только от клиента приходит «полная» команда, она обрабатывается

Т.е. ты в одном процессе открываешь тысячи файловых дескрипторов?

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

Ты про потоки? В этом случае синхронизация не нужна. Я уже дважды выше говорил, почему.

Может я пропустил, где ты говорил об этом. Ну придет от клиента команда на запись чего-нибудь, нужно синхронизироваться и возможно другие потоки/процессы оповестить или сделать запись в глобальной/общей области данных — пальцем в небо.

Т.е. ты в одном процессе открываешь тысячи файловых дескрипторов?

А что мне за это будет?

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

Ну придет от клиента команда на запись чего-нибудь

Только от «приватного». А он один.

Т.е. ты в одном процессе открываешь тысячи файловых дескрипторов?

А что мне за это будет?

Задолбаешься же контролировать все.

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

Только от «приватного». А он один.

Придет от одного, а синхронизировать нужно всех. В принципе я не спорю, что вариант thread per socket имеет право на существование, мне он был удобен при создании простых серверов, потом вылезли проблемы синхронизации и пожирания ресурсов при большом числе клиентов, я «задолбался контролировать все» и перешел на вышеописанную стратегию, которая пока ни разу не дала повода усомниться в ее правильности.

Кстати, давно собирался прочитать про 10k problem, ты мне как раз об этом напомнил, спасибо! Сейчас посмотрим, как делают настоящие самцы.

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

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

Ладно, придется удалять гланды через жопу.

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

ОК. Попробую и такой вариант.

Вопросы:
1) Как организовать доступ по индексу к динамическому массиву? Неужто при каждом новом подключении-отключении делать realloc с распихиванием номеров файловых дескрипторов? Не очень-то понимаю, как по результату select (диапазон активных fd) перебрать соответствующие этим fd структуры сведений о клиентах!
2) Как быть, если одновременно N клиентов отдают тебе что-то? Заставлять их ждать, обслуживая поочередно? А если им «надоест»?

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

Не очень-то понимаю, как по результату select (диапазон активных fd) перебрать соответствующие этим fd структуры сведений о клиентах!

Ну лично я делаю map (дерево), отображающий идентификатор сокета в структуру сведений. Можно попробовать хэш, но мап ИМХО оптимальней.

Как быть, если одновременно N клиентов отдают тебе что-то? Заставлять их ждать, обслуживая поочередно? А если им «надоест»?

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

Вообще советую почитать маны по select и пр. и посмотреть примеры кода.

staseg ★★★★★
()

Зачем вы закрываете 0, 1, 2? Даже в демонах их отпраляют в /dev/null, чтобы не было проблем, если какая-нибудь библиотека внезапно захочет что-то сказать. Да и в случае ошибки с fork(), тоже можно делать close(clientsocket).

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

Сигналы (kill()), они вобще отдельно, а не по трубам ходят. Откуда у вас там берётся [pipe:39128] я не знаю, может просто наследуется от родителя. Можно посмотреть в каких ещё /proc/*/fd есть такие-же [pipe:39128].

«гланды через жопу» были бы, если бы для получения кода завершения процесса-потока родительскому процессу нужно было бы что-то читать из pipe'а.

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

Зачем вы закрываете 0, 1, 2?

Привык.

чтобы не было проблем, если какая-нибудь библиотека внезапно захочет что-то сказать

Не было проблем.

Можно посмотреть в каких ещё /proc/*/fd есть такие-же [pipe:39128].

Только у родителя и дочерних.

«гланды через жопу» были бы, если бы для получения кода завершения процесса-потока родительскому процессу нужно было бы что-то читать из pipe'а

Ну все равно как-то непродуманно.

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

mky написал:
«/dev/null»

Тогда прошу ответить, как это сделать практически
в СИ-программе, кусок кода, и чтоб работало всюду,
Линукс, BSD, Центос, на любой UNIX.

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

Имеется процесс А, демон, демонизирован как у Eddy_Em, т. е.
close 0, 1, 2.
Процесс А создает три pipe-канала и
запускает процесс Б (fork->execl). Процессы А и Б связаны тремя
pipe-каналами, которые для процесса Б ввод, вывод, вывод ошибок (0, 1, 2).
Почему-то процесс Б на вывод буферизуется блоками, а нужно
построчную буферизацию. Вывод ошибок остается построчным, а может и
вообще не буферированным.
Пока в программу Б вписал setlinebuf(stdout); и это помогло.
Но весьма желательно, чтоб построчная буферизация была сама по
себе, без вмешательства в программу Б, как это бывает при запуске
программы Б из терминала.
Можно ли это сделать?
Наблюдал при невыясненных обстоятельствах и обратную картину -
построчную буферизацию вывода.

oleg_2
()

зомби ты получаешь потому что не делаешь waitpid

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

Затем, что процесс остается в таблице процессов пока родительский процесс не прочитает код возврата.

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

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

Если нужно именно именно C, то в libevent/libev уже всё сделано.

2) Как быть, если одновременно N клиентов отдают тебе что-то?

Если ты действительно тут начинаешь упираться в процессор, то самое простое решение — создать N event-loop'ов, где N==число_ядер.

Заставлять их ждать, обслуживая поочередно? А если им «надоест»?

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

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

которая сигнала завершения дочернего процесса ждет

Сигнала завершения дочернего процесса ждет его родитель. Ему же и нужно знать его статус завершения, и rusage. Именно для этого ось и оставляет зомби, чтобы родитель эту инфу мог получить. Где тут жопа? Так было испокон веков. На первых курсах учат делать waitpidы в обработчике SIGCHLD. Ну ок, на вторых.

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

Что-то я с этими select/poll запутался: и select, и poll, и epoll делают одно и то же. epoll выглядит как-то более высокоуровневым, но poll, похоже, самый простой. Чем пользоваться-то?

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

select имеет ограничение на 1024 сокета, poll не имеет ограничений, но имеет сложность O(n), epoll имеет сложность O(1), но прибит гвоздями к линуксу. А обертками (libevent/libev) ты пользоваться не хочешь?

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

epoll имеет сложность O(1), но прибит гвоздями к линуксу

Не вижу в этом ничего плохого.

А обертками (libevent/libev) ты пользоваться не хочешь?

А у них что со сложностью?

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

А у них что со сложностью?

Они по-умолчанию используют наиболее эффективный доступный метод на данной платформе.

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

Спасибо. Сейчас пошукаю в интернете документацию на libev (уже нашел тест, где его производительность вышла лучше, чем у libevent), сравню с epoll. Где меньше ручной работы, то и выберу.

А пока оформляю библиотечку. Сделал такие типы вебсокетов:

enum ws_type{
	 WS_SINGLE		// single socket -- only one client at a time
	,WS_FORKED		// 1 child process per client
	,WS_THREADED	// 1 thread per client
	,WS_POLL		// 1 process polls many sockets and run worker with client's id
};
+ флажок блокирующий/неблокирующий.

Соответственно, реализация блокирующего single уже есть, блокирующего forked — тоже. С блокирующим threaded проблем не будет. С неблокирующими чуть повозиться придется. Но, чует мое сердце, самым хитрым будет poll (ему, понятное дело, на флажок блокировки наплевать будет).

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

Нет, все-таки libev мне каким-то избыточным показался. epoll попроще.

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

Линукс, BSD, Центос, на любой UNIX

С любой UNIX поосторожнее. Можно подумать, что ваши программы скомпилируются в UNIX'е 1975 года :)

А так, используйте для демона библиотечную функцию daemon() (пример приводить не буду, это же одна строчка) и не думайте слишком много о «любой UNIX». Всё одно, любой практически значимый код, работающий на десятки разных платформ обрастает #ifdef'ами.

Ну вот ещё по теме демонов www.linux.org.ru/jump-message.jsp?msgid=245840

По второму вопросу. Буферизация вывода дело библиотеки, библиотка инициализируется заново при каждом exec(), поэтому только вмешательство в программу Б позволит достичь желаемого.

glibc буферизирует в соответствии с «man setvbuf» — stdout построчно при выводе на терминал и поблочно при выводе в файл (pipe). sdterr идёт без буферизации. Это поведение по умолчанию и изменить его можно (не переписывая библиотку) только вызовом из программы setlinebuf()/setvbuf().

Наблюдал при невыясненных обстоятельствах и обратную картину - построчную буферизацию вывода.

Не знаю, возможно другая libc — другие правила буферизации по умолчанию.

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