Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Логика работы сокетов [D7, Win95/98, WinME, NT4, Win2k, WinXP]


Страницы:   1 2 3 4 5

_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
Ответа на свой вопрос я, к сожалению, не получил. Буду рад любой информации: ссылки на документацию, адреса специализированных форумов, на кот. я могу получить ответы на мои вопросы.


_Sergey_   (2006-09-09 23:18:00) [61]

Частота зависит от процедуры формирования пакета. Но, чтобы клиент не занимался все время передачей пакетов, а больше времени тратил на его формирование, частота устанавливается не меньше 15 мин./клиент.

Критической секцией можно защитить. Но это не защитит от потери пакетов. А их терять нельзя, или нежелательно.


_Sergey_   (2006-09-09 23:14:00) [62]

Сервер отсылает каждому клиенту клиенту только пакет с подтверждением об отправке (если успешна). Размер - 7 байт.


Eraser   (2006-09-09 23:13:00) [63]

> [22] _Sergey_ ©   (09.09.06 23:10)

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

PS как часто клиенты "обращаются" (устанавливают соединение) с сервером?


_Sergey_   (2006-09-09 23:10:00) [64]

В одновременной работе планируется:
1 ПК: сервер и клиент
+ около 10 ПК (на каждый - по клиенту) [локальная сеть небольших размеров]

Клиенты асинхронно, но довольно долго формируют пакеты. Размер 1-го пакета:
до 16 Кб максимум. Сейчас - 256 байт.


Eraser   (2006-09-09 21:13:00) [65]

> [20] _Sergey_ ©   (09.09.06 14:41)

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


_Sergey_   (2006-09-09 14:41:00) [66]


> imho нужно менять логику работы программы, особенно убрать
> вот это

Я считаю, что это как раз не нужно менять. Причины:
1) У сервера есть некот. полезная нагрузка по обработке приходящих пакетов. Нужно, чтобы в каждый момент времени только 1 пакет обрабатывался. Если я создам сервер, рассчитанный на подключение многих клиентов, то придется каким-то образом переводить подключенных клиентов в состояние ожидания, а затем высылать с сервера им спецпакеты на разрешение передачи. А это уже нехорошо, т.к. при длительной работе и некот. сбоях возможны незакрытые сокеты, зависшие соединения, и т.п. (теоретически). Сейчас же я контролирую только 1 соединение, и слежу только за его работой. И все. Больше 2 открытых сокетов мое приложение не создает. Другой способ выйти из этой ситуации: если сервер занят обработкой пришедшего пакета, можно не ожидать окончания его обработки, а накапливать все пришедшие пакеты в каком-то буфере или файле. А затем, при освобождении сервера, постепенно обрабатывать этот файл. Но это тоже не годится. Сервер должен успевать обрабатывать вх. информацию, а не просто сбрасывать ее и откладывать ее обработку на потом. Иначе теряется логика работы моего приложения.
2) Для того, чтобы отслеживать ситуацию, когда еще не создан связанный с клиентским серверный сокет, придется вводить передачу дополнительных служебных пакетов. В текущей реализации этого нет.
3) Время приема пакетов сервером некритично. Если сервер не может сейчас принять пакет, то клиент переконнекчивается через тайм-аут секунд. При этом можно быть всегда уверенным, что, если сервер не может сейчас обслужить клиента, значит, он сейчас принимает или обрабатывает данные от очередного клиента, т.е., занят полезным "делом". И в этой реализации сервер всегда будет успевать обрабатывать данные от клиентов. Если клиентов будет оч. много, то все они будут пытаться отослать информацию по тайм-ауту. Это создаст дополнительную нагрузку на сеть, но в условиях хорошего канала связи или локальной сети это несущественно.
4) Пытался написать попроще. Всего 1 клиент. Думал, по логике вещей, это должно быть реализовано. Ведь, если не отлажена нормально связь с 1 клиентом, то как можно делать многоклиентский сервер - все каналы связи, кот. он откроет, могут глючить так, как N одноклиентских серверов, а может, еще и больше. Оказалось, библиотека сокетов ориентирована на создание многоклиентских серверов, событийно-ориентированных на неблокирующих сокетах. Мне это не подходит. Нужна стабильная работа 1 канала связи. Вот и все. Неужели я так много захотел? А, между прочим, пришлось около 2 нед. создавать этоу логику, кот. реализована сейчас. И она работает. Но неужели нужно было так сложно обходить все эти проблемы? Вот у меня и возникли вопросы: может, я что-то не так делаю; может, можно проще и надежнее; может, это неправильный способ, или дело в том, что сама реализация логики в библиотеке сокетов не позволяет по-другому достичь желаемого результата; или я чего-то недопонял?

Насчет стресс-теста. Приложение в таком режиме работать не будет. Но, я как программист, должен был удостовериться в его надежной работе. И тайм-ауты у меня в реальном (а не тестовом) варианте составляют 2 секунды. Думаю, это не так мало. Причем, обсуждаемые проблемы проявляются по-одиночке за 5-7 часов работы сервера. Но проявляются. Хотя, с помощью описанных хитростей и обратной связи мне удалось свести потери на нет.


Eraser   (2006-09-09 01:57:00) [67]

> [18] _Sergey_ ©   (09.09.06 01:46)


> При том, что 15 клиентов и 1 сервер с тайм-аутами 20-30
> мс на переподключение

для современных локальных сетей и тем более Интернета, какие-то нереальные цифры вы хотите выжать...
imho нужно менять логику работы программы, особенно убрать вот это

> написать сервер, который допускал бы подключение только
> одного клиента


_Sergey_   (2006-09-09 01:46:00) [68]


> при чем тут софтайс??? в Делфи есть приличный встроенный
> отладчик..

При том, что 15 клиентов и 1 сервер с тайм-аутами 20-30 мс на переподключение ни на каком отладчике не отладишь. Тем более на встроенном, т.к. не знаешь, для какого из этих 15 клиентов не вернется управление из ф-ии библиотеки сокетов. А только при таком тестировании обнаруживается описываемая ситуация. Т.н. стресс-тест.


Eraser   (2006-09-09 00:37:00) [69]

> [16] _Sergey_ ©   (09.09.06 00:16)

при чем тут софтайс??? в Делфи есть приличный встроенный отладчик..


_Sergey_   (2006-09-09 00:16:00) [70]


> Если подтверждение не поступило в заданный период прикладного
> таймайта ожидания, и при этом канал связи якобы активен,
>  значит либо противоположная сторона соединения чем-то занята
> (что ненормально с т.з. оговоренной прикладной логики) либо
> произошел разрыв канала связи по причинам, не зависящим
> от партнера по соединению.
> Для отслеживания таких ситуаций как правило задействуется
> т.н. "KeepAlive"-механизм.

Keep-Alive задействовать в моем случае нерационально, т.к. мой сервер предназначен для получения коротких сообщений, которые могут поступать с разным интервалом времени от многих клиентов. Клиенты при занятости сервера ожидают окончания обслуживания ним текущего подключенного клиента. Клиенты - приложения, работающие в автоматическом режиме, не ожидающие ввода команд пользователя при работе с сервером. После окончания формирования информации, подлежащей отправке на сервер, клиент пытается ее отправить. При этом пакет небольшой, времени такая пересылка занимает оч. мало, после чего (в случае успешной передачи) клиент разрывает соединение.

> в иных же случаях механизм реализуется на прикладном уровне.

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


> Если в момент отправки пакета произошла искл.ситуация на уровне
> Winsock-функции send(), ее следует интерпретировать как разрыв канала
> связи по причинам, не зависящим от партнера, не ждать более никаких
> ответов от партнера (ибо канала связи уже не существует) и закрывать
> гнездо со своей стороны.

Я так и делаю. От сервера не пришло подтверждение - уничтожаю сокет и повторяю процедуру подключения.


> Это нормально для прикл.протоколов на базе UDP, но совершенно не
> нормально для TCP-базы.

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


Для этого существует отладчик.

SoftIce-ом пользоваться так и не научился. Раньше отлаживал разные ассемблерные программки под DOS досовскими отладчиками. Так что опыт есть. Хоть и не подхлдящий для этой задачи, но принципы я прочувствовал еще тогда. Могу сказать следующее: только при интенсивном тестировании возникают такие "глюки". При нагрузке 15 клиентов + 1 сервер, 20-30 мс - тайм-аут на реконнект у клиентов, при этом через 15-20 мин. у 4-5 клиентов наступала такая ситуация. По моим представлениям такие "глюки" выловить отладчиком практически невозможно.

P.S> Спасибо за ответ. Все начинает проясняться. Но остался еще вопрос, заданный в самом начале обсуждения:
1-й клиент подключен, уничтожаю слушающий сокет, почему ф-ия connect 2-го клиента при этом не завершается по ошибке (слушающий сокет на стороне сервера разрушен же)?


Сергей М.   (2006-09-08 11:31:00) [71]


> Точно сказать не могу, на вызове какой ф-ии некоторые клиенты
> оказывались в "зависшем" состоянии


Для этого существует отладчик.


Сергей М.   (2006-09-08 11:30:00) [72]


> Если подтверждение не пришло, то пакет заново перепередается


Это нормально для прикл.протоколов на базе UDP, но совершенно не нормально для TCP-базы.

Если подтверждение не поступило в заданный период прикладного таймайта ожидания, и при этом канал связи якобы активен, значит либо противоположная сторона соединения чем-то занята (что ненормально с т.з. оговоренной прикладной логики) либо произошел разрыв канала связи по причинам, не зависящим от партнера по соединению.
Для отслеживания таких ситуаций как правило задействуется т.н. "KeepAlive"-механизм. Суть его проста - периодически "прощупывать" виртуальное соединение на предмет отказа, посылая партнеру по соединению спец.инф.пакеты а-ля "Я живой". Если в момент отправки пакета произошла искл.ситуация на уровне Winsock-функции send(), ее следует интерпретировать как разрыв канала связи по причинам, не зависящим от партнера, не ждать более никаких ответов от партнера (ибо канала связи уже не существует) и закрывать гнездо со своей стороны.
В нек.случаях можно задействовать "KeepAlive"-механизм на внутреннем уровне WSP (см. п.п. 4.2.3.6 RFC 1122 и ф-цию SetSockOpt(SO_KEEPALIVE)), в иных же случаях механизм реализуется на прикладном уровне.


> _Sergey_ ©   (07.09.06 18:21) [12]


Изучи внимательно циклограмму стандартнойпроцедуры установления ТСР-соединения

http://book.itep.ru/4/44/tcp_443.htm

Как видишь, accept() нигде не фигурирует - за прослушивание, прием и подтверждение вх.запросов на соединение отвечает listen-механизм, accept лишь выбирает из очереди (backlog queue) уже подтвержденныхзапросов (если таковые имеются) очередной запрос и создает для него отдельное гнездо, завершая тем самым создание виртуального соединения.
Т.е. акцептирование сервером соединения в этом случае происходит уже на уровне listen-механизма.

В случае же условного акцепта (SO_CONDITIONAL_ACCEPT) listen-механизм ведет себя иначе - в ответ на запрашивающий клиентский SYN-пакет он задерживает отправку  подтверждающего SYN-ACK-пакета до момента вызова WSAAccept(), которая может как подтвердить, так и отложить или отвергнуть запрос на соединение. В случае подтверждения SYN-ACK-пакет немедленно отправляется клиенту, в случае отложения запроса он ставится, если не ошибаюсь, в хвост backlog-очереди, в случае отвержения клиенту отправляется RST-пакет.


> данные куда-то уходят. Причем, без фиксации ошибок


В стандартной схеме пока сервер не выполнил accept, все, что клиент успел послать серверу, сохраняется в соотв.вх.буфере слушающего гнезда (сервер при желании может даже прочитать их с пом. recv), после accept "владельцем" вх.буфера становится тлько что созданное accept'ом гнездо.


_Sergey_   (2006-09-07 18:32:00) [73]


> Ты циклограмму установления TCP-соединения изучал ?

Нет. Но нашел. http://contacts.narod.ru/TCPIP/tcp18.html#t182000
Пока я не понял, как это связано с обсуждаемыми вопросами. Обмен SYN, ACK, FIN - это внутренняя логика. Она жесткая, ее изменить нельзя, пользуясь библиотекой сокетов.


_Sergey_   (2006-09-07 18:21:00) [74]


> Потому что он в этот момент уже успешно "законнекчен".

Странно. Насколько я читал, только при вызове accept создается сокет, связанный с текущим. Для 2-го клиента я accept не вызывал. Значит, получается, и сокета для него на стороне сервера нет. Если нет связанного сокета на стороне сервера, значит, и "законнекченным" он быть не может. А данные куда-то уходят. Причем, без фиксации ошибок.


_Sergey_   (2006-09-07 18:15:00) [75]


> Что за ерунда ?
> Как это связано с транспортным уровнем ?

Связано, как я писал, с ПРИКЛАДНЫМ уровнем. Связано, поскольку при большой нагрузке на сервер возникает ситуация, когда, несмотря на все проверки, сервер все же не получает информацию, а клиент считает, что он ее успешно отправил. Ранее я писал клиент, основывающийся на ф-ии TransmitFile. Впоследствии мне пришлось отказаться от такой реализации.Статистика:
1) 15 клиентов на основе TransmitFile, 1 сервер, запущены локально, тайм-аут у клиента при неудачной передаче - 30 мс. На сервере фиксировалось около 20 000 успешных передач от клиента. При этом у самих клиентов суммарное кол-во успешных отправлений было где-то на 20-30 больше. Вывод: на 20 тыс отправок в жестком тестировании терялось около 20-30 пакетов.
2) 15 клиентов на основе send, те же условия.
На 20 тыс. потери около 2000 - 2500. Разница налицо.

Как в 1-м, так и во 2-м случае потери есть. А нужно, чтоб их не было. Решение: ввел обратную связь. Теперь после каждого полученного "правильного" пакета сервер отсылает клиенту сообщение, кот. клиент принимает, разрывает связь (уничтожает сокет) и формирует след. пакет.
Если подтверждение не пришло, то пакет заново перепередается.

Вывод: пришлось создавать свой простейший протокол.
Результат: кол-во успешно отправленных и успешно принятых пакетов совпадает 1:1.
Вывод: если бы не было указанных странностей в работе сокетов, то не пришлось бы вводить обратную связь. Ошибка фиксировалась бы в момент попытки выполнить сет. ф-ию.


_Sergey_   (2006-09-07 17:59:00) [76]


> Ты о чем ? О "зависании" тех самых "многих" на вызове ими
> ф-ции connect() ?

Точно сказать не могу, на вызове какой ф-ии некоторые клиенты оказывались в "зависшем" состоянии, но логика работы клиента такова:
создать сокет, подключиться, послать нулевой пакет для проверки связи, послать информацию и через select ожидать (принять) подтверждение доставки от сервера (обратная связь). Если возникнет какая-нибудь ошибка, то уничтожить сокет и повторить все со слов "создать сокет". При этом все возникающие ошибки и некот. информация об успешно отправленном пакете записываются в логи. При небольш. кол-ве клиентов никаких проблем не возникает. Но при большом кол-ве клиентов и 1-м сервере, запущенных на локальной машине, возникает ситуация, когда через некот. время клиенты и не пишут в логи, и не обновляют статинфо на консоли, и не производят никаких других указанных действий.


Сергей М.   (2006-09-07 08:41:00) [77]


> Медведъъ   (06.09.06 16:57) [8]
> ступил


Да уж) .. Ерунду сморозил)


Медведъъ   (2006-09-06 16:57:00) [78]

аа ступил, сорри


Медведъъ   (2006-09-06 16:55:00) [79]

while true do
begin
 listen
 accept
 recv
 send
 shutdown
 closesocket
end

больше одного ну никак не подключится, если поток один


Сергей М.   (2006-09-06 16:10:00) [80]


> 2-ой клиент продолжает передавать данные, при этом ошибки
> send у него не появляется, т.е., он передает данные, которые
> уходят "в никуда". Вопрос: почему send отсылает данные без
> возникновения ошибок?


Потому что он в этот момент ужеуспешно "законнекчен".


> в данный момент этот параметр имеет значение SoMaxConn


А должен при любой логикебыть = 1.

p.s.

Ты циклограмму установления TCP-соединения изучал ?


Страницы:   1 2 3 4 5

Наверх

Титульная страница Поиск, карта сайта Написать письмо