Каков идиоматический способ программирования асинхронных сокетов в Delphi?

Как обычно люди, пишущие сетевой код в Delphi, используют асинхронный ввод-вывод с перекрывающимися сокетами в стиле Windows?

Вот мое предварительное исследование этого вопроса:

Компоненты Indy кажутся полностью синхронными. С другой стороны, хотя модуль ScktComp использует WSAAsyncSelect, он в основном только асинхронизирует мультиплексированное приложение сокета в стиле BSD. Вы получаете сброс в обратном вызове одного события, как если бы вы только что вернулись из select() в цикле, и должны сами выполнять всю навигацию по конечному автомату.

Ситуация с .NET значительно лучше, с Socket.BeginRead / Socket.EndRead, где продолжение передается непосредственно в Socket.BeginRead, и именно здесь вы можете вернуться. Продолжение, закодированное как замыкание, очевидно, имеет весь необходимый вам контекст и даже больше.


person Barry Kelly    schedule 31.08.2008    source источник


Ответы (10)


Для асинхронных вещей попробуйте ICS

http://www.overbyte.be/frame_index.html?redirTo=/products/ics.html

person ddowns    schedule 10.09.2008

Я обнаружил, что Indy, несмотря на более простую концепцию в начале, неудобен в управлении из-за необходимости уничтожать сокеты для освобождения потоков при завершении работы приложения. Кроме того, у меня перестала работать библиотека Indy после обновления патча ОС. ScktComp хорошо работает для моего приложения.

person Mike    schedule 01.09.2008
comment
Если вам приходится убивать сокеты вручную, значит, вы используете его неправильно. Серверы Indy обрабатывают все это автоматически. Я должен знать - я в команде разработчиков Indy. Что касается обновления ОС, что именно перестало работать? - person Remy Lebeau; 17.06.2009
comment
Чтобы уточнить, это Indy 9, а не Indy 10, что может объяснить необходимость вручную закрывать сокеты при выходе. Исправление безопасности ОС привело к тому, что вызовы Write() блокировались и не возвращались (между XP SP2 и 3). Приложение проработало 2 года и вышло из строя после патча безопасности. Думая, что это аппаратное обеспечение, группа развертывания перенесла приложение на второй сервер с тем же результатом. Поскольку это был производственный сервер, мы попробовали заменить интерфейс ScktComp, и все заработало. У меня не было времени использовать отладчик ядра, чтобы выяснить, что вызвало блокировку вызова. - person Mike; 17.06.2009

@Roddy - Синхронные сокеты - это не то, что мне нужно. Сжигание целого потока ради, возможно, долгоживущего соединения означает, что вы ограничиваете количество одновременных соединений количеством потоков, которое может содержать ваш процесс. Поскольку потоки используют много ресурсов — зарезервированное адресное пространство стека, выделенную память стека и переходы ядра для переключения контекста — они не масштабируются, когда вам нужно поддерживать сотни соединений, а тем более тысячи или более.

person Barry Kelly    schedule 01.09.2008
comment
С другой стороны, неблокирующие (асинхронные) сокеты включают очередь сообщений Windows, которая, вероятно, также будет узким местом, если речь идет о сотнях или тысячах подключений. + Я нашел эту статью от марта 2011 года очень интересной: в десять увеличение пропускной способности сервера за счет асинхронной обработки Servlet 3.0 - person mjn; 06.01.2012

Как обычно люди, пишущие сетевой код в Delphi, используют асинхронный ввод-вывод с перекрывающимися сокетами в стиле Windows?

Что ж, Indy уже давно является «стандартной» библиотекой для ввода-вывода сокетов, и она основана на блокировке сокетов. Это означает, что если вы хотите асинхронного поведения, вы используете дополнительные потоки для подключения/чтения/записи данных. На мой взгляд, это на самом деле большое преимущество, поскольку нет необходимости управлять какой-либо навигацией по конечному автомату или беспокоиться о процедурах обратного вызова или подобных вещах. Я считаю, что логика моего потока «чтения» менее загромождена и гораздо более переносима, чем это позволяют неблокирующие сокеты.

Indy 9 показался нам в основном надежным, быстрым и надежным. Однако переход на Indy 10 для Tiburon вызывает у меня небольшое беспокойство.

@Mike: «... необходимость убивать сокеты, чтобы освободить потоки ...».

Это заставило "а?" пока я не вспомнил, что наша библиотека потоков использует метод, основанный на исключениях, для безопасного уничтожения «ожидающих» потоков. Мы вызываем QueueUserAPC, чтобы поставить в очередь функцию, которая вызывает исключение C++ (НЕ производное от класса Exception), которое должно быть перехвачено только нашей процедурой-оболочкой потока. Все деструкторы вызываются, поэтому все потоки завершаются чисто и убираются на выходе.

person Roddy    schedule 01.09.2008
comment
Именно на этапе нестабильности API от Indy9 до Indy10 я начал отходить от этапа Indy. Сейчас пользуюсь только ICS и очень доволен. Это было не из-за синхронизации/асинхронности, а из-за всех глюков (особенно зависаний при завершении работы приложения) и всего IdAntifreeze, из-за которого мне надоел Indy. - person Warren P; 18.02.2010
comment
+1 за упоминание улучшенной ясности кода с блокирующими сокетами - person mjn; 06.01.2012

«Синхронные сокеты — это не то, что мне нужно».

Понятно, но я думаю, что в этом случае ответ на ваш первоначальный вопрос заключается в том, что просто не существует идиомы Delphi для ввода-вывода с асинхронным сокетом, потому что на самом деле это узкоспециализированное и необычное требование.

В качестве побочного вопроса, вы можете найти эти ссылки интересными. Они оба немного устарели и больше *nxy, чем Windows. Второй подразумевает, что в правильной среде потоки могут быть не такими плохими, как вы думаете.

Проблема C10K

Почему события — плохая идея (для серверов с высокой степенью параллелизма)

person Roddy    schedule 02.09.2008

@Chris Miller - то, что вы сказали в своем ответе, фактически неверно.

Асинхронизация в стиле сообщений Windows, доступная через WSAAsyncSelect, действительно в значительной степени является обходным путем из-за отсутствия надлежащей модели многопоточности в дни Win 3.x.

Однако .NET Begin/End не использует дополнительные потоки. Вместо этого он использует перекрывающийся ввод-вывод, используя дополнительный аргумент в WSASend / WSARecv, в частности, перекрывающуюся процедуру завершения, чтобы указать продолжение.

Это означает, что стиль .NET использует поддержку асинхронного ввода-вывода ОС Windows, чтобы предотвратить сжигание потока путем блокировки сокета.

Поскольку потоки, как правило, обходятся дорого (если только вы не укажете очень маленький размер стека для CreateThread), блокировка потоков на сокетах не позволит вам масштабироваться до 10 000 одновременных подключений.

Вот почему важно использовать асинхронный ввод-вывод, если вы хотите масштабироваться, а также почему .NET не, я повторяю, не просто «использует потоки». , [...] просто управляемый Framework».

person Barry Kelly    schedule 04.09.2008
comment
И учитывая, что INDY — это набор компонентов, которому я не доверяю, а ICS — это сторонний набор компонентов, который является асинхронным без поддержки IOCP, я думаю, что Embarcadero должна взять на себя ответственность и создать надлежащий сетевой уровень async + iocp. Тот факт, что INDY является слоем под нынешним сетевым уровнем корпоративной базы данных VCL, просто ужасен, и это причина, по которой я не буду касаться этой шатающейся груды вещей десятифутовым шестом. - person Warren P; 18.02.2010

@Roddy - я уже читал ссылки, на которые вы указываете, на обе они ссылаются из презентации Пола Тима «Тысячи потоков и блокирующий ввод-вывод - старый способ написания серверов Java снова стал новым».

Однако некоторые вещи, которые не обязательно бросаются в глаза из презентации Пола, заключаются в том, что он указал -Xss:48k для JVM при запуске и что он предполагает, что реализация NIO JVM эффективна, чтобы она была действительной. сравнение.

Indy не указывает столь же уменьшенный и строго ограниченный размер стека. В кодовой базе Indy нет вызовов BeginThread (процедура создания потока Delphi RTL, которую следует использовать в таких ситуациях) или CreateThread (необработанный вызов WinAPI).

Размер стека по умолчанию хранится в PE, а для компилятора Delphi он по умолчанию равен 1 МБ зарезервированного адресного пространства (пространство выделяется ОС страница за страницей фрагментами по 4 КБ; фактически компилятору необходимо генерировать код для обращения к страницам, если в функции содержится >4 КБ локальных переменных, поскольку расширение контролируется ошибками страниц, но только для самой нижней (защитной) страницы в стеке). Это означает, что у вас закончится адресное пространство после максимум 2000 одновременных потоков, обрабатывающих соединения.

Теперь вы можете изменить размер стека по умолчанию в PE с помощью директивы {$M minStackSize [,maxStackSize]}, но это повлияет на все потоки, включая основной поток. Я надеюсь, что вы не будете делать много рекурсии, потому что 48 КБ или (аналогично) - это не так много места.

Теперь, прав ли Пол в отношении неэффективности асинхронного ввода-вывода, в частности, для Windows, я не уверен на 100% - мне нужно измерить это, чтобы быть уверенным. Однако я точно знаю, что аргументы в пользу того, что многопоточное программирование проще, чем асинхронное программирование на основе событий, представляют собой ложную дихотомию.

Асинхронный код не обязательно должен быть основан на событиях; он может быть основан на продолжении, как в .NET, и если вы укажете замыкание в качестве своего продолжения, вы получите состояние, поддерживаемое для вас бесплатно. Кроме того, преобразование из линейного кода в стиле потока в асинхронный код в стиле с передачей продолжения может быть выполнено компилятором механически (преобразование CPS является механическим), поэтому ясность кода также не требуется.

person Barry Kelly    schedule 04.09.2008
comment
Я думаю, вам все равно придется беспокоиться о синхронизации доступа к данным при использовании замыканий, или все коммуникации сериализуются? Как это сочетается с f.x. асинхр. ответы базы данных, предоставляющие данные на большое количество входящих запросов? Использовать пулы рабочих потоков? Вы должны записать это в блог. - person Lars Fosdal; 17.09.2008
comment
Потоки Indy основаны на классе TThread VCL, который не позволяет указывать размер стека. - person Remy Lebeau; 17.06.2009

Существуют бесплатные компоненты сокетов IOCP (порты завершения): http://www.torry.net/authorsmore.php?id=7131 (включая исходный код)

"От Набережных Сергей Н.. Высокопроизводительный сокет-сервер на базе Windows Completion Port и с использованием Windows Socket Extensions. Поддерживается IPv6."

я нашел его, когда искал лучшие компоненты/библиотеку для перестройки моего маленького сервера обмена мгновенными сообщениями. Я еще не пробовал, но на первое впечатление код выглядит хорошо.

person Community    schedule 13.01.2009

Indy использует синхронные сокеты, потому что это более простой способ программирования. Асинхронная блокировка сокетов была добавлена ​​в стек winsock еще во времена Windows 3.x. Windows 3.x не поддерживала потоки, и там вы не могли выполнять ввод-вывод сокетов без потоков. Дополнительную информацию о том, почему Indy использует модель блокировки, см. в этой статье.

Вызовы .NET Socket.BeginRead/EndRead используют потоки, они просто управляются платформой, а не вами.

@Roddy, Indy 10 был связан с Delphi с Delphi 2006. Я обнаружил, что переход с Indy 9 на Indy 10 является простой задачей.

person Chris Miller    schedule 03.09.2008

С классами ScktComp вам нужно использовать сервер ThreadBlocking, а не тип сервера NonBlocking. Используйте событие OnGetThread, чтобы передать параметр ClientSocket новому потоку вашей разработки. Создав экземпляр унаследованного экземпляра TServerClientThread, вы создадите экземпляр TWinSocketStream (внутри потока), который можно использовать для чтения и записи в сокет. Этот метод избавляет вас от попыток обрабатывать данные в обработчике событий. Эти потоки могут существовать только в течение короткого периода времени, необходимого для чтения или записи, или зависать на время с целью повторного использования.

Тема написания сервера сокетов довольно обширна. Существует множество техник и практик, которые вы можете применить. Метод чтения и записи в тот же сокет, что и в TServerClientThread, прост и подходит для простых приложений. Если вам нужна модель для высокой доступности и высокой параллелизма, вам нужно изучить шаблоны, такие как шаблон Proactor.

Удачи!

person Jody Dawkins    schedule 11.09.2008