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

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

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

2 08

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

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

LeaveCriticalSection(&data),

>

SetEvent(hEvent);

// Вычисляющий поток void ProcessThread(void)

{

WaitForSingleObject(hEvent),

EnterCriticalSection(&data),

DoSomeComputationOnData(buff),

//Удаление обработанных данных из буфера

//и сдвиг оставшихся в начало массива nBytes -= NUM_BYTES_REQUIRED,

LeaveCnticalSection(&data),

}

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

Неблокирующий режим

Альтернатива описанному режиму — режим без блокировки Он несколько сложнее в использовании, но обеспечивает те же возможности, что и режим блокировки, плюс некоторые преимущества В листинге 8-3 показано, как создать сокет и перевести его в неблокирующий режим

Листинг 8-3. Создание сокета без блокировки

SOCKET s, unsigned long ul = 1, int nRet,

s = socket(AF_INET, SOCK_STREAM, 0);

nRet = ioctlsocket(s, FIOBIO, (unsigned long *) &ul); if (nRet == SOCKET_ERROR)

{

// если не удалось перевести сокет в неблокирующий режим

}

Если сокет находится в неблокирующем режиме, функции Winsock завершаются немедленно В большинстве случаев они будут возвращать ошибку

I

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

209

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

щWSAAccept и accept — приложение не получило запрос на соединение, повторите вызов для проверки наличия запросов,

щclosesocket скорее всего, функция setsockopt была вызвана с параметром SOJJNGER и задан ненулевой тайм-аут,

WSAConnect и connect — соединение инициировано, для проверки завершения повторите вызов,

щ WSARecv, recv, WSARecvFrom и recvfrom — данные не были приняты, повторите проверку позже,

Ш WSASend, send, WSASendTo и sendto в буфере нет места для записи исходящих данных, попробуйте вызвать функцию позже

Так как большинство вызовов в неблокирующем режиме будут возвращать ошибку WSAEWOULDBLOCK, анализируйте все возвращаемые коды ошибок и будут готовы прервать операцию в любой момент К сожалению, многие программисты непрерывно вызывают функцию до успешного завершения Между тем, для чтения 200 байт данных создание цикла, состоящего лишь из вызова recv, ничуть не лучше, чем вызов в блокирующем режиме с флагом MSG_PEEK, обсуждавшийся ранее Модели ввода-вывода Winsock могут помочь приложению определить, когда сокет доступен для чтения и записи

У каждого режима работы сокета свои достоинства и недостатки Режим блокировки проще концептуально, но в нем сложнее обрабатывать несколько сокетов одновременно или нерегулярные потоки данных С другой стороны, режим без блокировки требует больше кода для обработки ошибки WSAEWOULDBLOCK в каждом вызове Модели ввода-вывода сокетов помогают приложению асинхронно обрабатывать соединения на одном и более сокетах

Моделиввода-выводасокетов

Приложения Winsock могут использовать пять моделей для управления вво- Дом-выводом select, WSAAsyncSelect, WSAEventSelect, перекрытый ввод-вывод и порты завершения В этом разделе объясняются особенности каждой модели и основные принципы управления сокетами На прилагаемом компактдиске приведены примеры приложений, показывающие разработку простей-

е г о TCP-сервера с учетом принципиальных особенностей каждой модели

Модельselect

амодель наиболее широко доступна в Winsock Мы называем ее select, по-

Учто управление вводом-выводом в ней основано на использовании Ункц select Идея восходит к модели сокетов Беркли для Unix Эта модель

210

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

была встроена в Winsock 1.1, чтобы дать приложениям, избегающим блокировки при вызове сокета, возможность управлять несколькими сокетами в определенном порядке. Так как Winsock версии 1.1 совместим с Беркли приложение, использующее сокеты Беркли и функцию select, в принципе может выполняться в Windows без модификации.

Функцию select используют и для определения, есть ли в сокете данные и можно ли туда записать новые. Основная цель функции — избежать блокировки приложения при связанных с вводом-выводом вызовах, например send или recv, когда сокет работает в блокирующем режиме, и предотвратить появление ошибки WSAEWOULDBLOCK — в неблокирующем. Функция select блокирует операции ввода-вывода, пока не будут соблюдены условия, заданные в качестве параметров. Прототип функции select:

int select( int nfds,

fd_set FAR * readfds,

нfd_set FAR * writefds, fd set FAR • exceptfds,

const struct timeval FAR * timeout

);

Первый параметр — nfds, игнорируется, он включен лишь для совместимости с приложениями Беркли. Заметьте, что есть три параметра с типом fdset: один для проверки возможности чтения — readfds, другой для проверки возможности записи — writefds и третий для срочных (out of band, OOB) данных — exceptfds. Тип fdset представляет набор сокетов. Набор readfds определяет сокеты, удовлетворяющие одному из следующих условий:

• данные доступны для чтения; Ш соединение закрыто, сброшено или завершено;

• если вызвать функцию listen, когда соединение находится в состоянии ожидания, вызов функции accept будет успешным.

Набор writefds определяет сокеты, удовлетворяющие одному из следующих условий-

Ш возможна отправка данных;

• если обрабатывается неблокирующий вызов соединения, попытка соединения удалась.

Наконец, набор exceptfds определяет сокеты, удовлетворяющие одному из следующих условий:

• если обрабатывается неблокирующий вызов соединения, попытка соединения не удалась;

Ш ООВ-данные доступны для чтения.

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

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

211

fSy exceptfds) могут быть NULL (но хотя бы один должен быть не NULL). Любой ненулевой набор должен содержать хотя бы один описатель сокета, иначе функции select будет нечего ожидать. Последний параметр — timeout, представляет собой указатель на структуру timeval, определяющую, сколько времени select будет ждать окончания ввода-вывода. Если timeout равен NULL, select будет ждать, пока не найдет хотя бы один описатель, отвечающий заданному критерию. Структура timeval определена так:

struct timeval

{

long tv_seo; long tv_usec;

};

Поле tv_sec задает время ожидания в секундах, а поле tv_usec — в миллисекундах. Тайм-аут {0, 0} означает, что функция select должна завершаться немедленно, позволяя приложению определить ее результат. Впрочем, этого следует избегать. При успешном завершении select возвращает в структу- paxfd_set общее количество описателей сокетов, у которых есть ожидающие операции ввода-вывода. Если время timeval истекает, возвращается 0. В случае любой неудачи select возвращает SOCKETJ1RROR.

Перед тем как отслеживать сокеты с помощью select, ваше приложение должно сформировать одну или все структурыfd_set, присвоив набору описатели. Добавив сокет в один из наборов, вы сможете узнать, происходила ли конкретная операция ввода-вывода с этим сокетом. В Winsock определены следующие макросы для работы с наборамиу#_$е£

FD_CLR(s, *set) удаляет сокет 5 из набора set;

ШFD_ISSET(s, *set) проверяет, входит ли сокет s в набор set; li FD_SET(s, *set) — добавляет сокет s в набор set;

ШFDZERO(*set) инициализирует set как пустой набор.

Например, если вы хотите узнать, можно ли читать данные из сокета без блокировки, добавьте его в набор fdjread при помощи макроса FD_SET и вызовите select. Чтобы проверить, остался ли сокет в этом наборе, используйте макрос FDJSSET. Вот типичный алгоритм применения select для работы с одним или несколькими сокетами.

1 • Инициализируйте все интересующие вас fdset макросом FDZERO.

Добавьте описатели сокетов в соответствующие наборы fdjset макросом

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

спользуя результат работы select, приложение может определить, какие сокеты осуществляют ввод-вывод в данное время, проверяя каждыйfd_set макросом FD ISSET.

n 11.

Ч А С I ь II Интерфейс прикладного программирования Winsock

____

5 Выявив активные сокеты, обработайте их ввод-вывод и продолжите с шага 1

По завершении работы функция select удаляет из каждой структуры/rf set описатели сокетов, не участвующих в операциях ввода-вывода Этим объясняется необходимость использовать макросы FDISSETm шаге 4, чтобы определить, является ли конкретный сокет частью набора В листинге 8-4 показаны основные этапы реализации модели select для одного сокета Для нескольких сокетов нужно обработать список или массив дополнительных сокетов

Листинг 8-4. Применение модели select для управления вводом-выводом через сокет

SOCKET s, fd_set fdread, mt ret,

//Создание сокета и установление соединения

//Управление вводом-выводом сокета while(TRUE)

{

//Всегда очищайте набор перед вызовом

//selectO

FD_ZERO(&fdread),

i

// Добавление сокета s к набору для проверки чтения

с

* if ((ret = select(0, &fdread, NULL, NULL, NULL))

==SOCKET ERROR)

&/I Обработка ошибки

с}

h if (ret > 0)

n

{

Ш

// В этом простейшем случае selectO должна вернуть 1

щ/I Приложение, работающее с несколькими сокетами,

//может получить большую величину

//Здесь должна быть проверка,

//входит ли сокет в набор

if (FD_ISSET(s, &fdread))

{

// Через сокет s идет чтение

}

,

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

213

МодельWSAAsyncSelect

Winsock поддерживает полезную асинхронную модель ввода-вывода, позволяющую приложению получать информацию о событиях, связанных с сокетом, при помощи сообщений Windows Это достигается вызовом функции WSAAsyncSelect после создания сокета Данная модель первоначально появилась в приложениях Winsock 1 1 для облегчения взаимодействия приложений в многозадачной среде 16-битных платформ, таких как Windows for Workgroups Но она полезна и для современных приложений, особенно если они обрабатывают сообщения окон в стандартной процедуре {ivmproc) Эта модель также используется объектом CSocket из библиотеки классов Microsoft (Microsoft Foundation Class, MFC)

Уведомления о сообщениях

Прежде чем использовать модель WSAAsyncSelect, приложение должно создать окно, используя функцию CreateWmdow, и процедуру обработки сообщений (ivmproc) для этого окна Можно использовать диалоговое окно с диалоговой процедурой (так как это частный случай окна) Здесь достаточно продемонстрировать простое окно с дополнительной процедурой Создав инфраструктуру окна, вы вправе создавать сокеты и активизировать уведомления вызовом функции WSAAsyncSelect

int WSAAsyncSelect( SOCKET s,

HWND hWnd, unsigned int wMsg, long lEvent

Параметр s — интересующий нас сокет Параметр hWnd — описатель окна (или диалога), которое должно получить уведомление, когда произойдет сетевое событие Параметр wMsg определяет сообщение, которое будет в этом случае отправлено окну с описателем hWnd Обычно сообщению присваивается код выше WMJJSER, чтобы избежать совпадения сетевых сообщений со стандартными сообщениями окна Последний параметр — lEvent, задает битовую маску, определяющую комбинацию сетевых событий, которые нужно отслеживать Эти события принимают уведомления о

к

FDREAD — готовности к чтению,

*

 

FDJWRITE готовности к записи,

 

FDOOB — получении срочных данных,

» |Я1

 

FO_ACCEPT — входящих соединениях,

ж

FDCONNECT завершении соединения или многоточечной операции join,

F^CLOSE — закрытии сокета,

— изменении QoS,

прикладного программирования winsocK

Я FDGROUPQOS — изменении QoS (зарезервировано для будущего использования группами сокетов),

FD ROUTING INTERFACECHANGE изменении интерфейса марщру. тизации для указанных адресов,

FDADDRESSIISTCHANGE — изменении списка локальных адресов для семейства протокола сокета

Вбольшинстве случаев нужно отслеживать события типов FD READ, FD WRITE, FD_ACCEPT, FDJCONNECTYL FDJOLOSE Конечно, обработка событий

FD_ACCEPT пли FDjOONNECT зависит от того, является приложение клиентом или сервером Если приложению нужно отслеживать несколько типов событий, присвойте параметру lEvent значение, полученное побитовым ИЛИ над масок соответствующих типов

WSAAsyncSelect(s, hwnd, WM_S0CKET,

FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);

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

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

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

LRESULT

CALLBACK WindowProc(

 

HWND

hWnd,

 

UINT

uMsg,

 

WPARAM

wParam,

'"

LPARAM

lParam

 

Здесь параметр hWnd — описатель окна, вызвавшего оконную процеДУРУ Параметр uMsg обозначает сообщение, которое нужно обработать. В данном случае мы будем перехватывать сообщение, определенное в вызове WSAAsync~ Select. Параметр wParam определяет сокет, на котором произошло сетевое событие. Он необходим, если к одной оконной процедуре привязано несколь

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

215

сокетов Параметр IParam состоит из двух частей младшее слово указывапроизошедшее событие, а старшее — содержит код ошибки.

Когда процедура окна получает сообщение о сетевом событии, она в первую очередь проверяет старшее слово параметра IParam, чтобы определить, не было ли ошибки Существует специальный макрос — WSAGETSELECTERROR, возвращающий код ошибки в старшем слове После этого нужно определить тип события, инициировавшего сообщение, а для этого — прочитать младшее слово IParam Значение этого слова возвращает макрос WSAGETSELECTEVENT

В листинге 8-5 показана обработка сообщения окна при использовании модели WSAAsyncSelect Выделены последовательные шаги, необходимые для разработки любого сервера и опущены фрагменты, требующиеся для полноценной функциональности в среде Windows.

Листинг 8-5. Программирование сервера в модели WSAAsyncSelect

«define WM_S0CKET WM_USER + 1 «include <windows.h>

int WINAPI WinMain(HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

SOCKET Listen;

HWND Window;

// Создание окна и привязка процедуры ServerWinProc

Window = CreateWmdowO;

// Запуск Winsock и создание сокета

WSAStartup(...);

Listen = SocketO;

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

InternetAddr.sin_family = AF_INET;

InternetAddr.sm_addr.s_addr = htonl(INADDR_ANY);

InternetAddr.sin_port = htons(5150);

bind(Listen, (PSOCKADDR) ilnternetAddr,

n

 

sizeof(InternetAddr));

"„..

//

Настройка уведомлений с сокета,

i 01

 

//

используя сообщение WM_S0CKET, определенное

выше

WSAAsyncSelect(Listen, Window, WM_S0CKET,

FD.ACCEPT | FD_CLOSE);

listenUisten, 5);

:ЭТ1Я1ЦН«sea

см. след. стр.

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

I/ Трансляция и обработка сообщения окна до окончания работы приложения

BOOL CALLBACK ServerWinProc(HWND hDlg,WORD wMsg, WORD wParam, DWORD lParam)

{

SOCKET Accept;

switch(wMsg)

{

case WM_PAINT:

// Обработка сообщений прорисовки окна break;

case WM_SOCKET:

//Определение возможных ошибок

//макросом WSAGETSELECTERRORO

if (WSAGETSELECTERROR(lParam))

{

// Вывод сообщения об ошибке и закрытие сокета closesocket(wParam);

break;

// Определение типа произошедшего события

switch(WSAGETSELECTEVENT(lParam))

{

case FD_ACCEPT:

// Прием входящего соединения Accept = accept(wParam, NULL, NULL);

 

 

// Подготовка сокета принятого соединения для отправки

 

 

// уведомлений о чтении,

записи и закрытии

"Ч'.м

WSAAsyncSelect(Accept,

hwnd, WM_SOCKET,

•У

1

FD.READ | FD_WRITE

| FD_CLOSE);

к

»Hi

break;

4 < W

,case FD_READ:

//Прием данные из сокета в wParam break;

case FD_WRITE:

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

217

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

II Сокет wParam готов отправлять данные break;

case FD_CLOSE:

// Закрытие соединения closesocket(wParam); break;

break;

return TRUE;

}

При обработке уведомления о событии FDWRITE важно следующее. Это уведомление отправляется только в одном из трех случаев:

И после подключения к сокету функцей connect win WSAConnect; Ж после приема соединения функцией accept или WSAAccept;

Ш когда функции send, WSASend, sendto или WSASendTo возвращают ошибку WSAEWOULDBLOCK и место в буфере освобождается.

Поэтому приложение должно полагать, что запись в сокет возможна, начиная с первого уведомления FDWRITE и до тех пор, пока send, WSASend, sendto или WSASendTo не вернет ошибку WSAEWOULDBLOCK. После этого нужно ждать следующего уведомления FD_WRITE, извещающего, что запись в сокет снова возможна.

МодельWSAEventSelect

В Winsock поддерживается еще одна полезная модель асинхронного вводавывода, позволяющая получать уведомления о сетевых событиях на сокетах. Эта модель похожа на WSAAsyncSelect — приложение получает и обрабатывает те же события. Но есть и отличие — сообщения отправляются описателю объекта «событие», а не процедуре окна.

Уведомления о событиях

Модель WSAEventSelect требует, чтобы приложение создало объект «событие» Для каждого сокета, вызвав функцию WSACreateEvent:

WSAEVENTWSACreateEvent(void);

Она возвращает описатель объекта «событие». Получив описатель объек- а> нужно связать его с сокетом и зарегистрировать те типы сетевых событий, которые надо отслеживать (см. список в разделе «Модель WSAAsyncSelect. Уведомления о сообщениях»). Это достигается вызовом функции WSAEventSelect-

l n t WSAEventSelect(

 

 

SOCKET s,

l W (

i a i J

WSAEVENT hEventObject,

"'