Чтение онлайн

на главную

Жанры

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

 // функция select ничего не ждала, а возвращала

 // готовность сокетов на момент вызова.

 Timeout.tv_sec := 0;

 Timeout.tv_usec := 0;

 // Проверяем готовность сокета для чтения

 if select(0, @SocketSet, nil, nil, @Timout) = SOCKET_ERROR then

 begin

AddMessageToLog('Ошибка при проверке готовности сокета: ' + GetErrorString);

Exit;

 end;

 // Проверяем, оставила ли функция select сокет в множестве.

 //Если оставила, значит, во входном буфере сокета есть данные.

 if FD_ISSET(FSocket, SocketSet) then

 begin

AddrLen := SizeOf(RecvAddr); //
Получаем дейтаграмму

RecvLen :=

recvfrom(FSocket, Buffer, SizeOf(Buffer), 0, RecvAddr, AddrLen);

// Так как UDP не поддерживает соединение, ошибку при вызове recvfrom

// мы можем получить, только если случилось что-то совсем

// экстраординарное.

if RecvLen < 0 then

begin

AddMessageToLog('Ошибка при получении сообщения: ' +

GetErrorString);

Exit;

end;

// Устанавливаем нужный размер строки

SetLength(Msg, RecvLen);

// и копируем в неё дейтаграммы из буфера

if RecvLen > 0 then Move(Buffer, Msg[1], RecvLen);

AddMessageToLog('Сообщение с адреса ' + inet_ntoa(RecvAddr.sin_port) +

':' + IntToStr(ntohs(RecvAddr.sin_port)) + ': ' + Msg);

 end;

end;
 

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

select
не покажет, что сокет не готов к чтению, и извлечь за один раз сразу все сообщения, которые накопились в буфере сокета. Этого не сделано, чтобы уменьшить уязвимость чата по отношению к действиям потенциального злоумышленника. Имеется в виду та разновидность DoS-атаки, когда злоумышленник посылает большой поток сообщений, чтобы парализовать работу чата. Работа в этом случае, конечно же, будет парализована независимо от того, будет ли в обработчике события таймера извлекаться одно сообщение или все сразу — все равно чат будет замусорен бессмысленными сообщениями. Но в первом случае между показом сообщений будут интервалы, и пользователь хотя бы сможет корректно закрыть программу. Во втором же случае, если злоумышленник посылает сообщения достаточно быстро, цикл может оказаться бесконечным, обработка других оконных сообщений прекратится, и пользователь вынужден будет снять задачу средствами системы. Таким образом, извлечение только одного сообщения за один раз снижает ущерб от атаки. (Разумеется, вряд ли кто-то всерьез захочет атаковать наш учебный пример, но эту возможность следует учитывать при разработке более серьезных приложений.)

Перейдем к следующему примеру использования

select
— TCP-серверу, который может работать одновременно с неограниченным числом клиентов (пример находится на компакт-диске в папке SelectServer). Этот сервер будет усовершенствованной версией нашего простейшего сервера (см. разд. 2.1.12) и тоже будет консольным приложением (функция
select
, как мы видели на примере UDP-чата, позволяет создавать приложения с графическим интерфейсом пользователя, так что реализация сервера в качестве консольного приложения — это не необходимость, а свободный выбор для иллюстрации различных способов применения функции
select
).

Примечание

Разумеется, ни один сервер не может работать с неограниченным числом клиентов. Здесь и далее под словом "неограниченный" подразумевается то, что количество клиентов сервера ограничивается только ресурсами системы, а не самой реализацией

сервера.

Инициализация сокета и установка его в режим прослушивания в новом сервере ничем не отличается от простейшего, изменения начинаются только с цикла. Теперь цикл только один (вложенные циклы в нем есть, но они выполняют чисто техническую роль). Начинается цикл с того, что с помощью функции

select
определяется готовность к чтению слушающего сокета. Если слушающий сокет готов к чтению, то в данном случае это означает, что есть клиенты, которые уже подключились к серверу, но еще не были обработаны функцией
accept
. Если такие клиенты есть, то сервер принимает подключение, причем только одно за одну итерацию цикла. Для каждого подключившегося клиента сервер создает экземпляр записи
TConnection
, которая описана в листинге 2.25.

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

Поделиться:
Популярные книги

Чужое наследие

Кораблев Родион
3. Другая сторона
Фантастика:
боевая фантастика
8.47
рейтинг книги
Чужое наследие

Бальмануг. Студентка

Лашина Полина
2. Мир Десяти
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Бальмануг. Студентка

Авиатор: назад в СССР

Дорин Михаил
1. Авиатор
Фантастика:
попаданцы
альтернативная история
5.25
рейтинг книги
Авиатор: назад в СССР

Генерал-адмирал. Тетралогия

Злотников Роман Валерьевич
Генерал-адмирал
Фантастика:
альтернативная история
8.71
рейтинг книги
Генерал-адмирал. Тетралогия

На границе империй. Том 6

INDIGO
6. Фортуна дама переменчивая
Фантастика:
боевая фантастика
космическая фантастика
попаданцы
5.31
рейтинг книги
На границе империй. Том 6

Теневой путь. Шаг в тень

Мазуров Дмитрий
1. Теневой путь
Фантастика:
фэнтези
6.71
рейтинг книги
Теневой путь. Шаг в тень

Защитник

Астахов Евгений Евгеньевич
7. Сопряжение
Фантастика:
боевая фантастика
постапокалипсис
рпг
5.00
рейтинг книги
Защитник

Кодекс Охотника. Книга XIII

Винокуров Юрий
13. Кодекс Охотника
Фантастика:
боевая фантастика
попаданцы
аниме
7.50
рейтинг книги
Кодекс Охотника. Книга XIII

Его маленькая большая женщина

Резник Юлия
Любовные романы:
современные любовные романы
эро литература
8.78
рейтинг книги
Его маленькая большая женщина

Проклятый Лекарь IV

Скабер Артемий
4. Каратель
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Проклятый Лекарь IV

Жандарм 4

Семин Никита
4. Жандарм
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Жандарм 4

Тринадцатый V

NikL
5. Видящий смерть
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Тринадцатый V

Кодекс Охотника. Книга IV

Винокуров Юрий
4. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Охотника. Книга IV

Энфис 2

Кронос Александр
2. Эрра
Фантастика:
героическая фантастика
рпг
аниме
5.00
рейтинг книги
Энфис 2