А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Перейдем к следующему примеру использования select
— TCP-серверу, который может работать одновременно с неограниченным числом клиентов (пример находится на компакт-диске в папке SelectServer). Этот сервер будет усовершенствованной версией нашего простейшего сервера (см. разд. 2.1.12) и тоже будет консольным приложением (функция select
, как мы видели на примере UDP-чата, позволяет создавать приложения с графическим интерфейсом пользователя, так что реализация сервера в качестве консольного приложения — это не необходимость, а свободный выбор для иллюстрации различных способов применения функции select
).
Разумеется, ни один сервер не может работать с неограниченным числом клиентов. Здесь и далее под словом "неограниченный" подразумевается то, что количество клиентов сервера ограничивается только ресурсами системы, а не самой реализацией сервера.
Инициализация сокета и установка его в режим прослушивания в новом сервере ничем не отличается от простейшего, изменения начинаются только с цикла. Теперь цикл только один (вложенные циклы в нем есть, но они выполняют чисто техническую роль). Начинается цикл с того, что с помощью функции select
определяется готовность к чтению слушающего сокета. Если слушающий сокет готов к чтению, то в данном случае это означает, что есть клиенты, которые уже подключились к серверу, но еще не были обработаны функцией accept
. Если такие клиенты есть, то сервер принимает подключение, причем только одно за одну итерацию цикла. Для каждого подключившегося клиента сервер создает экземпляр записи TConnection
, которая описана в листинге 2.25.
TConnection
// запись TConnection хранит информацию о подключившемся клиенте.
// поле ClientAddr содержит строковое представление адреса клиента.
// Поле ClientSocket содержит сокет, созданный функцией accept
// для взаимодействия с данным клиентом.
// Поле Deleted - служебное. Если оно равно False, значит,
// соединение с данным клиентом по каким-то причинам потеряно,
// и сервер должен освободить ресурсы, выделенные для этого клиента.
PConnection = ^Connection;
TConnection = record
ClientAddr: string;
ClientSocket: TSocket;
Deleted: Boolean;
end;
Поле ClientAddr
хранит строковое представление адреса клиента в виде "X.X.X.X:Port" — это поле используется только при выводе сообщений, связанных с данным клиентом. Поле ClientSocket
содержит сокет, созданный для связи с данным клиентом. Поле Deleted
необходимо для того, чтобы упростить удаление записей для тех клиентов, соединение с которыми уже потеряно. Список соединений хранится в глобальной переменной FConnections
типа TList
. Потеря соединения обнаруживается при попытке чтения или отправки данных через сокет. Если в одном цикле делать и попытки чтения, и удаление ненужных записей, этот цикл усложняется, и в нем легко сделать ошибку в индексах. Чтобы избежать этого, в "читающем" цикле те записи, для которых потеряно соединение, просто помечаются как удаленные с помощью свойства Deleted
. Затем другой цикл удаляет все записи, помеченные для удаления.
После проверки новых подключений начинается проверка получения сообщений от тех клиентов, которые уже подключены. Для этого перебираются сокеты из списка подключений и для каждого вызывается select
. Чтобы повысить производительность, сокеты проверяются не по одному, а группами. Как уже было сказано, множество типа TFDSet
может содержать не более FD_SETSIZE
сокетов, а в нашем списке их может оказаться больше. Приходится разбивать сокеты на группы размером по FD_SETSIZE
и для каждой группы вызывать select
отдельно.
Для тех сокетов, которые готовы к чтению, вызывается процедура ProcessSocketMessage
. Ее код практически полностью совпадает с кодом одной итерации внутреннего цикла примера SimplestServer (см. листинг 2.15), т.е. процедура сначала читает размер строки, затем — саму строку, после этого формирует ответ и отправляет его клиенту. Реализуя эту функцию таким образом, мы пошли на некоторый риск блокировки, потому что функция select информирует только о том, что во входном буфере сокета есть хоть что-то, но вовсе не гарантирует, что там лежит уже все сообщение целиком. Наша же функция реализована таким образом, что она завершается либо после прочтения сообщения целиком, либо после обнаружения ошибки. Тем не менее в простых случаях можно пойти на такой риск, потому что, во-первых, короткие сообщения редко разбиваются на части, а во-вторых, если даже такое произойдет, оставшаяся часть сообщения, скорее всего, догонит первую достаточно быстро, и блокировка долгой не будет, так что риск при нормальной работе сети и клиента не очень велик.
Эта ситуация отличается от использования select
для UDP-сокетов. С ними такой проблемы не возникает, т.к. дейтаграмма никогда не приходит по частям, и если функция select
показала готовность сокета. значит, уже получено все сообщение целиком.
Завершается основной цикл сервера удалением всех ресурсов, связанных с закрытыми соединениями. После небольшой паузы, сделанной для того, чтобы сервер не нагружал процессор непрерывно, управление передается на начало цикла (листинг 2.26).
// Тайм-аут для функции select, хотя и передается через указатель,
// является для нее входным параметром, который не изменяется.
// Так как у нас везде будет использоваться один и тот же нулевой
// тайм-аут, можем один раз задать значение переменной Timeout
// и в дальнейшем всегда им пользоваться.
Timeout.tv_sec := 0;
Timeout.tv_usec := 0;
// Начало цикла подключения и общения с клиентами
repeat
// Сначала проверяем, готов ли слушающий сокет.
// Если он готов, это означает, что есть подключившийся,
// но не обработанный функцией accept клиент
FD_ZERO(SockSet);
FD_SET(MainSocket, SockSet);
if select(0, @SockSet, nil, nil, @Timeout) = SOCKET_ERROR then
raise ESocketException.Create('Ошибка при проверке готовности слушающего сокета: ' +
GetErrorString);
// Если функция select оставила MainSocket в множестве, значит,
// зафиксировано подключение клиента, и функция accept не приведет
// к блокированию нити.
if FD_ISSET(MainSocket, SockSet) then
begin
ClientSockAddrLen := SizeOf(ClientSockAddr);
// Принимаем подключившегося клиента. Для общения с ним создается
// новый сокет, дескриптор которого помещается в ClientSocket.
ClientSocket :=
accept(MainSocket, @ClientSockAddr, @ClientSockAddrLen);
Интервал:
Закладка: