| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Сети > Неблокирующий сокет Си проблема с закрытием |
| Автор: MnxVol 22.10.2012, 13:27 | ||
Здравствуйте! Подскажите пожалуйста, в чем может быть проблема. Есть задание написать серверное приложение на Си, которое будет выступать в качестве сервера N2H2 для маршрутизатора Cisco. Смысл данного сервера - ограничение доступа пользователей к ресурсам, указанным в файле banners.txt. Есть рабочее приложение на perl (переработанное из неработающего приложения вот тут: http://www.opennet.ru/base/cisco/cisco_banner.txt.html). Нужно было написать то же самое, но на Си. В итоге программа была написана, и программа работает, но до определенного момента, пока не заканчиваются файловые дескрипторы в моем debian (так как для завершения сокета в цикле используется функция shutdown, вместо close - при использовании close cisco не дает открыть никакую страницу, интернет попросту висит). Извиняюсь за много букв, но я постарался как можно кратко объяснить суть проблемы. спасибо за посильную помощь. Внизу привожу код на Си.
В файле - код на perl. |
| Автор: feodorv 22.10.2012, 18:49 | ||
| А я так понял, что сокет вообще не нужно закрывать, ни с помощью shutdown, ни с помощью close. Просто нужно висеть на этом сокете и ждать прихода пакета. На каждый пришедший пакет слать ответ:
|
| Автор: MnxVol 6.11.2012, 11:26 | ||||
пробовал.
если не использую ни shutdown ни close - сначала работает, но медленно, потом виснет все. Но Nsd все равно постоянно растет, и если потом превысит предельно допустимое для системы - все упадет - too many open files. Это предположение. не проверял. Nsd при проверке доросло только до 6, дальше на запросы перестал отвечать сервер. правда, я не менял условия цикла, я только убрал shutdown. |
| Автор: MnxVol 6.11.2012, 11:54 |
| Кстати, в последнее время замечаю, что если я вместо shutdown вызываю close, то работает, но нестабильно, во-первых очень медленно, во-вторых, недолго))) а если вызываю close после shutdown, происходит то же самое, работает медленно и нестабильно. Но значение Nsd меняется и в том и в другом случае |
| Автор: feodorv 6.11.2012, 11:57 | ||||
Только что обратил внимание, что код на перле использует UDP.
Ну правильно. Схема-то какая должна быть:
для одного клиента. Для нескольких нужно изыскивать более изощрённую схему. |
| Автор: boostcoder 6.11.2012, 12:01 |
| именно по этому я и не приемлю использование системного API, а предпочитаю концентрироваться на решении задачи. |
| Автор: MnxVol 6.11.2012, 16:16 | ||
а разве суть неблокирующих сокетов не в одновременном ожидании больше чем одного подключения? код на perl использует udp, вы правы, пришлось поменять код на tcp для достижения цели, видимо, cisco поменяли протокол. При использовании udp не работает, tcp - все прекрасно. В том то и дело, что дана установка использовать системные API. Не могу отойти от нее. Подскажите тогда в какую сторону сейчас смотреть. |
| Автор: feodorv 6.11.2012, 17:39 | ||
Это всё прописывается на циске. Смотрите соответствующий мануал.
Простите, но в Вашем коде никакой сути неблокирующих сокетов не просматривается. ЗаводИте список клиентов, список сокетов и т.д. Какие проблемы? Однако от этого схема не изменится. Вот маловероятно, что циска устанавливает новое соединение на новый вопрос, скорее всего, она пользуется старым соединением. И именно это уже установленное соединение Вы бросаете на произвол судьбы - ни close, ни чтения/записи (ну, кроме одного единственного). Вы пробовали на этом же соединении читать следующие записи? Прочли запись, посмотрели в базу данных, послали ответ. И всё по новой. Соединение закрылось (например, при ребуте циски), возвращаемся к accept. PS Для данной задачи UDP значительно проще. Поищете команды циски, которые прописывают сервер проверки и протокол обмена. Настоятельно советую))) |
| Автор: Олег2005 6.11.2012, 18:12 | ||
Попробую изложить свое представление о "неблокирующих" и других сокетах. Блокирующих, неблокирующих или асинхронных сокетов в природе не существует. Сокет - это сложный конгломерат-абстракция. В простоте - это как временный рабочий файл - так он и трактуется в *никсах. Блокируют ход программы - операции ввода/вывода, выполняющиеся с этим "псевдорабочим временным файлом". Потому операция connect() - это типичная блокирующая операция. Типично блокирующей операцией я вляется и accept(). Что означает тогда к примеру неблокирующий connect()? Это означает, что типично блокирующую функцию запускают в несвойственном ей режиме, который не является для нее естественным. Потому connect() и отдает сразу управление на следующую команду программы, возвращая SOS - EWOULDBLOCK. И теперь задача дальнейшего кода - все проверить, как же все-таки реально через некоторое время завершится connect(). Вот типичное поведение разных функций, для которых проставлен режим неблокирования: В этом случае работа этого сокета для разных сокетных операций осуществляется следующим образом: · accept() завершает работу сразу же с ошибкой EWOULDBLOCK; · connect() завершает работу сразу же с ошибкой EINPROGRESS; вызывающий процесс продолжит свое выполнение в любом случае, даже если на установку соединения потребуется несколько десятков секунд; · recv(), read() или recvfrom() возвратят -1 (с применением флагов FIONBIO или FNDELAY функции ioctl()) или 0 (O_NDELAY- функция fcntl()) при отсутствии считываемых данных; выставляется ошибка EWOULDBLOCK или EAGAIN. Потому ваш вопрос так сказать повисает в воздухе. Подключения ждет модуль TCP, и в ходящие подключения буферизируются в очереди прослушивающего сокета. accept() висит (блокирует) до тех пор, пока в очереди полностью установленных соединений прослушивающего сокета не появится очередной готовый запрос на обслуживание. Как только запрос появился - accept() cразу создает новый присоединенный сокет и возвращает управление на следующую команду. Насчет асинхронности. Есть функции, которые по жизни сделаны неблокирующими. Не нужны ни ioctl или что еще. Этот режим для них родной...... |
| Автор: feodorv 6.11.2012, 18:58 | ||
Вроде бы это http://www.cisco.com/en/US/products/hw/vpndevc/ps2030/products_configuration_example09186a008088517b.shtml |
| Автор: MnxVol 7.11.2012, 07:50 | ||
не совсем, у меня команда ip urlfilter server vendor n2h2 10.10.10.10 - и из допустимых параметров дальше outside Specify if the server is on the outside network port Specify URL filter server port number retrans Specify retransmission count timeout Specify retransmission timeout vrf Specify VRF <cr> так что я предположил что по крайней мере в моем случае буду использовать tcp. Олег2005, спасибо за подробное объяснение. feodorv, если я вас правильно понял, мне лучше поменять концепцию с неблокирующих на другие?) попробую. Спасибо, чуть позже отчитаюсь о новом варианте. |
| Автор: feodorv 7.11.2012, 09:35 | ||||
Я хоть слово сказал против неблокирующих сокетов? Я двумя руками за. Было бы у меня 10 рук, был бы десятью за. Но ведь надо понимать, что это за зверь - неблокирующий сокет. Вот у Вас в коде применяется recv на неблокирующем сокете. Правильно применяется? Нет. Потому что при работе с неблокирующими сокетами нужно ожидать наступления некоторого события, для чего очень важен вызов select. С помощью этого вызова можно ожидать наступления сразу массы событий от массы же сокетов (и файловых дескрипторов). Но наступления события нужно ожидать (вызовы с блокирующим сокетом просто не вернут управление в программу, пока не наступит ожидаемое событие). У Вас в коде где ожидание события "готовы данные для чтения на таком-то сокете"??? Его нет. Я не против блокирующих сокетов, я против неправильного их использования))) Неблокирующие сокеты позволяют работать сразу с несколькими клиентами, позволяя при этом ожидать подключения новых, в одном единственном потоке. Если у Вас такой клиент один (та самая циска), то смысла в использовании неблокирующих сокетов нет. Да, в этой ситуации достаточно одного блокирующего (nsd). Более того, напишите грамотную программу с участием блокирующего сокета, которая бы постоянно читала бы из сокета nsd (и постоянно бы отвечала туда же). А не требовала бы от циски каждый раз после очередного таймаута переустанавливать соединение с сервером проверки.
Очень жаль. |
| Автор: feodorv 16.11.2012, 16:34 | ||||
Ну, я вижу код приблизительно таким:
А уже process_socket анализирует пришедшие 20 байт, если нужно, дочитывает из сокета оставшееся и посылает ответ:
при очень важном условии: читается из сокета ровно столько, сколько запрошено, и пишется туда ровно столько, сколько запрошено (т.е. если прочитано меньше, надо дочитывать, если записано меньше, нужно дозаписывать остаток). Предварительно нужно убедиться, что размер структуры client_data ровно 20 байт, если нет - воспользоваться #pragma pack |
| Автор: MnxVol 23.11.2012, 12:49 |
| feodorv, спасибо за помощь! При таком раскладе все работает. По крайней мере уже лучше. Правда, оператор case пришлось все же заменить на if, почему-то не хочет у меня case по нормальному с константами работать, а когда прописываю конкретно число - сравнивая два одинаковых числа попадает на default. Спасибо огромное! |
| Автор: feodorv 23.11.2012, 16:03 |
То есть не всё гладко?))) Странно, может ntohs(cd->id) виновато? |
| Автор: MnxVol 27.11.2012, 10:17 | ||
| Не понимаю, что было виной, но сначала он не хотел работать с константой, определенной как unsigned short const REQ_REQUEST = 0x0200; пытался определить ее по-другому - та ошибка пропала, но желаемого результата я не получил. Не уверен что ntohs(cd->id) виновато, потому что в операторе if оно отрабатывает. Да, не все гладко. При долгосрочном тесте почему-то программа виснет. Сейчас проверяю. вот то, что у меня получилось:
|
| Автор: feodorv 27.11.2012, 12:07 | ||||||||
Так в том-то и дело, что не должен закрываться сокет после каждого url. Соединение действует постоянно. Судя по всему, программа виснет, когда циска закрывает соединение (по каким причинам - тоже выяснить бы нужно, возможно, прочла из сокета некорректный ответ). В этом случае recv возвращает 0. Проверки на этот случай в программе нет, а она нужна:
Кроме того, в случае возврата циске положительного решения подпрограмма process_socket явным образом не возвращает никакого значения (а, следовательно, возвращает случайный мусор, хотя обязана вернуть 1 или 0):
И довольно странный подход к указателю cd. Почему (*cd).id, а не cd->id? Добавлено через 3 минуты и 54 секунды Соответственно
|
| Автор: MnxVol 28.11.2012, 08:44 |
| Изменил то, на что вы указали - работает, спасибо! За 16 часов не вылетела ни разу, постоянно обрабатывает запросы. Подскажите еще, с помощью чего лучше реализовать регулярные выражения на С, чтобы разобрать url и сравнить с файлом, содержащим маски запрещенных ресурсов, и еще, есть ли смысл какие-то функции перенести на 64-битную ось. Я например не нашел 64 битного аналого функции recv, зато есть 64 битная функция open, что в данном случае будет лучше? |
| Автор: feodorv 29.11.2012, 03:35 | ||
Очень хорошо! А его и нету))) Если Вы задумались над 64-битной версией, то работа с сетью останется прежней, но в Вашем коде прежде всего нужно обратить внимание на структуру cisdata и pragma pack. Вообще структура этой структуры мне не нравится, ужасное выравнивание, в 64-битном окружении стоит переписать работу с ней.
Ну, для этого вопроса лучше всего открыть новую тему |
| Автор: MnxVol 30.11.2012, 08:00 | ||
| Еще маленький вопрос, все конечно работает, но сегодня заметил - не поступало запросов в течении часа - и программа не захотела закрывать сокет, весь час ждала запросов на этом сокете. Может как-то реализовать выход по таймауту? Я сегодня буду пытаться прикрутить многозадачность, а в таком случае это будет лишь бесконечное множество процессов)) вот так примерно пока у меня в голове многозадачность выглядит, пока правда не проверял, сегодня займусь.
|
| Автор: feodorv 30.11.2012, 19:41 | ||
И? Дальше-то работала? Это нормальное поведение сервера. Не вижу причин для закрытия сокета. Зачем? Где-то так, только это самоубийство))) Не нужен waitpid: http://www.rsdn.ru/article/unix/sockets.xml |
| Автор: MnxVol 3.12.2012, 10:24 |
| Дальше работала, но у меня может быть до 1000, а может и больше, одновременных подключений, получается каждое подключение будет висеть незакрытым, рано или поздно закончатся файловые дескрипторы в системе. |
| Автор: feodorv 3.12.2012, 10:48 | ||||||||
Честно говоря, моё терпение начинает подходить к концу))))
Почему Вы зависли на идее, что для каждого запроса нужно открывать новый сокет? Если так действовать, то, да, рано или поздно закончатся файловые дескрипторы. Посмотрите как следует код, в каких случаях происходит переоткрытие сокета, в каких случаях используется (многократно) уже созданный и уже присоединённый сокет nsd. |
| Автор: MnxVol 4.12.2012, 08:37 |
| Извиняюсь, не понял Вас изначально до конца) А тогда если делать fork, то как я понимаю, процессы будут распараллеливаться, там тоже будет все нормально? Я как понял, по идее, в дочерней копии процесса происходит обработка, и если обработка завершена, процесс закроется, и управление вернется родительскому процессу, так? |
| Автор: feodorv 4.12.2012, 10:28 |
Будет))) Только не нужно делать так: Родительский процесс должен работать вне зависимости от дочернего |