Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование в сетях Windows

.pdf
Скачиваний:
538
Добавлен:
11.03.2015
Размер:
3.02 Mб
Скачать

miврфв>\1, при1\ладни1 о программирования wmsocK

Листинг 8-9. (продолжение)

IIШаг 7

//Привязка сокета к порту завершения

CreateIoCompletionPort((HANDLE) Accept,

CompletionPort, (DWORD) PerHandleData, 0);

//Шаг8

//Начало обработки ввода-вывода на сокете

//Отправка одного или нескольких запросов WSASendO или

//WSARecv() на сокет, используя перекрытый ввод-вывод

WSARecv( ),

Порты завершения и перекрытый ввод-вывод

После привязки описателя сокета к порту завершения можно обрабатывать запросы ввода-вывода, отправляя сокету запросы на передачу и прием Теперь вы вправе опираться на уведомления ввода-вывода порта завершения модель портов завершения использует преимущества механизма перекрытого ввода-вывода Win32, в котором вызовы функций Winsock API (таких, как WSASend и WSARecv) завершаются немедленно после вызова Затем приложение должно правильно извлечь результаты из структуры OVERLAPPED В модели портов завершения это достигается постановкой в очередь ожидания на порте завершения одного или нескольких рабочих потоков с помощью функции GetQueuedCompletionStatus

BOOL GetQueuedCompletionStatus(

 

HANDLE

CompletionPort,

 

LPDWORD

lpNumberOfBytesTransferred,

 

LPDWORD

lpCompletionKey,

 

LPOVERLAPPED * lpOverlapped,

- "«

DWORD dwMilliseconds

 

),

 

 

Параметр CompletionPort — порт завершения, на котором будет ждать поток Параметр ipNumberOfBytesTransferred принимает байты, перемещенные после операции ввода-вывода, такой как WSASend или WSARecv В параметре lpCompletionKey возвращаются данные описателя сокета, который был первоначально передан в функцию CreateloCompletionPort Как мы уже упоминали, лучше передавать через этот параметр описатель сокета В параметр lpOverlapped записывается перекрытый результат завершенной операции ввода-вывода Это действительно важный параметр — он позволяет получить данные операции ввода-вывода (per I/O-operation data) Последний параметр — dwMilliseconds, задает, сколько миллисекунд вызывающий поток будет ждать появления пакета завершения на порте (если он равен INFINITE, ожидание длится бесконечно) „ л t л_ ifc eh

Г Л А ВА 8 Ввод-вывод в Winsock

239

Данные описателя и операции

Koi да рабочий поток получает уведомление о завершении ввода-вывода от функции GetQueuedCompletionStatusi, параметры ipCompletionKey и ipOverlapped

содержат информацию о сокете, которая может быть использована для продолжения обработки ввода-вывода через порт завершения Через эти параметры получают важные данные двух типов описателя и операции

Параметр ipComplettonKey содержит данные, которые мы называем данными описателя, потому что они относятся к описателю сокета в момент перионачальной привязки к порту завершения Эти данные передаются в параметре СотрШюпКеу при вызове функции CreateloCompletionPort Как \ же упоминалось, приложение может передать через этот параметр любой i пп информации, связанной с сокетом Как правило, здесь хранится описают сокета, связанного с запросом ввода-вывода

Параметр IpOverlapped содержит структуру OVERLAPPED, за которой сле- l\ ю i так называемые данные операции В них содержатся все сведения, необходимые рабочему потоку при обработке пакета завершения (эхо-отра- жение данных, прием соединения, новый запрос на чтение и т п) Данные операции могут содержать любое количество байт вслед за структурой OVERlAPPbD, переданной в функцию ввода-вывода, которая приняла ее в качестве параметра Для этого проще всего определить свою структуру с первым эле- \iei 1 гом типа OVERLAPPED Например, мы объявляем следующую структуру для \ правления данными операции

typedef struct

 

{

 

OVERLAPPED

Overlapped,

WSABUF

DataBuf,

CHAR

Buffer[DATA_BUFSIZE],

BOOL

OperationType,

>PER_IO_OPERATION_DATA,

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

но привести вашу структуру к указателю на OVERLAPPED или просто пере- д п ь ссылку на элемент OVERLAPPED вашей структуры

PER.I0_OPERATION_DATA PerloData, // Функцию нужно вызывать так

WSARecv(socket,

,

(OVERLAPPED *)&PerIoData);

// или так

 

 

WSARecv(socket,

,

&(PerIoData Overlapped)),

Затем, когда в рабочем потоке функция GetQueuedCompletionStatus вернет чр>кгуру OVERLAPPED (и параметр СотрШюпКеу), можно определить тип запроса, который был отправлен на сокет, сняв имя с элемента структуры

2 40 ЧАСТЬ II Интерфейс прикладного программирования Wmsock

OperationType. (Для этого приведите структуру OVERLAPPED к вашему типу PER_IO_OPERATION_DATA.)Данныеобоперациивесьмаполезны,таккакпозволяют управлять несколькими операциями ввода-вывода (чтение-запись, множественное чтение, множественная запись и т. п.) на одном описателе. Может возникнуть вопрос: зачем отправлять запросы на несколько операций одновременно на один сокет? Для масштабируемости. Например, на многопроцессорной машине, где рабочие потоки используют все процессоры, несколько процессоров смогут отправлять и принимать данные через один сокет одновременно.

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

Листинг 8-10. Рабочий поток порта завершения

DWORDWINAPIServerWorkerThread(

LPVOIDCompletionPortID)

HANDLECompletionPort=(HANDLE)CompletionPortID;

DWORDBytesTransferred;

LPOVERLAPPEDOverlapped;

LPPER_HANDLE_DATAPerHandleData;

LPPER_IO_OPERATION_DATA PerloData;

DWORDSendBytes,RecvBytes;

DWORDFlags;

while(TRUE)

//Ожидание завершения ввода-вывода на любом

//из сокетов, связанных с портом завершения

GetQueuedCompletionStatus(CompletionPort,

SBytesTransferred,(LPDWORD)&PerHandleData,

(LPOVERLAPPED *) &PerIoData, INFINITE);

h// Сначала проверим, не было ли ошибки

//на сокете; если была, закрываем сокет

//и очищаем данные описателя

//и данные операции

if (BytesTransferred == 0 && (PerIoData->OperationType == RECV_POSTED || PerIoData->OperationType == SEND_POSTED))

//Отсутствие перемещенных байт (BytesTransferred)

//означает, что сокет закрыт партнером по соединению

1

/ / и нам тоже нужно закрыть сокет. Примечание:

 

)i

// для ссылки на сокет, связанный с операцией ввода-вывода,

 

// использовались данные описателя.

nub

''ОМ ,БЭ

 

 

 

 

 

 

 

 

 

 

ГЛАВА 8

Ввод-вывод в Winsock

241

Листинг 8-10.

(продолжение)

 

 

 

 

 

closesocket(PerHandleData->Socket);

 

 

 

 

GlobalFree(PerHandleData);

 

 

 

~~

 

GlobalFree(PerloData);

 

 

 

 

 

continue;

 

 

 

 

 

 

//

Обслуживание завершившегося запроса ввода-вывода.

 

 

//

Чтобы

определить,

какой запрос завершился,

 

 

//

нужно просмотреть в данных операции поле OperationType.

 

 

if

(PerIoData->OperationType

== RECV_POSTED)

 

 

{

 

 

 

 

 

 

 

 

 

 

 

// Обработка

принятых данных

 

 

 

 

// в буфере PerIoData->Buffer

 

 

 

//

Отправка нового запроса ввода-вывода

WSASend или WSARecv.

 

 

//

В качестве примера отправим еще один асинхронный запрос WSARecvO

/'

 

Flags

=

0;

 

 

 

 

 

'" "

 

//

Настройка

данных

операции

 

 

^

 

I/

для

следующего запроса

перекрытого

ввода-вывода

 

Г->ь

ZeroMemory(&(PerIoData->Overlapped),

 

 

-~<

 

 

 

sizeof(OVERLAPPED));

 

 

 

нк

 

PerIoData->DataBuf.len = DATA_BUFSIZE;

 

,st/

 

PerIoData->DataBuf.buf = PerIoData->Buffer;

 

ш

 

PerIoData->OperationType = RECV_POSTED;

 

w

 

WSARecv(PerHandleData->Socket,

 

 

 

 

&(PerIoData->DataBuf), 1, &RecvBytes,

 

f'1"

 

 

 

&Flags,

&(PerIoData->Overlapped), NULL);

 

Последний момент, не отраженный ни в листингах 8-9 и 8-10, ни на прилагаемом компакт-диске — корректное закрытие порта завершения. Это особенно важно, если один или несколько выполняющихся потоков ведут вводвывод на нескольких сокетах. Главное — не освобождать структуру OVERLAPPED, пока выполняется перекрытый запрос ввода-вывода. Лучше всего вызывать функцию closesocket для каждого описателя сокета, тогда все операции перекрытого ввода-вывода будут завершены. После закрытия всех сокетов нужно завершить все рабочие потоки порта завершения. Отправьте каждому потоку специальный завершающий пакет функцией PostQueuedCompletionStatus, это заставит поток немедленно прекратить работу:

пи1и программирования vvinsocK

BOOL PostQueuedCompletionStatus(

HANDLE CompletlonPort,

DWORD dwNumberOfBytesTransferred, DWORD dwCompletionKey, LPOVERLAPPED lpOverlapped

);

Параметр CompletlonPort — объект порта завершения, которому нужно отправить завершающий пакет Параметры dwNumberOfBytesTransferred, dwCompletionKey и lpOverlapped позволяют задать значение, которое будет записано прямо в соответствующий параметр функции GetQueuedCompletionStatus Когда рабочий поток получит эти три параметра, он сможет определить, когда следует прекращать работу, на основе специального значения, заданного в одном из трех параметров Например, можно передать 0 в параметре dwCompletionKey — рабочий поток интерпретирует это как инструкцию об окончании работы После закрытия всех рабочих потоков закройте порт завершения, вызвав функцию CloseHandle, и безопасно выйдите из программы

Как повысить эффективность ввода-вывода

Существует несколько приемов, позволяющих увеличить эффективность вво- да-вывода через сокеты с использованием портов завершения Один из них — опытным путем подобрать размер буфера сокета, чтобы увеличить производительность и масштабируемость приложения Например, приложение, которое использует один большой буфер и только один запрос WSARecv вместо трех маленьких буферов для трех запросов WSARecv, будет плохо масштабироваться на многопроцессорных машинах, поскольку с одним буфером одновременно может работать только один поток Пострадает и производительность если одновременно выполняется только одна операция приема, драйвер сетевого протокола будет недостаточно загружен То есть если вам приходится ждать завершения WSARecv перед получением новых данных, протокол будет простаивать между завершением WSARecv и следующим приемом

Существует еще один способ увеличить производительность — проанализируйте результаты использования параметров сокета SOSNDBUF и SO_RCVBUF для управления размером внутренних буферов сокета Они позволяют приложению изменять размер внутренних буферов Если приравнять их к 0, Winsock будет напрямую использовать буфер приложения во время перекрытого вызова для передачи данных в стек протокола и обратно, уменьшая межбуферное копирование Следующий фрагмент показывает, как вызывать функцию setsockopt для настройки параметра SOSNDBUF

int nZero = 0;

setsockopt(socket, SOL.SOCKET, SO_SNDBUF, (char *)&nZero, sizeof(nZero)),

Заметьте нулевой размер буферов даст положительный эффект, только если несколько запросов ввода-вывода отправляются одновременно Подробнее об этих параметрах сокета — в главе 9

ГЛАВА 8 Ввод-вывод в Winsock

243

Наконец, для соединений, где передаются небольшие порции данных, производительность можно увеличить с помощью функции AcceptEx Это позволяет приложению обслужить принятый запрос и извлечь данные одним вызовом API-функции, исключив издержки от раздельных вызовов " функций accept и WSARecv При этом запрос AcceptEx обслуживается через порт завершения, так какAcceptEx использует структуру OVERLAPPED Функция AcceptEx полезна, если планируется, что сервер будет обрабатывать небольшое количество транзакций приема-передачи после установления соединения (как на Web-сервере) Если же приложение выполняет сотни и тысячи передач данных после установления соединений, реального увеличения производительности не будет

В заключение заметим, что Winsock-приложения не должны применять \Ут32-функции ReadFile и WnteFile для обработки ввода-вывода через порт завершения Хотя эти функции используют структуру OVERLAPPED и ошибки не произойдет, функции WSARecv и WSASendлучше оптимизированы для обработки ввода-вывода в Winsock 2 Обращение к ReadFile и WnteFile ведет к множеству ненужных вызовов процедур системного ядра, переключений контекста и передачи параметров, что в итоге значительно снижает производительность

Сравнение моделей ввода-вывода

Как же при проектировании приложения выбрать модель ввода-вывода' У каждой из них свои достоинства и недостатки, и все модели сложнее в программировании, чем простой ввод-вывод с блокировкой и несколькими потоками Рассмотрим возможные решения для разработки клиентских и серверных приложений

Клиент

При разработке клиентского приложения, управляющего одним или несколькими сокетами, мы рекомендуем использовать модель перекрытого ввода-вывода или модель WSAEventSelect для увеличения производительности Впрочем, при разработке приложения Windows, работающего с сообщениями окна, модель WSAAsyncSelect может быть лучше, гак как сама опирается на модель сообщений Windows, а ваше приложение уже содержит механизм обработки сообщений

Сервер

При проектировании сервера, обрабатывающего несколько сокетов одновременно, рекомендуем модель перекрытого ввода-вывода Впрочем, если вы планируете, что сервер будет одновременно обрабатывать большое количество запросов ввода-вывода, попробуйте использовать порты завершения

Резюме

Мы рассмотрели все модели ввода-вывода, доступные в Winsock Они позволяют приложению использовать ввод-вывод Winsock в соответствии со сво-

II интерфейс прикладного программирования vvinsocK

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

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

Вглавах 9- И будут рассмо грены специальные темы Winsock Следующая глава посвящена параметрам сокетов и командам управления вводом-выво- дом, регулирующим работу как сокетов, так и базовых протоколов

Г Л А В А

Параметры сокета и команды idI управления вводом-выводом

i,'

Создав сокет, можно манипулировать его свойствами через параметры и команды управления вводом-выводом Некоторые из этих параметров просто возвращают информацию, но другие — влияют на поведение сокета в приложении Также воздействует на поведение сокета ioctl-команды В этой главе обсуждаются четыре функции Winsock getsockopt, setsockopt, ioctlsocket и WSAIoctl У каждой множество команд, в большинстве своем плохо документированных Мы рассмотрим обязательные и дополнительные параметры для каждой функции, а также поддерживающие их платформы Каждый параметр предположительно работает на всех платформах Win32 (Windows СЕ, 95, 98, NT и 2000), если иное не оговорено Поскольку интерфейс Winsock 2 дос гупен не на всех платформах, ioctl-команды и параметры из его состава не поддерживаются в Windows СЕ или Windows 95 (кроме случаев, когда для Windows 95 установлено обновление Winsock 2) Напомним, что Windows СЕ не поддерживает никакие параметры, не относящиеся к TCP/IP

Большинство ioctl-команд и параметров описаны в файлах Winsockh или Winsock2 h (в зависимости от их специфичности для Wmsock 1 или Winsock 2) Впрочем, некоторые параметры специфичны для поставщика Microsoft или для определенного транспортного протокола Расширения Microsoft описаны в Winsock h и Mswsock h Расширения поставщиков транспорта описаны в их собственных заголовочных файлах для каждого протокола Для таких параметров мы укажем соотве1ствующий заголовочный файл Приложения, использующие расширения Microsoft, нужно компоновать с библиотекой Mswsock lib

Параметры сокета

Функция getsockopt наиболее часто используется для получения информации о данном сокете

lit getsockopt ( SOCKET s, int level, int optname,

char FAR* optval, int FAR* optlen

)

IIJJUI

Первый параметр — s, он определяет сокет, с которым вы будете работать Это должен быть действительный сокет для используемого протокола Количество параметров зависит от протокола и типа сокета, хотя некоторые применимы ко всем типам сокетов Первый параметр связан со вторым — level Параметр уровня SOL_SOCKET — универсальный, не обязательно характерный для данного протокола Мы говорим «не обязательно), потому что не все протоколы реализуют все параметры сокета на уровне SOLSOCKET Например, SOJBROADCAST переводит сокет в широковещательный режим, но не все поддерживаемые протоколы реализуют широковещательные сокеты Фактически, нас интересует параметр optname

Имена параметров — постоянные значения, определенные в заголовочных файлах Wmsock Большинство общих и независимых от протокола параметров (например, с уровнем SOL_SOCKET) определены в Winsock h и Winsock2 h У каждого протокола есть свой заголовочный файл, где определены специфичные для него параметры И наконец, параметры optval и optlen — переменные, возвращаемые со значением интересующего вас параметра (как правило, это целое)

Функция setsockopt позволяет задать параметры сокета на уровне сокета или протокола

int

setsockopt (

 

SOCKET s,

-i

int

level,

(

mt

optname,

,1

const char FAR • optval,

,,int optlen

rt Параметры те же, что и у getsockopt, кроме того, что вы передаете в функцию значения параметров optval n optlen, которые присваиваются определенным параметрам сокета Как и в getsockopt, optval, как правило, целое число

Типичная ошибка вызова getsockopt или setsockopt — попытка получить информацию о сокете, чей базовый протокол не поддерживает данную характеристику Например, сокет типа SOCKSTREAM не поддерживает широковещание данных, поэтому попытка задать или получить значение параметра SO BROADCAST вызовет ошибку WSAENOPROTOOPT

УровеньSOL_SOCKET

Рассмотрим параметры сокета, возвращающие информацию на основе характеристик самого сокета Данная информация не специфична для протокола сокета

ПараметрSO_ACCEPTCONN

Этот параметр (тип optval — BOOL, версия Winsock 1+) можно только получить Если возвращается TRUE, сокет находится в режиме прослушивания

Если сокет был переведен в режим прослушивания функцией listen, возвращается TRUE Сокеты типа SOCKDGRAM не поддерживают этот параметр

Г Л А ВА 9 Параметры сокета и команды управления вводом-выводом

247

ПараметрSO_BROADCAST

Этот параметр (тип optval — BOOL, версия Winsock 1+) можно и получить, и задать Если он равен TRUE, сокет сконфигурирован для отправки или приема широковещательных сообщений

Используйте setsockopt с параметром SOJBROADCAST для включения широковещательных функций этого сокета Этот параметр допустим для всех сокетов, кроме типа SOCK_STREAM

Как уже упоминалось, широковещание — это возможность отправлять данные каждому компьютеру в локальной подсети Конечно, на каждом компьютере должен быть запущен процесс, который прослушивает входящие широковещательные данные Недостаток широковещания в том, что если множество процессов одновременно отправляют широковещательные данные, сеть может перенасытиться, из-за чего снизится ее быстродействие Для приема широковещательных сообщений, активизируйте соответствующий параметр и используйте одну из функций получения дейтаграмм, например recvfrom или WSARecvfrom Также можно подключить сокет к широковещательному адресу, вызвав connect или WSAConnect, а затем — recv или WSARecv Для широковещания по UDP нужно указать номер порта для отправки дейтаграмм, аналогично, получатель должен запросить прием широковещательных данных на том же порте Вот пример, иллюстрирующий, как отправить широковещательное сообщение по UDP

SOCKET

s,

 

BOOL

bBroadcast,

 

char

*sMsg = This is a test

,

SOCKADDR.IN

beast,

 

s = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED),

bBroadcast

= TRUE,

 

setsockopt(s, S0L_S0CKET, SO_BROADCAST,

(char *)&bBroadcast,

sizeof(BOOL)),

beast sin_family = AF_INET,

beast sin_addr s_addr = inet_addr(INADDR_BROADCAST), beast sin_port = htons(5150),

sendto(s, sMsg, strlen(sMsg), 0, (SOCKADDR *)&bcast, sizeof(bcast)),

В UDP предусмотрен специальный адрес, на который должны отправляться широковещательные данные — 255255255255 Ему соответствует константаINADDR_BROADCAST

AppleTalk также способен передавать широковещательные сообщения и также предусматривает специальный адрес для их приема В главе 6 мы упоминали, что адрес AppleTalk состоит из трех частей сеть, узел и сокет (пункт назначения) Для широковещания пункт назначения равен ATADDRBROADCAST (OxFF) — в результате дейтаграммы отправляются всем конечным точкам указанной сети

Обычно при отправке широковещательной дейтаграммы необходимо задать только парамет р SO_BROADCAST Для приема такой дейтаграммы прослушивают только входящие дейтаграммы на заданном порте Впрочем, при