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

на главную

Жанры

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

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

Шрифт:

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

FD_SET
и, соответственно, эти сокеты выпадут из дальнейшего рассмотрения, т.е. даже если клиент что-то пришлет, сервер этого не увидит. Решить проблему можно тремя способами. Самый простой — это отказывать в подключении лишним клиентам. Для этого сразу после вызова
accept
нужно вызывать для нового сокета
closesocket
. Второй способ — это увеличение количества сокетов в множестве, как это было описано ранее. В этом случае все равно остается та же проблема, хотя если сделать
число сокетов в множестве достаточно большим, она практически исчезает. И наконец, можно разделить сокеты на несколько порций, для каждой из которых вызывать select отдельно. Это потребует усложнения примера, потому что сейчас в функции
select
мы используем бесконечное ожидание. При разбиении сокетов на порции это может привести к тому, что из-за отсутствия готовых сокетов в первой порции программа не сможет перейти к проверке второй порции, в которой готовые сокеты, может быть, есть. Пример разделения сокетов на порции будет рассмотрен в следующем разделе.

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

FD_ZERO
, ни
FD_SET
:

Move((PChar(Sockets) - 4)^, FDSet, Length(Sockets) * SizeOf(TSocket) + SizeOf(Integer));

Почему такая конструкция будет работать, предлагаем разобраться самостоятельно, изучив по справке Delphi, как хранятся в памяти динамические массивы, а по MSDN — структуру типа

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

Второй шаг — это собственно выполнение ожидания готовности сокетов с помощью функции

select
. Готовность к записи и к чтению высокоприоритетной информации нас в данном случае не интересует, поэтому мы ограничиваемся заданием множества
readfds
. В нашем простом примере не должно выполняться никаких действий, если ни один сокет не готов, поэтому последний параметр тоже равен
nil
, что означает ожидание, не ограниченное тайм-аутом.

Третий шаг выполняется только после функции

select
, т.е. тогда, когда хотя бы один из сокетов находится в состоянии готовности. На этом шаге мы проверяем сокеты, созданные для взаимодействия с клиентами на предыдущих итерациях цикла с помощью функции
accept
. Эти сокеты располагаются в массиве сокетов, начиная с элемента с индексом 1. Программа в цикле просматривает все сокеты и, если они находятся в состоянии готовности, выполняет операцию чтения.

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

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

Напомним, что функция

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

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

accept
, чтобы создать сокеты для взаимодействия с этими клиентами.

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

select
, а не в том, что он дает готовое решение на все случаи жизни.

2.1.14. Примеры использования функции select

Рассмотрим два практических примера использования функции

select
для получения информации о готовности сокета. Оба примера станут развитием рассмотренных ранее.

Сначала модифицируем UDP-чат (см. разд. 2.1.10) таким образом, чтобы он использовал один сокет и для отправки, и для получения сообщений (пример SelectChat на компакт-диске). Вторая нить нам теперь не понадобится, всё будет делать главная форма. Процедуры создания сокета и отправки сообщений изменений не претерпели, главное дополнение — это то, что на форме появился таймер, в обработчике события

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

Примечание

Несмотря на эти изменения, новая версия UDP-чата может обмениваться сообщениями со старой, т.к. протокол обмена остался неизменным.

Листинг 2.24. Проверка готовности сокетов при обработке сообщения от таймера

// Реакция на таймер. С периодичностью, заданной таймером,

// проверяем, не пришли ли сообщения, и если пришли,

// получаем их.

procedure TChatForm.TimerChatTimer(Sender: TObject);

var

 // Множество сокетов для функции select.

 // Будет содержать только один сокет FSocket.

 SocketSet: TFDSet;

 // Тайм-аут для функции select

 Timeout: TTimeVal;

 // Буфер для получения сообщения.

 // Размер равен максимальному размеру UDP-дейтаграммы

 Buffer: array[0..65506] of Byte;

 Msg: string;

 // Адрес, с которого пришло сообщение

 RecvAddr: TSockAddr;

 RecvLen, AddrLen: Integer;

begin

 // Инициализируем множество сокетов,

 // т.е. очищаем его от случайного мусора

 FD_ZERO(SocketSet);

 // Добавляем в это множество сокет FSocket

 FD_SET(FSocket, SocketSet);

 // Устанавливаем тайм-аут равным нулю, чтобы

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

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

Кораблев Родион
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