Теперь рассмотрим все сказанное на практике. Откройте пример TCPServer, который мы написали в разделе 4.10 (см. компакт-диск, файл Sources\ch04\TCPServer). Давайте скорректируем пример так, чтобы он работал в неблокирующем режиме.

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

// Переход в неблокирующий режим iMode := 1;

ioctlsocketCsServertisten. FI0NBI0, iMode), Таким образом, мы перевели сокет в асинхронный режим. Теперь попробуйте запустить пример. Сначала вы должны увидеть два сообщение об удачном запуске сервера, после чего программа возвратит ошибку "Accept filed". В асинхронном режиме функция accept не блокирует работу программы, а значит, не ожидает соединения. В этом случае, если в очереди нет ожидающих подключения клиентов, функция вернет ошибку WSAEWOULDBLOCK.

Чтобы избавиться от этого недостатка, нужно скорректировать код в цикле ожидания соединений. В нашем случае это бесконечный цикл while, который идет после вызова функции 1 i sten. Пример цикла для асинхронного варианта приведен в листинге 4.9.

Листинг 4.9. Цикл обработки входящих сообщений

while (true) do begin
FD_ZERO(ReadSet):
FD_SET(sServerListen. ReadSet):
ReadySock := select(0. @ReadSet. nil. nil. nil):

if (ReadySock = S0CKET_ERR0R) then begin MessageBoxCO. 'Ошибка функции select', 'Error'. 0); exit:

end:

if (FD_ISSET(sServerListen. ReadSet)) then begin iSize := sizeof(clientaddr); // Прием нового соединения

sClient := accept(sServerListen, @clientaddr. GHSize):
if sClient = INVALID_SOCKET then
begin
TestWi nSockError('accept');
break:
end:

// Соединение принято, создаем поток si := TCPClientThread Create(true): si.Sock := sClient: si.Resume:

end:
end.

Для работы примера в раздел var используемой процедуры нужно добавить две переменные:

ReadSet: TFDSet: ReadySock: Integer;

Переменная ReadSet будет применяться для хранения набора сокетов, a ReadySock - для хранения количества готовых к использованию сокетов. Пока у нас только один сокет, поэтому вторую переменную не трогаем.

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

Обратите внимание, что мы запускаем функцию для ожидания данных на чтение. Что нам читать из сокета, когда к нам не подключены клиенты? А читать мы будем как раз запрос на подключение. Когда сокет готов к чтению, значит, кто-то прислал запрос на подключение. Этот запрос читать не имеет смысла, а вот принять соединение необходимо.

При вызове функции select происходит "заморозка" программы иона ожидает подключение. Так в чем же смысл использования неблокирующих сокетов, если блокировка все равно происходит? В данном случае у нас только один сокет и поэтому преимущества не видно. А что, если нужно ждать подключения от клиента на нескольких портах? В блокирующем режиме пришлось бы создавать несколько потоков, и каждый из них работал бы со своим портом. В данном случае мы можем создать еще один сокет, связанный с другим портом, прямо в этом коде, добавить его в набор и потом ожидать с помощью функции select подключения на любой из сокетов (любой порт).

И это не единственный пример, когда можно добиться максимальной производительности без блокировок. В книге "Программирование в Delphi глазами хакера" я описывал пример создания самого быстрого сканера портов, и там для повышения производительности как раз использовались неблокирующие сокеты.

Когда сокет готов к чтению, значит, к нашему серверу хочет подключиться клиент. Но прежде чем предпринимать какие-то действия, необходимо проверить вхождение сокета в набор функцией FDISSET.

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

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

Возникает вопрос, в каком режиме работает сокет sClient, который создан функцией accept для работы с клиентским соединением. Мы знаем, что по умолчанию сокеты работают в блокирующем режиме, и это значение не изменялось. Если в программе клиента убрать отправку данных и отключение от сервера, запустить сервер и подключиться в качестве клиента, то сервер примет соединение и "заснет". Это говорит о том, что функция recv "заморозит" поток в блокирующем режиме и будет бесконечно ожидать данные. Несмотря на то что основной сокет работает в неблокирующем режиме, сокеты, созданные функцией accept, будут блокирующими.

Если вы попытаетесь протестировать этот сервер с помощью программы TCPCIient, написанной ранее (см. компакт-диск, файл Sources\ch04\TCPCIient), то после получения данных произойдет ошибка. Почему? Это связано с тем, что сервер обрабатывает сообщения в цикле, а, клиент, отправив команду и получив ответ, закрывает соединение, и на сервере генерируется ошибка чтения с закрытого сокета.

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

ПРИМЕЧАНИЕ -

Исходный код рассмотренного здесь примера находится на компакт-диске в каталоге Sources\ch04\Seiect.

4.13.1. Проверка готовности сокета через функцию select || Оглавление || 4.13.3. События Windows


Delphi в шутку и всерьез: что умеют хакеры



Новости за месяц

  • Декабрь
    2021
  • Пн
  • Вт
  • Ср
  • Чт
  • Пт
  • Сб
  • Вс
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31