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

         

Обмен данными


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

Сразу замечу, что функции создавались тогда, когда еще не было даже разговоров о UNICODE (универсальная кодировка, позволяющая работать с любым языком). Поэтому, чтобы отправить данные в этой кодировке, нужно привести их к типу char*, а длину умножить на 2, потому что каждый символ в UNICODE занимает 2 байта (в отличие от ASCII, где символ равен одному байту).

Чтобы принять данные, нужно их сначала отправить. Поэтому начну рассмотрение функций обмена данными с этого режима. Для передачи данных серверу существуют функции send и WSASend (для WinSock2). Функция send выглядит следующим образом:

int send ( SOCKET s, const char FAR * buf, int len, int flags );

Функция передает следующие параметры:

s — сокет, через который будет происходить отправка данных. В программе может быть открыто одновременно несколько соединений с разными серверами, и нужно четко определить, какой сокет надо использовать;

buf — буфер, содержащий данные, которые необходимо отправить;

len — длина буфера в параметре buf;

flags — флаги, определяющие метод отправки. Здесь можно указывать сочетание из следующих значений:

0 — флаги не указаны;

MSG_DONTROUTE — отправляемые пакеты не надо маршрутизировать. Если транспортный протокол, который отправляет данные, не поддерживает этот флаг, то он игнорируется;

MSG_OOB — данные должны быть отправлены вне очереди (out of band), т.е. срочно.

Функция WSASend выглядит следующим образом:

int WSASend ( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE );

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

s — сокет, через который будет происходить отправка данных;



lpBuffers — структура или массив структур типа WSABUF. С этой структурой вы познакомились, когда рассматривали функцию connect. Эта же структура использовалась для отправки данных во время соединения;


dwBufferCount — количество структур в параметре lpBuffers;

lpNumberOfBytesSent — количество переданных байт для завершенной операции ввода/вывода;

dwFlags — определяет метод отправки и может принимать такие же значения, как и параметр dwFlags функции send;

pOverlapped и pCompletionRoutine — задаются при использовании пере-крытого ввода/вывода (overlapped I/O). Это одна из моделей асинхронной работы сети, поддерживаемой WinSock.

Если функция send (или WSASend) отработала успешно, то она вернет количество отправленных байт, иначе — значение —1 (или константу SOCKET_ERROR, которая равна -1). Получив ошибку, вы можете проанализировать ее с помощью функции WSAGetLastError:

WSAECONNABORTED — соединение было разорвано, или вышло время ожидания или произошла другая ошибка;

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

WSAENOTCONN — соединение не установлено;

WSAETIMEDOUT — время ожидания ответа вышло.

Для получения данных используются функции recv и WSARecv (для второй версии WinSock). Функция recv выглядит следующим образом:

int recv ( SOCKET s, char FAR * buf, int len, int flags );

Параметры очень похожи на те, которые описаны для функции send:

s — сокет, данные которого надо получить;

buf — буфер, в который будут помещены принятые данные;

len — длина буфера в параметре buf;

flags — флаги, определяющие метод получения. Здесь можно указывать сочетание из следующих значений:

0 — флаги не указаны;

MSG_PEEK — считать данные из системного буфера без удаления. По умолчанию считанные данные стираются из системного буфера;

MSG_OOB — обработать срочные данные out of band.

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



Функция WSARecv выглядит следующим образом:

int WSARecv ( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE );

Здесь также бросается в глаза сходство в параметрах с функцией WSASend. Давайте рассмотрим их назначение:

s — сокет, через который будет происходить получение данных;

lpBuffers — структура или массив структур типа WSABUF. В эти буферы будут помещены полученные данные;

dwBufferCount — количество структур в параметре lpBuffers;

lpNumberOfBytesSent — количество полученных байт, если операции ввода/вывода уже завершились;

dwFlags — определяет метод отправки и может принимать такие же значения, как и параметр dwFlags функции recv. Но есть один новый флаг — MSG_PARTIAL. Его нужно указывать для протоколов, ориентированных на чтение сообщения в несколько приемов. В случае указания этого флага при каждом считывании можно получить только часть данных;

pOverlapped и pCompletionRoutine — устанавливаются при использова-нии перекрытого ввода/вывода (overlapped I/O). Это одна из моделей асинхронной работы сети, поддерживаемой WinSock.

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

Есть еще одна интересная сетевая функция, которая появилась в WinSock2. Если все рассмотренные в этой главе функции сетевой библиотеки (без префикса WSA) существуют не только в Windows, но и в UNIX-системах, то функция TransmitFile является расширением Microsoft и работает только в Windows.

Функция TransmitFile отсылает по сети целый файл. Это происходит достаточно быстро, потому что отправка идет через ядро библиотеки. Вам не надо заботиться о последовательном чтении и проверять количество отправленных данных, потому что это гарантируется библиотекой WinSock2.



Функция выглядит следующим образом:

BOOL TransmitFile( SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerSend, LPOVERLAPPED lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwFlags );

Рассмотрим ее параметры:

hSocket — сокет, через который нужно отправить данные;

hFile — указатель на открытый файл, данные которого надо отправить;

nNumberOfBytesToWrite — количество отправляемых из файла байт. Если указать о, то будет отправлен весь файл;

nNumberOfBytesPerSend — размер пакета для отправки. Если указать 1024, то данные будут отправляться пакетами в 1024 байт данных. Если указать 0, то будет использовано значение по умолчанию;

lpOverlapped — используется при перекрестном вводе/выводе;

lpTransmitBuffers — содержит служебную информацию, которую надо послать до и после отправки файла. По этим данным на принимающей стороне можно определить начало или окончание передачи;

dwFlags — флаги. Здесь можно указать следующие значения:

TF_DISCONNECT — закрыть сокет после передачи данных;

TF_REUSE_SOCKET — подготовить сокет для повторного использования;

TF_WRITE_BEHIND — завершить работу, не дожидаясь подтверждения о получении данных со стороны клиента.

Параметр lpTransmitBuffers имеет тип структуры следующего вида:

typedef Struct _TRANSMIT_FILE_BUFFERS { PVOID Head; DWORD HeadLength; PVOID Tail; DWORD TailLength; } TRANSMIT_FILE_BUFFERS;

У этой структуры следующие параметры:

Head — указатель на буфер, содержащий данные, которые надо послать клиенту до начала отправки файла;

HeadLength — размер буфера Head;

Tail — указатель на буфер, содержащий данные, которые надо послать клиенту после завершения отправки файла;

TailLength — размер буфера Tail.


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