![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Логика работы сокетов [D7, Win95/98, WinME, NT4, Win2k, WinXP]
_Sergey_ © (2006-09-05 22:57:00)Здравствуйте.
Недавно занялся сетевым программированием с использованием библиотеки WinSock (без применения каких-либо компонентов). Основная задача: написать сервер, который допускал бы подключение только одного клиента. Использую блокирующие сокеты, 1 поток и ф-ию select перед recv для осуществления отключения клиента от сервера по тайм-ауту передачи, клиент и сервер - консольные приложения. При этом возникли некоторые, как я полагаю, логические противоречия в работе сокетов.
1. перевожу сервер в режим ожидания подключения ф-ией listen, жду подключения от клиента, после подключения вызываю ф-ию accept. На этом этапе уже существуют 3 сокета:
1) клиентский
2) связанный с ним серверный
3) серверный в режиме ожидания подключения
Вызываю accept для 1-го клиента. После этого получаю данные от 1-го клиента. Но при этом подключаюсь к серверу 2-ым клиентом. Ф-ия connect 2-го клиента завершается без ошибки (2-й клиент добавляется в очередь на подключение). Самым "обидным" является то, что 2-ой клиент продолжает передавать данные, при этом ошибки send у него не появляется, т.е., он передает данные, которые уходят "в никуда". Вопрос: почему send отсылает данные без возникновения ошибок?
2. В обход сложившейся ситуации поступаю следующим образом: после подключения 1-го клиента вызываю CloseSocket для слушающего сокета сервера. При этом 1-й клиент передает данные без проблем (так и должно быть). Но возникает другая проблема: у 2-го клиента удачно завершается ф-ия connect (к какому слушающему сокету он подключается?). Но при первой же попытке передачи данных серверу 2-м клиентом возникает ошибка send. После этой ошибки клиент уничтожает свой сокет и переподключается. Сервер же после отключения 1-го клиента создает заново слушающий сокет и ожидает подключения. Вопрос: почему ф-ия connect 2-го клиента не завершается по ошибке (слушающий сокет на стороне сервера разрушен же)?
Изучал RFC793, RFC791, RFC768. Ситуация не прояснилась. Есть предположение, что для UDP и TCP-сокетов создаются одни и те же программные структуры (сокеты). Кроме того, есть предположение, возникшее после прочтения спецификаций, что слушающий сокет отличается от обычного только установкой спецфлага, т.е., имеет вх. и вых. буферы, и обмен информацией даже со слушающим сокетом не запрещен. И так, как в UDP реализуется передача данных до установления соединения, то реализуется ситуация 1 (данные записываются во вх. буфер слушающего сокета, ошибка не возникает). 2-я ситуация: ф-ия connect проверяет только наличие сокета, но не проверяет, слушающий он или нет. Поправьте меня, если я ошибаюсь.
Я задавал подобный вопрос на одном из форумов: http://www.delphikingdom.com/asp/answer.asp?IDAnswer=44294
Ответа на свой вопрос я, к сожалению, не получил. Буду рад любой информации: ссылки на документацию, адреса специализированных форумов, на кот. я могу получить ответы на мои вопросы.
Сергей М. (2006-09-11 08:47:00) [41]
> _Sergey_ © (09.09.06 00:16) [16]
Ну и понесло же тебя в дебри)
Нагородил ты себе, понимаешь, огромную кучу несуществующих проблем и теперь героически пытаешься разгрести эту кучу, хватаясь то за одно то за другое)
Зачем тебе нужна последовательная обработка кл.запросов на сервере, я так и не понял.. При грамотной реализации сервера он будет корректно и вовремяобрабатывать одновременныезапросы сотен и тысяч столь же грамотно реализованных клиентов, и никакие пакеты никуда исчезать не будут при этом.
Eraser (2006-09-11 01:45:00) [42]> [43] _Sergey_ © (10.09.06 23:59)
> А вот как защититься от DOS-атак?
если за опр. промежуток времени произошло опр. количесвто неудачных соединений с конкретного IP - заносить этот IP в черный список.. на опр. промежуток времени :)
хотя если нужна настоящая защита - при каждом соединений необходимо производить сначала сеанс аутентификации, потом сеанс авторизации и уже по защищенному каналу обмениваться данными. Либо производить аутентификацию/авторизацию при первой попытке связи клиента с сервером, а в дальнейшем уже использовать ранее сгенерированный ключ для шифрования данных. Второй вариант в вашем случае предпочтителен, т.к. генерация ключей с надежной криптоустойчиваостью - процесс небыстрый.. несколько секунд тратится.
> осталась проблемка: с включенным HT при стресс-тесте иногда
> клиенты вылетают с сообщением об ошибке обращения к памяти,
> адреса все время разные.
возможно в данном случае поможет ф-ия SetThreadAffinityMask, которая позволяет "привязать" поток к определенному процессору.. не знаю как оно будет работать с HT.. думаю нормально )
PS попробуйте использовать какой-нибудь готовый сервер, например TIdTCPServer и сравните скорость работы с вашей реализацией, возможно не стОит усложнять себе жизнь изобретая велосипеды, хотя может быть тут я не прав.
_Sergey_ (2006-09-10 23:59:00) [43]Со списком "черных" IP все понятно - SO_CONDITIONAL_ACCEPT, WSAAccept(), в процедуре - CF_REJECT. Контроль от переполнения буффера, как я понял - StrLCat (используется для "склейки" приходящих пакетов). А вот как защититься от DOS-атак? Шифровать пакеты, дак это не то. Может, занесение в черный список при оч. частых коннектах с определенного адреса, или большом числе "неправильных" пакетов с этого адреса. Было бы полезно знать. А сейчас реализовал предложенную логику, но только по более простой схеме (пока пренебрег безопасностью). Логика такова:
1) accept-им клиента
2) сразу же высылаем ему пакет с подтверждением об разрешении отправки данных
3) если клиенту не приходит данное разрешение за тайм-аут сек., то он уничтожает сокет, создает его заново и пытается законнектиться по тайм-ауту. Иначе (разрешение пришло) он отсылает данные.
4) Сервер принимает данные по тайм-ауту. Как только сервер приймет маркер окончания пакета данных, он высылает клиенту пакет с подтверждением об успешной доставке данных. При ошибке send подтверждения обработка пакета отменяется (защита от дублирования пакетов). Сервер закрывает сокет клиента и вызывает процедуру обработки принятого пакета. После чего снова переходит на Accept.
5) клиент принимает тайм-аут сек. пакет с подтверждением. Если он не получит этот пакет, то уничтожит сокет, создаст его заново, подключится и снова будет пытаться послать старый пакет через тайм-аут сек. Иначе (получил подтверждение) клиент закрывает сокет и вызывает процедуру формирования нового пакета. И снова повторяет цикл.
Отключил алгоритм Нагеля на стороне клиента. На стороне сервера после каждого accept отключаю этот алгоритм для сокета, связанного с клиентским.
Размер очереди в Listen заменил с максимальной до 1.
Кроме того, кажется нашел ответ на свой 2-ой вопрос, заданный в [0] ;)
Как показал эксперимент, слушающий сокет закрывается не сразу, поэтому к нему еще можно подконнектиться. Кажется так.
Нашел опасную описку. "Зависание" некот. клиентов, описанное выше, оказывается, было вызвано тем, что я отсылал пакет 0-го размера для проверки занятости сервера, используя конструкцию вида:
while send(...) = SOCKET_ERROR do goto reconnect;
На самом деле нужно было
if send(...) = SOCKET_ERROR then goto reconnect;
Кроме того, WSAAccept вклинивается в станд. обмен запрсами и ответами на транспортном уровне. И может создавать, как мне кажется, разл. нестандартные тайм-ауты и ситуации. Хотя, может быть, я здесь и ошибаюсь.
Результаты:
1) скорость работы 15 клиентов (тайм-ауты на переконнект - 2 сек., время формирования 1 пакета - меньше 0,5 сек) и 1 сервера, тестируемые на 1 машине через loopback, субъективно увеличилось где-то в 2 раза по сравнению с предыд. реализацией (стресс-тест, в реальности в таком режиме сервер работать не будет). [алгоритм Нагеля]
2) При анализе логов после стресс-теста обнаружилось почти полное отсутствие сообщений об занятости сервера [алгоритм Нагеля]
3) Исчезли те самые 2000 потерь пакетов на 20000 передач, описываемые выше. Раньше я просто исправлял эту ситуацию, перепередавая пакеты. Сейчас этого делать не нужно. [обратная связь + ликвидация_описки]
4) на машине W2k Pro SP4 с интегр. сетевой картой ранее были такие проблемы: после ~ 30 мин. работы винда вылетала с синим экраном, самопроизвольно перезагружаясь (автоперезагрузка при сбое отключена).
Причину по коду ошибки выяснить не удалось. При конфигурации: P4 3.0 HT, WinXP SP1, интегр. сет. такого не было. При текущей реализации сервер проработал на 1-ой машинке 5 часов без синих экранов. [слушающий сокет не создается/уничтожается с оч. большой частотой]
5) осталась проблемка: с включенным HT при стресс-тесте иногда клиенты вылетают с сообщением об ошибке обращения к памяти, адреса все время разные. При выключенном HT такая проблема исчезает. Вероятная причина: винда или ее библиотеки (включая сетевые) не всегда правильно работает на многопроцессорной машинке (процессоры хоть и виртуальные, но их все же формально 2 шт.). В старой реализации такая прблема проявлялась раза в 4 чаще. Сейчас - реже. Считаю это не проблемой моего приложения.
Т.е., улучшения налицо. Еще раз спасибо за подсказку. Оказалось, я искал решения на транспортном уровне. А надо было, как делают все протоколы, защищаться обратной связью с отправкой пакетов со служебными сообщениями.
Eraser (2006-09-10 15:47:00) [44]> [41] _Sergey_ © (10.09.06 00:35)
> зафлудить будет тяжелее, т.к. если клиентом будет посылаться
> непрерывный поток данных, то вероятность встретить там исходную
> 7-байтную комбинацию оч. мала, а, если сервер получает не
> ее, можно сразу же серверу закрыть сокет
от флуда и других злоумышленных атак данный метод не пойдет. если нужна настоящая защита - должен быть реализован настоящий анти-флуд, со списком "черных" IP, контролем от переполнения буффера, защитой от DOS-атак и т.п.
_Sergey_ (2006-09-10 00:35:00) [45]Конечно, сгодится. Но можно не 1 байт, а 7 или больше, для надежности (зафлудить будет тяжелее, т.к. если клиентом будет посылаться непрерывный поток данных, то вероятность встретить там исходную 7-байтную комбинацию оч. мала, а, если сервер получает не ее, можно сразу же серверу закрыть сокет).
И критическая секция не понадобится. Accept вызовется только после окончания обработки данных от текущего клиента. Всего у меня 1 поток. А до этого момента будет существовать только 1 канал связи.
Eraser (2006-09-10 00:05:00) [46]> [38] _Sergey_ © (09.09.06 23:58)
> Хотя, в такой реализации сервер будет менее защищен от "неправильных"
> клиентов, поскольку можно будет просто подключиться, и не
> посылать ни одного байта
это легко обойти такой схемой:
1. Клиент подключается к серверу и отсылает ему 1 байт данных.
2. Сервер пытается считать из сокета 1 байт, если не получается - закрывает сокет, если получается - входит в критическую секцию.
3. Сервер отсылает 1 байт клиенту.
4. Клиент пытается считать 1 байт, если получается - отсылает пакет, если нет - убивает сокет.
5. Сервер принимает пакет и закрывает критическую секцию.
по моему степень надежности данной схемы довольно неплохая, по крайней мере в качестве "защиты от дурака" сгодится.
_Sergey_ (2006-09-10 00:04:00) [47]Да, последний мой пост неверен. При подключении клиента сервер просто сразу же посылает ему сообщение. Потом ждет данных, после прихода маркера окончания пакета данных отсылает подтверждение об успешной отправке и разрывает соединение. По кол-ву таймаутов сервера эти две реализации не отличаются.
_Sergey_ (2006-09-09 23:58:00) [48]Хотя, в такой реализации сервер будет менее защищен от "неправильных" клиентов, поскольку можно будет просто подключиться, и не посылать ни одного байта. Тогда сервер будет занят таймаут мс, после чего отключит такого клиента, но всегда же можно переподключиться. Моя же реализация иребует меньше времени на таких клиентов.
_Sergey_ (2006-09-09 23:55:00) [49]Хорошо. Все понятно, придется где-то пятую часть кода сервера вырезать и переписать ;) Но, думаю, это к лучшему. Более правильная реализация стоит этого.
В любом случае о результатах сообщу в эту ветку. Если все хорошо, недельки через 1,5 максимум.
Удачи.
P.S> Если есть еще какие вопросы относительно мого приложения, с радостью отвечу.
Eraser (2006-09-09 23:48:00) [50]> [35] _Sergey_ © (09.09.06 23:46)
когда то тоже примерно так поступал - делал размер прикладного пакета чуть больше tcp пакета, пока не узнал про TCP_NODELAY ;)
правильнее [34] :)
_Sergey_ (2006-09-09 23:46:00) [51]Понял. Спасибо. Кстати, я устанавливаю для клиентов размер буфера для отправки в 0. Чтобы данные сразу уходили в виде пакетов в сеть.
Eraser (2006-09-09 23:44:00) [52]> [33] _Sergey_ © (09.09.06 23:40)
в догонку:
при малых объемах информации и частой смене направления передачи данных, как в данном случае, и если критична скорость реагирования, можно отключить алгоритм Нагеля, для сокетов.
см. setsockopt + TCP_NODELAY.
_Sergey_ (2006-09-09 23:40:00) [53]Хорошо. Это тоже вариант, причем более корректный с точки зрения работы с сокетами. Спасибо. Буду думать, тестировать ...
Eraser (2006-09-09 23:34:00) [54]> [30] _Sergey_ © (09.09.06 23:33)
> Ваш совет, как я понял, таков: при существующей обратной
> связи с подтверждением об успешной отправке ввести обратную
> связь перед отправкой, свидетельствующей, что соединение
> с сервером установлено, и он готов получать пакет.
именно!
Eraser (2006-09-09 23:34:00) [55]PS чтобы данные не уходили "в никуда" самый простой и применяемый способ проверки "живости" сервера - считать с него 1 байт данных, это вполне нормальный способ.
_Sergey_ (2006-09-09 23:33:00) [56]Ваш совет, как я понял, таков: при существующей обратной связи с подтверждением об успешной отправке ввести обратную связь перед отправкой, свидетельствующей, что соединение с сервером установлено, и он готов получать пакет.
Нет. Не на сервере формируется пакет. Это клиенты формируют пакеты длительное время. А сервер эти пакеты только получает, и быстренько их обрабатывает, после чего опять создает слушающий сокет (т.е., снова готов к работе).
Eraser (2006-09-09 23:28:00) [57]> [28] _Sergey_ © (09.09.06 23:23)
> Критическая секция не защитит от записи пакета от какого-
> то клиента в буфер сокета, причем, получается, что именно
> слушающего.
стоп-стоп! так и пусть пишет в этот буфер! при чем тут уже соединенный клиент? для него задействован уже совершенно другой сокет.
еще одно. когда на сервере формируется пакет для определенного клиента (как я понял это довольно длительный процесс) могут ли другие клиенты получать свои пакеты?
_Sergey_ (2006-09-09 23:23:00) [58]1 раз в 15 мин. (может, больше 15 мин.) при формировании пакета
через каждые 2 сек при занятости сервера
Критическая секция не защитит от записи пакета от какого-то клиента в буфер сокета, причем, получается, что именно слушающего. И на стороне сервера я эту ситуацию никак не отслежу. Выход: придется высылать клиентам пакет с разрешением на передачу. И только после получения разрешения клиент должен передавать пакет. Но, как мне кажется, введение еще 1 обратной связи - это не оч. красивый выход из этой ситуации.
Eraser (2006-09-09 23:21:00) [59]> [25] _Sergey_ © (09.09.06 23:18)
> частота устанавливается не меньше 15 мин./клиент.
понятно, тоже очень мало.
> Но это не защитит от потери пакетов.
если уж так критична защита от неудачных соединений (думаю в нашем контексе это уместнее называть так, чем "потеря пакетов"), то нужно предусмотреть защиту против этого на прикладном уровне, но никак не на транспортном!
Eraser (2006-09-09 23:18:00) [60]> [24] _Sergey_ © (09.09.06 23:14)
не.. меня немного другое интересут. Какова интенсивность подключения клиентов к серверу, т.е. сколько раз в минуту, в среднем, один клиент может подсоединится к серверу в штатном режиме.
почему не устраивает вариант с простой защитой кода от доступа из нескольких потоков? imho в данном случае - самое оно, если уж отказались от многопоточной архитектуры..