А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
if ClientSocket = INVALID_SOCKET then raise
ESocketException.Create(
'Ошибка при ожидании подключения клиента: ' + GetErrorString);
// Создаем в динамической памяти новый экземпляр TConnection
// и заполняем его данными, соответствующими подключившемуся клиенту
New(NewConnection);
NewConnection.ClientSocket := ClientSocket;
NewConnection.ClientAddr :=
Format('%u.%u.%u.%u:%u',
Ord(ClientSockAddr.sin_addr.S_un_b.s_bl),
Ord(ClientSockAddr.sin_addr.S_un_b.s_b2),
Ord(ClientSockAddr.sin_addr.S_un_b.s_b3),
Ord(ClientSockAddr.sin_addr.S_un_b.s_b4),
ntohs(ClientSockAddr.sin_port));
NewConnection.Deleted := False;
// Добавляем соединение в список
Connections.Add(NewConnection);
WriteLn(OemString('Зафиксировано подключение с адреса ' +
NewConnection.ClientAddr));
end;
// Теперь проверяем готовность всех сокетов подключившихся клиентов.
// Так как множество SockSet не может содержать более чем FT_SETSIZE
// элементов, а размер списка Connections мы нигде не ограничиваем,
// приходится разбивать Connections на "куски" размером не более
// FD_SETSIZE и обрабатывать этот список по частям.
// Поэтому у нас появляется два цикла - внешний, который повторяется
// столько раз, сколько у нас будет кусков, и внутренний, который
// повторяется столько раз, сколько элементов в одном куске.
for J := 0 to Ceil(Connections.Count, FD_SETSIZE) - 1 do
begin
FD_ZERO(SockSet);
for I := FD_SETSIZE * J to Min(FD_SETSIZE * (J + 1) - 1, Connections.Count - 1) do
FD_SET(PConnection(Connections[I])^.ClientSocket, SockSet);
if select(0, @SockSet, nil, nil, @Timeout) = SOCKET_ERROR then
raise ESocketException.Create(
'Ошибка при проверке готовности сокетов: ' + GetErrorString);
// Проверяем, какие сокеты функция select оставила в множестве,
// и вызываем для них ProcessSocketMessage. В этом есть некоторый
// риск, т.к. для того, чтобы select оставила сокет в множестве,
// достаточно, чтобы он получил хотя бы один байт от клиента,
// а не все сообщение. Поэтому может возникнуть такая ситуация,
// когда сервер получил только часть сообщения, но уже пытается
// прочитать сообщение целиком. Это приведет к блокированию нити,
// но вероятность блокирования на долгое время мы оцениваем как
// крайне низкую, т.к. оставшаяся часть сообщения, скорее всего,
// придет достаточно быстро, и поэтому идем на такой риск.
for I := FD_SETSIZE * J to Min(FD_SETSIZE * (J + 1) - 1, Connections.Count - 1) do
if FD_ISSET(PConnection(Connections[I])^.ClientSocket, SockSet) then
ProcessSocketMessage(PConnection(Connections[I])^);
end;
// Проверяем поле Deleted у всех соединений. Те, у которых
// оно равно True, закрываем: закрываем сокет, освобождаем память,
// удаляем указатель из списка. Цикл идет с конца списка к началу,
// потому что в ходе работы цикла верхняя граница списка
// может меняться, и цикл for снизу вверх мог бы привести
// к появлению индексов вне диапазона.
for I := Connections.Count - 1 downto 0 do
if PConnection(Connections[I])^.Deleted then
begin
closesocket(PConnection(Connections[I])^.ClientSocket);
Dispose(PConnection(Connections[I]));
Connections.Delete(I);
end;
Sleep(100);
until False;
Функции Ceil
и Min
, которые встречаются здесь, можно было бы заменить одноимёнными функциями из модуля Math
. Но этот модуль входит не во все варианты поставки Delphi, и чтобы пример можно было откомпилировать в любом варианте поставки Delphi, мы описали их здесь самостоятельно (листинг 2.27).
Ceil
и Min
// Функция Ceil возвращает наименьшее целое число X, удовлетворяющее
// неравенству X >= А / В
function Ceil(A, B: Integer): Integer;
begin
Result := A div B;
if A mod В <> 0 then Inc(Result);
end;
// Функция Min возвращает меньшее из двух чисел
function Min(А, В: Integer): Integer;
begin
if A < В then Result := A
else Result := B;
end;
Получившийся сервер более устойчив к DoS-атакам, чем написанный ранее многонитевой сервер. Так как он обходится одной нитью, планировщик задач не перегружается при большом числе подключившихся клиентов. DoS-атака заставляет расходовать только ресурсы библиотеки сокетов и процессорное время, причем вредный эффект последнего легко уменьшить, установив процессу сервера низкий приоритет.
Однако сервер имеет другую уязвимость, связанную с возможным отступлением от протокола обмена клиентом (случайным или злонамеренным). Если клиент, например, пришлет всего один байт и на этом остановится, не разрывая связь с сервером, то при попытке получить сообщение от такого клиента сервер окажется заблокированным, т.к. будет ожидать как минимум четырех байтов (длина строки). Это полностью парализует работу сервера, потому что его единственная нить окажется заблокированной, и обрабатывать сообщения от других клиентов он не сможет.
Многонитевой сервер в этом отношении надежнее: некорректное сообщение клиента заблокирует только ту нить, которая взаимодействует с этим клиентом, никак не влияя на остальные нити, работающие с другими клиентами.
Сделать сервер более устойчивым к некорректным действиям клиента можно, если каждый раз читать ровно столько байтов, сколько пришло. Это усложнит сервер, т.к. придется между "сеансами связи с клиентом" помнить сколько байтов было прочитано в прошлый раз. Однако это поможет полностью избежать блокировок при операциях чтения, что существенно повысит надежность сервера. В этом разделе мы не будем рассматривать соответствующий пример, а реализуем эту возможность в следующем сервере, использующем неблокирующие сокеты. В сервере на основе select
это делается совершенно аналогично.
2.1.15. Неблокирующий режим
Ранее мы столкнулись с функциями, которые могут надолго приостановить работу вызвавшей их нити, если действие не может быть выполнено немедленно. Это функции accept
, recv
, recvfrom
, send
, sendto
и connect
(в дальнейшем в этом разделе мы не будем упоминать функции recvfrom
и sendto
, потому что они в смысле блокирования эквивалентны функциям recv
и send
соответственно, и все, что будет здесь сказано о recv
и send
, применимо к recvfrom
и sendto
). Такое поведение не всегда удобно вызывающей программе, поэтому в библиотеке сокетов предусмотрен особый режим работы сокетов — неблокирующий. Этот режим может быть установлен или отменен дм каждого сокета индивидуально с помощью функции ioctlsocket
, имеющей следующий прототип:
function ioctlsocket(s: TSocket; cmd: DWORD; var arg: u_long): Integer;
Данная функция предназначена для выполнения нескольких логически мало связанных между собой действий. Возможно, у разработчиков первых версий библиотеки сокетов были причины экономить на количестве функций, потому что мы и дальше увидим, что иногда непохожие операции выполняются одной функцией. Но вернемся к ioctlsocket
. Ее параметр cmd
определяет действие, которое выполняет функция, а также смысл параметра arg
. Допустимы три значения параметра cmd
: SIOCATMARK
, FIONREAD
и FIONBIO
. При задании SIOCATMARK
параметр arg
рассматривается как выходной: в нем возвращается ноль, если во входном буфере сокета имеются высокоприоритетные данные, и ненулевое значение, если таких данных нет (как уже было оговорено, мы в этой книге не будем касаться передачи высокоприоритетных данных).
Интервал:
Закладка: