Нашёл свою старую статью, которую всё собирался-собирался, но так и не дописал. Файл датирован февралём 2010 года – уже порядочно времени прошло, всё откладывал, да откладывал.

Статья не дописана. Выложу как есть. Если кто захочет закончить – милости прошу, размещу ссылку на продолжение. А может быть, эта публикация меня самого смотивирует найти время ;)

В предыдущем посте (январь 2010) мы рассмотрели проведение http запроса с помощью синхронного сокета. Сегодня доработаем тот пример, но вместо синхронного (блокирующего) мы будем использовать асинхронный (неблокирующий) сокет. Разобравшись с данной темой, мы сможем заняться более интересными вещами с сокетами :)

ioctlsocket — меняем режим сокета

Функция ioctlsocket меняет режим сокета. Как уже упоминалось, сокеты бывают двух видов: синхронный и асинхронный. Или «блокирующий» и «неблокирующий» – это равносильные понятия. По умолчанию сокет синхронный, а наша задача на сегодня – использовать асинхронный (неблокирующий) сокет.

  arg:=1;
  if ioctlsocket(Socket1,FIONBIO,arg)=SOCKET_ERROR then begin
    writeln('socket error #', WSAGetLastError);
    readln;
    exit;
  end;

Пояснения: FIONBIO — константа в модуле winsock, говорит функции о том, что сокет надо делать или синхронным, или асинхронным. Есть еще пара таких констант для установки других параметров сокета, но мы их сегодня не станем рассматривать. arg — переменная типа integer. В случае с FIONBIO следует устанавливать:

  • 0 для перевода сокета в синхронный режим,
  • не 0 (мы установили 1, что, впрочем, несущественно) для перевода в асинхронный режим.

По возвращаемому значению мы проверяем, всё ли прошло нормально, иначе выводим ошибку. Напомню еще раз: в данном примере мы пишем консольную программу.

Приведённый выше код следует вставить в наш проект перед вызовом функции connect. Пробуем запустить – вот незадача: connection error #10035. Эта ошибка расшифровывается как WSAEWOULDBLOCK, да и не ошибка это вовсе. Просто нас хотят предупредить о том, что мы используем асинхронный сокет, и соединение произойдёт не сразу.
Поэтому немного модифицируем окрестность вызова функции connect:

  if Connect(Socket1,SockAddr1,SizeOf(SockAddr1))<>0 then
    if WSAGetLastError<>10035 then begin
      writeln('connection error #',WSAGetLastError);
      readln;
      exit;
    end;

Теперь при запуске программы будет ошибка 10057 — сокет не подключён, что неудивительно — нам надо дождаться соединения, о чём в следующих параграфах.

Структура TFDSet как массив сокетов

Всё-таки работа с асинхронными сокетами на winapi оптимизирована под их массив. Нам сейчас предстоит работать с массивом из одного сокета.
В саму структуру TFDSet входит массив сокетов и переменная для хранения их количества. Мы не станем в данную структуру углубляться, так как у нас уже имеются готовые функции для работы с TFDSet.
Нам понадобятся переменные:

  FDSet1:TFDSet;
  TimeVal1:TTimeVal;

Структура TTimeVal служит для установки таймаута, в ней можно указать секунды и микросекунды (!).

Теперь заполним структуры:

  FD_ZERO(FDSet1);
  FD_SET(Socket1, FDSet1);
  TimeVal1.tv_sec:=10;
  TimeVal1.tv_usec:=0;

Мы добавили наш Socket1 в FDSet1 с помощью процедуры FD_SET, поставили таймаут 10 секунд.
На будущее: функция FD_ZERO очищает TFDSet, функция FD_CLR противоположна FD_SET — убирает указатель на сокет из структуры. А с помощью функции FD_ISSET мы можем проверить наличие сокета в структуре. У последних двух синтаксис такой же, как и у FD_SET.

Функция select

Функция select — синхронная. То есть программа блокируется и ждёт результата. Дело в том, что с помощью этой функции мы будем работать с массивом асинхронных сокетов, а точнее со структурой TFDSet.
Синтаксис функции:

function select(nfds: Integer; readfds, writefds, exceptfds: PFDSet; timeout: PTimeVal): Longint;

Параметр nfds спокойно ставим в ноль: этот параметр здесь только для совместимости с «Berkeley sockets» (см. msdn). Как утверждает википедия, это связано с линуксом. Да, о кроссплатформенности winsock будет отдельный пост (вероятно, что уже не будет – 2014) :)
Тип PFDSet, как несложно догадаться, – указатель на структуру TFDSet. readfds, writefds, exceptfds — целых три параметра, давайте разберёмся, что же нам нужно передать этой функции.
readfds – список сокетов, которые нужно проверить на «readability» (msdn). Иными словами, можно ли уже считать с сокета полученную информацию. Ещё можно перефразировать, что функция recv сразу вернёт данные.
writefds – аналогично проверяем, можно ли с помощью данных сокетов отправить запрос. То есть, в большинстве случаев, если коннект уже произошел. Иными словами, вызов send будет точно успешен.
exceptfds – список сокетов, которые проверяем на наличие ошибки. Например, функция connect не удалась, что не редкость.
Стоит отметить, что все параметры необязательные, однако должен присутствовать хотя бы один, и в каждом присутствующем должен быть указатель хотя бы на один сокет.
Параметр timeout – указатель на переменную типа TTimeVal – очевидно, время выжидания для функции select. Указываем nil для бесконечного ожидания :)
Однако если структуру всё-таки указать, а саму её выставить в (tv_sec=0; tv_usec=0), то функция не будет ничего ждать. Таким образом мы можем просто проверить статусы сокетов.

Что же возвращает функция select? Где же полученный список сокетов?
Отвечаю :) Она возвращает общее количество сокетов, подходящих тому условию, которое мы передали функции select (читай, те три структуры).

Дальше я не дописал. Считайте, что это первая часть :)

Недавно узнал про бизнес-модель SaaS, что значит «software as a service». Имеется в виду то, что, когда некто разрабатывает приложение, то продаёт не его, а повременной доступ к нему. Например, таким образом можно приобрести различные сервисы для улучшения продуктивности отдельных людей или компаний (может быть как онлайн-приложение, так и обычное, десктопное). Аналогичным образом предоставляют различные программы для бухгалтерии, CRM-системы. С последними, кстати, удобно: их не нужно устанавливать на свой компьютер – ведь они используются удалённо. Если интересен такой подход к программному обеспечению, советую заглянуть в каталог saas сервисов – там много всего. Меня, кстати, удивило, что там WordPress есть, и стоит он 99 рублей в месяц. Видимо для тех, кто совсем не хочет париться с установкой этого движка :)