Передача данных

В сетевом программировании самое главное — уметь отправлять и принимать данные. Для пересылки данных по сокету используются функции send и WSASend. Аналогично, для приема данных существуют функции recv и WSARecv.

Все буферы, используемые при отправке и приеме данных, состоят из элементов типа char. To есть эти функции не предназначены для работы с кодировкой UNICODE. Это особенно важно для Windows СЕ, так как она использует UNICODE по умолчанию. Отправить строку символов UNICODE можно двумя способами: в исходном виде или привести к типу char. Нюанс в том, что при указании количества отправляемых или принимаемых символов результат функции, определяющей длину строки, нужно умножить на 2, так как каждый UNICODE-символ занимает 2 байта строкового массива. Другой способ: сначала перевести строку из UNICODE в ASCII функцией WideCharToMultiByte.

Все функции приема и отправки данных при возникновении ошибки возвращают код SOCKET_ERROR. Для получения более подробной информации об ошибке можно вызвать функцию WSAGetLastError. Самые распространенные ошибки — WSAECONNABORTED и WSAECONNRESET. Обе возникают при закрытии соединения: либо по истечении времени ожидания, либо при закрытии соединения партнерским узлом. Еще одна типичная ошибка — WSAEWOULDBLOCK, обычно происходит при использовании неблокирующих или асинхронных сокетов. По существу, она означает, что функция не может быть выполнена в данный момент. В следующейглаве будут описаны разные методы ввода-вывода Winsock, которые помогут избежать этих ошибок.

^ Функции send и WSASend

API-функция send для отправки данных по сокету определена так:

// Code 3.13

int send(

SOCKET s,

const char FAR * buf,

int len,

int flags

);

Параметр s определяет сокет для отправки данных. Второй параметр — buf, указывает на символьный буфер, содержащий данные для отправки. Третий — len, задает число отправляемых из буфера символов. И последний параметр — flags, может принимать значения 0, MSG_DONTROUTE, MSG_OOB, или результат логического ИЛИ над любыми из этих параметров. При указании флага MSG_DONTROUTE транспорт не будет маршрутизировать отправляемые пакеты. Обработка этого запроса остается на усмотрение базового протокола (например, если транспорт не поддерживает этот параметр, запрос игнорируется). Флаг MSG_OOB указывает, что данные должны быть отправлены вне полосы (out of band), то есть срочно.

При успешном выполнении функция send вернет количество переданных байт, иначе — ошибку SOCKET_ERROR. Одна из типичных ошибок — WSAECONNABORTED, происходит при разрыве виртуального соединения из-за ошибки протокола или истечения времени ожидания. В этом случае сокет должен быть закрыт, так как он больше не может использоваться. Ошибка WSAECONNRESET происходит, если приложение на удаленном узле, выполнив аппаратное закрытие, сбрасывает виртуальное соединение, или неожиданно завершается, или происходит перезагрузка удаленного узла. В этой ситуации сокет также должен быть закрыт. Еще одна ошибка — WSAETIMEDOUT, часто происходит при обрыве соединения по причине сбоев сети или отказа удаленной системы без предупреждения.

Функция Winsock версии 2 WSASend — аналог send, определена так:

// Code 3.14

int WSASend(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent,

DWORD dwFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE

);

Сокет является действительным описателем сеанса соединения. Второй параметр указывает на структуру WSABUF или на массив этих структур. Третий — определяет число передаваемых структур WSABUF. Структура WSABUF включает сам символьный буфер и его длину. Может возникнуть вопрос: зачем нужно отправлять более одного буфера за раз? Это называется комплексным вводом-выводом (scatter-gather I/O). Подробней он будет обсуждаться далее, сейчас можно отметить, что при использовании нескольких буферов для отправки данных по сокету соединения массив буферов отправляется, начиная с первой и заканчивая последней структурой WSABUF.

Параметр lpNumberOfBytesSent — указатель на тип DWORD, который после вызова WSASend содержит общее число переданных байт. Параметр флагов dwFlags такой же, что и в функции send. Последние два указателя — 1рOverlapped и lpCompletionROUTINE используются для перекрытого ввода-вывода (overlapped I/O) — одной из моделей асинхронного ввода-вывода, поддерживаемых Winsock (см. также следующую главу).

WSASend присваивает параметру lpNumberOfBytesSent количество записанных байт. При успешном выполнении функция возвращает 0, иначе — SOCKET_ERROR. Ошибки те же, что и у функции send.

^ Функция WSASendDisconnect

Это специализированная функция используется редко. Она определена так:

// Code 3.15

int WSASendDisconnect (

SOCKET s,

LPWSABUF lpOUT boundDisconnectData

)

^ Срочные данные

Если приложению требуется отправить через потоковый сокет информацию более высокого приоритета, оно может обозначить эти сведения как срочные данные (out-of-band, OOB). Приложение с другой стороны соединения получает и обрабатывает ООВ-данные через отдельный логический канал, концептуально независимый от потока данных.

В TCP передача ООВ-данных реализована путем добавления 1-битового маркера (называемого URG) и 16-битного указателя в заголовке сегмента TCP, которые позволяют выделить важные байты в основном трафике. На данный момент для TCP существуют два способа выделения срочных данных. В RFC 793, описывающем TCP и концепцию срочных данных, говорится, что указатель срочности в заголовке TCP является положительным смещением байта, следующего за байтом срочных данных. Однако в RFC 1122 это смещение трактуется, как указатель на сам байт срочности.

В спецификации Winsock под термином ООВ понимают как независимые от протокола ООВ-данные, так и реализацию механизма передачи срочных данных в TCP. Для проверки, есть ли в очереди срочные данные, вызывается функция ioctlsocket с параметром SIOCATMARK. Подробнее об этой функции — в последующих главах.

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

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

Функция WSASendDisconnect начинает процесс закрытия сокета и отправляет соответствующие данные. Она доступна только для протоколов, поддерживающих постепенное закрытие и передачу данных при его осуществлении. Ни один из существующих поставщиков транспорта на данный момент не поддерживает передачу данных о закрытии соединения. Функция WSASendDisconnect действует аналогично shutdown с параметром SD_SEND, но также отправляет данные, содержащиеся в параметре boundDisconnectData. После ее вызова отправлять данные через сокет невозможно. В случае неудачного завершения WSASendDisconnect возвращает значение SOCKET_ERROR. Ошибки, встречающиеся при работе функции, аналогичны ошибкам send.

^ Функции recv и WSARecv

Функция recv — основной инструмент приема данных по сокету. Она определена так:

// Code 3.16

int recv(

SOCKET s,

char FAR * buf,

int len,

int flags

);

Параметр s определяет сокет для приема данных. Второй параметр — buf, является символьным буфером и предназначен для полученных данных, a len указывает число принимаемых байт или размер буфера buf. Последний параметр — flags, может принимать значения 0, MSG_PEEK, MSG_OOB или результат логического ИЛИ над любыми из этих параметров. Разумеется, 0 означает отсутствие особых действий. Флаг MSG_PEEK указывает, что доступные данные должны копироваться в принимающий буфер и при этом оставаться в системном буфере. По завершении функция также возвращает количество ожидающих байт.

Считывать сообщения таким образом не рекомендуется. Мало того, что из-за двух системных вызовов (одного — для считывания данных, и другого, без флага MSG_PEEK — для удаления данных), снижается производительность. В ряде случаев этот способ просто не надежен. Объем возвращаемых данных может не соответствовать их суммарному доступному количеству. К тому же, сохраняя данные в системных буферах, система оставляет все меньше памяти для размещения входящих данных. В результате уменьшается размер окна TCP для всех отправителей, что не позволяет приложению достичь максимальной производительности. Лучше всего скопировать все данные в собственный буфер и обрабатывать их там. Флаг MSG_OOB уже обсуждался ранее при рассмотрении отправки данных.

Использование recv в сокетах, ориентированных на передачу сообщений или дейтаграмм, имеет несколько особенностей. Если при вызове recv размер ожидающих обработки данных больше предоставляемого буфера, то после его полного заполнения возникает ошибка WSAEMSGSIZE. Ошибка превышения размера сообщения происходит только при использовании протоколов, ориентированных на передачу сообщений. Потоковые протоколы буферизируют поступающие данные и при запросе приложением предоставляют их в полном объеме, даже если количество ожидающих обработки данных больше размера буфера. Таким образом, ошибка WSAEMSGSIZE не может произойти при работе с потоковыми протоколами.

Функция WSARecv обладает дополнительными по сравнению с recv возможностями: поддерживает перекрытый ввод-вывод и фрагментарные дейтаграммные уведомления.

// Code 3.17

int WSARecv(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPOWORD lpNumberOfBytesRecvd,

LPDWORD lpFlags,

LPWSAOVERLAPPED lpOveflapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE

);

Параметр s — сокет соединения. Второй и третий параметры определяют буферы для приема данных. Указатель lpBuffers ссылается на массив структур WSABUF, a dwBufferCount — определяет количество таких структур в массиве. Параметр lpNumberOfBytesReceived в случае немедленного завершения операции получения данных указывает на количество принятых этим вызовом байт. Параметр lpFlags может принимать значения MSG_PEEK, MSG_OOB, MSG_PARTIAL или результат логического ИЛИ над любыми из этих параметров.

У флага MSG_PARTIAL в зависимости от способа использования могут быть разные значения и смысл. Для протоколов, ориентированных на передачу сообщений, этот флаг задается после вызова WSARecv (если все сообщение не может быть возвращено из-за нехватки места в буфере). В этом случае каждый последующий вызов WSARecv задает флаг MSG_PARTIAL, пока сообщение не будет прочитано целиком. Если этот флаг передается как входной параметр, операция приема данных должна завершиться, как только данные будут доступны, даже если это только часть сообщения. Флаг MSG_PARTIAL используется только с протоколами, ориентированными на передачу сообщений. Запись каждого протокола в каталоге Winsock содержит флаг, указывающий на поддержку этой возможности (см. также последующие главы). Параметры lpOverlapped и lpCompletionROUTINE применяются в операциях перекрытого ввода-вывода (обсуждаются в следующей главе).

^ Функция WSARecvDisconnect

Эта функция обратна WSASendDisconnect и определена так:

// Code 3.18

int WSARecvDisconnect(

SOCKET s,

LPWSABUF lpInboundDisconnectData

);

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

^ Функция WSARecvEx

Эта функция — специальное расширение Microsoft для Winsock 1. Она идентична recv во всем, кроме того, что параметр flags передается по ссылке. Это позволяет базовому поставщику задавать флаг MSG_PARTLAL.

// Code 3.19

int PASCAL FAR WSARecvEx(

SOCKET s,

char FAR * buf,

int len,

int * flags

);

Если полученные данные не составляют полного сообщения, в параметре flags возвращается флаг MSG_PARTIAL. Он используется только с протоколами, ориентированными на передачу сообщений. Когда при принятии неполного сообщения флаг MSG_PARTIAL передается как часть параметра flags, функция завершается немедленно, вернув принятые данные. Если в буфере не хватает места, чтобы принять сообщение целиком, WSARecvEx вернет ошибку WSAEMSGSIZE, а оставшиеся данные будут отброшены. Есть разница между флагом MSG_PARTIAL и ошибкой WSAEMSGSIZE: в случае ошибки сообщение поступило целиком, однако соответствующий буфер слишком мал для его приема. Флаги MSG_PEEK и MSG_OOB также можно использовать в WSARecvEx.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: