Программирование на C++ глазами хакера


Прием и передача данных


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

Применяют следующие режимы сетевого ввода/вывода (прием/передача данных):

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

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

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

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

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

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


int ioctisocket ( SOCKET s, long cmd, u_long FAR* argp );

У этой функции три параметра:

сокет, режим которого надо изменить;

команда, которую необходимо выполнить;

параметр для команды.

Изменение режима блокирования происходит при указании в качестве команды константы FIONBIO. При этом, если параметр команды имеет нулевое значение, то будет использоваться блокирующий режим, иначе — неблокирующий.

Давайте посмотрим на пример создания сокета и перевода его в неблокирующий режим:

SOCKET s; unsigned long ulMode;

s = socket(AS_INET, SOCK_STREAM, 0); ulMode = 1; ioctisocket(s, FIONBIO, (unsigned long*)ulMode);

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

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

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


Содержание раздела