Программирование в сетях Windows. Э. Джонс, Д. Оланд
Скачать 2.88 Mb.
|
Другие семейства адресов Все API-функции Winsock, представленные в этой главе, не зависят от про- токола Поэтому их легко можно применять и для других протоколов, под- держиваемых платформами Win32 Следующие разделы объясняют приме- ры клиент-серверного кода для других семейств протоколов, которые нахо- дятся на прилагаемом компакт-диске Протокол AppleTalk Единственный пример, связанный с AppleTalk, представлен для иллюстрации основных клиент-серверных технологий и поддерживает как РАР, так и ADSP Протокол РАР ориентирован на сообщения, не требует соединения, и не надежен Этим он похож на UDP, но есть и два важных отличия РАР поддер- живает фрагментарные сообщения, то есть функция WSARecvEx может вер- нуть лишь часть дейтаграммного сообщения При этом необходимо прове- рить значение флага MSG_PARTIAL, чтобы узнать, нужно ли дополнительно вызывать функцию для получения остатков сообщения Кроме того, необхо- димо задавать специфичные для протокола РАР параметры сокета перед каж- дым чтением (Подробнее о параметре SOJ4UME_READ, используемом совме- стно с функцией setsockopt, — в главе 9 ) Ознакомьтесь с примером A talk с на прилагаемом компакт-диске, где иллюстрируется проверка флага MSGPAR- TIAL и порядок использования параметра SOPRIMEREAD Протокол ADSP требует соединения, потоковый, надежный и во многом похож на TCP API-вызовы для AppleTalk практически не отличаются от пред- ставленных в этой главе примерах для UDP и TCP Разница лишь в разреше- 2 0 2 ЧАСТЬ II Интерфейс прикладного программирования Winsock нии имен. Помните, что для AppleTalk перед поиском или регистрацией име- ни необходимо выполнить привязку к пустому адресу (см. главу 6). У протокола AppleTalk есть ограничение: его поддержка появилась в Win- sock 1.1, а когда был разработан Winsock 2, оказалось, что с AppleTalk не ра- ботают некоторые новые функции. Использование любой из WSASend- или WSARecv-функций может привести к непредсказуемым результатам, например к возврату отрицательного количества байт. Данная проблема изложена в ста- тье Q164565 базы знаний Microsoft Knowledge. Исключение составляет функ- ция WSARecvEx, которая просто вызывает recv, а в параметре ввода-вывода/я^ можно просмотреть значение флага MSG'PARTIAL после выхода. Инфракрасные сокеты Поддержка IrDA появилась недавно, в Windows СЕ, 98 и 2000. Протокол IrDA требует соединения, потоковый и надежный. Снова вопрос в разрешении имен, которое значительно отличается от принятого в IP. Необходимо знать и еще одно отличие: на платформах Windows СЕ можно использовать толь- ко функции Winsock 1.1 для инфракрасных сокетов, так как Windows СЕ под- держивает только спецификацию Winsock 1.1. В Windows 98 и 2000 допус- тимо применение функций, специфичных для Winsock 2. Код примера ис- пользует только функции Winsock 1.1. В Windows 98 и 2000 необходимо заг- рузить библиотеку Winsock 2.2 или более позднюю, потому что поддержка семейства адресов AFJRDA в более ранних версиях не доступна. Пример кода для инфракрасных сокетов приведен в файлах Ircommon.h, Ircommon.c, Irclient.c и Irserver.c. Первые два файла просто определяют две общие функции: одну — для отправки, другую — для приема данных. Они используются как клиентским, так и серверным приложениями. Клиентская часть подробно расписана в файле Irclient.c. Сначала нумеруются все устрой- ства в зоне видимости. Затем следуют попытки подключиться к каждому из них с заданным именем службы. Дальнейшая работа ведется с первым уст- ройством, принявшим соединение. Клиент отправляет данные и считывает их обратно. Серверная часть описана в файле Irserver.c. Сервер создает ин- фракрасный сокет, выполняет привязку заданного имени службы к сокету и ожидает подключений клиентов. Для каждого клиента порождается поток для приема данных и отправки их обратно. Заметьте: данные примеры написаны для Windows 98 и 2000. Подобно примерам по TCP/IP, их нужно немного изменить для запуска в Windows СЕ. Также следует учесть два уже упоминавшихс ограничения Windows СЕ: эта ОС не поддерживает консольные приложения и требует кодировать строки в UNICODE. Интерфейс с NetBIOS В главе 1 мы говорили, что NetBIOS совместима с несколькими разными транс- портами, применяемыми в Winsock, а в главе 6 — обсудили, как регистрировать совместимые с NetBIOS транспорты и сокеты на основе любого из них. Для каждой комбинации «протокол — адаптер» есть две записи: SOCK_DGRAM и SOCK_SEQPACKET, соответствующих не требующим соединения дейтаграммам Г Л А В А 7 Основы Wmsock 203 и потоковым сокетам, которые весьма похожи на сокеты UDP и TCP. Кроме раз- решения имен работа с Winsock-интерфейсом NetBIOS ничем не отличается от описанной в этой главе. Помните, что хорошо написанный сервер должен про- слушивать все доступные LANA, а клиент со своей стороны — пытаться устано- вить соединение по всем LANA. Первые два примера на компакт-диске — Wsnbsvr.c и Wsnbclnt с. Они ис- пользуют потоковые сокеты SOCKSEQPACKET. Сервер создает сокет для всех LANA, которые нумеруются функцией WSAEnumProtocols, и привязывает его к стандартному имени сервера. После установления клиентом соединения, сервер создает поток для его обслуживания. С этого момента поток просто читает входящие данные и возвращает их клиенту. Аналогично, клиент пы- тается подключиться по всем LANA. После первого успешного соединения другие сокеты закрываются. Затем клиент отправляет данные серверу, а сер- вер снова их возвращает. В файле Wsnbdgs.c приведен код для отправки и приема дейтаграммных сообщений (через сокет с типом SOCKJDGRAM). Этот протокол не требует соединения, поэтому для отправки сообщений серверу используются все доступные транспорты, так как заранее неизвестно, какие из них поддержи- вает сервер. Кроме того, в примере реализована поддержка уникальных, групповых и широковещательных сообщений (см. главу 1). Протокол IPX/SPX В файле Sockspx.c приведен пример использования протокола IPX, а также потокового и последовательного SPXII. В одном примере реализован отпра- витель и приемник для всех трех протоколов. Конкретный протокол зада- ется при помощи параметра -р в командной строке. Пример прост и легок в освоении. Функция main анализирует аргументы, а затем вызывает функ- цию Server или Client. Для протокола SPXII, ориентированного на соедине- ния, это значит, что сервер привязывает сокет к адресу внутренней сети и ожидает соединений с клиентом, который, в свою очередь, пытается устано- вить соединение с указанным в командной строке сервером. После установ- ления соединения отправка и прием данных идут обычным образом. Для не требующего соединения протокола IPX пример еще проще. Сер- вер просто выполняет привязку к внутренней сети и ожидает входящие дан- ные, вызывая функцию recvfrom. Клиент отправляет данные получателю, ука- занному в командной строке, функцией sendto. Два раздела этого примера требуют разъяснения. Функция FilllpxAddress отвечает за кодирование указанного в командной строке в кодировке ASCII IPX-адреса в структуру SOCKADDRJPX. Как упоминалось в главе б, IPX-адре- са представляются в виде шестнадцатеричных строк, то есть каждый символ адреса фактически занимает 4 бита в адресных полях структуры SOCKA- DDRJPX. Функция FilllpxAddress получает IPX-адрес и вызывает функцию AtoH, которая и выполняет преобразование. Еще одна функция — EnumerateAdapters, выполняется, если в командной строке задан флаг —т. Для подсчета количества доступных локальных IPX- адресов она использует параметр сокета IPX_MAX_ADAPTER_NUM, а затем для 2 0 4 ЧАСТЬ II Интерфейс прикладного программирования Winsock получения каждого адреса вызывает параметр сокета IPXADDRESS. Мы ис- пользуем их, потому что в нашем примере IPX-адреса заданы напрямую, раз- решение имен не выполняется (подробнее — в главах 9 и 10). Протокол ATM Протокол ATM доступен в Winsock под управление Windows 98 и 2000. Пример для ATM состоит из файлов Wsockatm.c, Support.c, and Support.h. Два последних файла содержат вспомогательные подпрограммы, используемые в Wsockatm.c и обеспечивающие регистрацию локальных ATM-адресов и их перекодирова- ние. ATM-адреса шестнадцатеричные, как и IPX-адреса, поэтому мы применя- ем уже знакомую функцию AtoH. Для получения количества локальных АТМ- интерфейсов используется команда SIO_GET_NUMBERJDF_ATM_DEV1CES, а затем при помощи команды SIO_GET_ATM ADDRESS мы получаем фактический адрес (подробнее — в главе 9)- В отличие от описанных ранее примеров, клиентское и серверное при- ложение представлено одним файлом — Wsockatm.c. Так как ATM поддержи- вает только подключения, требующие соединения, пример небольшой и большинство кода находится в функции main. Сервер выполняет привязку к определенному локальному интерфейсу и ожидает подключений клиентов, которые обрабатываются в том же потоке, что и слушающий сокет. Это оз- начает, что сервер может одновременно обслуживать только одного клиен- та. Клиент вызывает connect с ATM-адресом сервера. После установления со- единения начинается обмен данными. А теперь несколько предостережений. После вызова сервером функции WSAAccept будет выведен адрес клиента. Однако к моменту получения серве- ром запроса на соединение адрес клиента еще неизвестен, потому что функ- ция accept не устанавливает соединение до конца. Это справедливо и для кли- ентской стороны — клиентский вызов для установления соединения с серве- ром признается успешным, даже если соединение до конца не установлено. Это означает, что после вызова connect или accept немедленная отправка мо- жет сорваться без выдачи предупреждений. К сожалению, приложение не спо- собно узнать, когда соединение полностью готово к использованию. Вдоба- вок, ATM поддерживает только резкое завершение: при вызове функции clo- sesocket соединение немедленно разрывается. Для протоколов, не поддержи- вающих плавное завершение, при вызове closesocket данные, ожидающие в очереди на любом конце соединения, обычно отбрасываются. Это вполне приемлемое поведение. Впрочем, поставщик ATM действует предусмотритель- но: при закрытии одной из сторон своего сокета во время передачи данных, Winsock все же возвращает данные из приемной очереди этого сокета. Резюме В этой главе обсуждались основные функции Winsock, необходимые для обмена данными по различным протоколам. Мы предоставили данную ин- формацию в контексте только одной модели ввода-вывода: блокирующих сокетов. В следующей главе мы рассмотрим другие модели ввода-вывода, доступные в Winsock. Г Л А В А Ввод-вывод в Winsock Эта глава посвящена управлению вводом-выводом через сокеты в Windows- приложениях. В Winsock такое управление реализовано с помощью режимов работы и моделей ввода-вывода. Режим (mode) сокета определяет поведение функций, работающих с сокетом. Модель (model) сокета описывает, как при- ложение производит ввод-вывод при работе с сокетом. Модели не зависят от режима работы и позволяют обходить их ограничения. Winsock поддерживает два режима: блокирующий (blocking) и неблоки- рующий (nonblocking). Эти режимы подробно описаны в начале главы, здесь же демонстрируется их использование в приложениях для управления вво- дом-выводом. Далее приведен ряд интересных моделей, которые помогают приложению управлять несколькими сокетами одновременно в асинхрон- ном режиме: select, WSAAsyncSelect, WSAEventS'elect, перекрытый ввод-вывод (overlapped I/O) и порт завершения (completion port). В конце главы рас- сматриваются достоинства и недостатки различных режимов и моделей, а также обсуждается выбор вариантов, наиболее подходящих для конкретных случаев. Все платформы Windows поддерживают блокирующий и неблокирую- щий режимы работы сокета. Впрочем, конкретная платформа может поддер- живать не все модели. В Windows СЕ доступна лишь одна модель ввода-вы- вода, в Windows 98 и Windows 95 — большинство моделей, кроме портов завершения (поддержка конкретной модели зависит от версии Winsock). В Windows NT и Windows 2000 доступны все модели (табл. 8-1). Табл. 8-1. Поддерживаемые модели ввода-вывода Платформа Windows СЕ Windows 95 (Winsock 1) Windows 95 (Winsock 2) Windows 98 Windows NT Windows 2000 Select Async Да Да Да Да Да Да WSA- Event Select Нет Да Да Да Да Да WS А - ввод- Select Нет Нет Да Да Да Да Перекрытый Port вывод Нет Нет Да Да Да Да Порт завершения Нет Нет Нет Нет Да Да 2 0 6 ЧАСТЬ II Интерфейс прикладного программирования Winsock Режимы работы сокетов В блокирующем режиме функции ввода-вывода, такие как send и recv, перед завершением ожидают окончания операции. В неблокирующем — работа функций завершается немедленно. Приложения, выполняемые на платфор- мах Windows СЕ и Windows 95 (в случае Winsock 1), поддерживают очень мало моделей ввода-вывода и требуют от программиста описать блокирова- ние и разблокирование сокетов в разных ситуациях. Блокирующий режим При блокировке сокета необходима осторожность, так как этом режиме лю- бой вызов функции Winsock именно блокирует сокет на некоторое время. Большинство приложений Winsock следуют модели «поставщик — потреби- тель», в которой программа считывает или записывает определенное количе- ство байт и затем выполняет с ними какие-либо операции (листинг 8-1). Листинг 8-1. Простейший пример блокировки сокета SOCKET sock; char buff[256]; int done = 0; while(ldone) nBytes = recv(sock, buff, 65); if (nBytes == S0CKET_ERR0R) printf("recv failed with error Xd\n" WSAGetLastErrorQ); Return; OoComputationOnData(buff); } Проблема в том, что функция recv может не завершиться никогда, так как для этого нужно считать какие-либо данные из буфера системы. В такой си- туации некоторые программисты могут соблазниться «подглядыванием» данных (чтение без удаления из буфера), используя флаг MSG_PEEK в recv или вызывая ioctlsocket с параметром FIONREAD. Подобный стиль програм- мирования заслуживает резкой критики. Издержки, связанные с «подгляды- ванием», велики, так как необходимо сделать один или более системных вы- зовов для определения числа доступных байт, после чего все равно прихо- дится вызывать recv для удаления данных из буфера. Чтобы этого избежать, следует предотвратить замораживание приложе- ния из-за недостатка данных (из-за сетевых проблем или проблем клиента) Г Л А В А 8 Ввод-вывод в Wmsock 207 без постоянного «подглядывания» в системные сетевые буферы. Один из ме- тодов — разделить приложения на считывающий и вычисляющий потоки, совместно использующие общий буфер данных. Доступ к буферу регулиру- ется синхронизирующим объектом, таким как событие или мьютекс. 31да- ча считывающего потока — постоянно читать данные из сети и поме ч ать их в общий буфер. Считав минимально необходимое количество данных, этот поток инициирует сигнальное событие, уведомляющее вычисляющий поток, что можно начинать вычисления. Затем вычисляющий поток удаля- ет часть данных из буфера и производит с ними необходимые операции. В листинге 8-2 реализованы две функции: для приема данных (ReadThread) и их обработки (ProcessThread). Листинг 8-2. Пример многопоточного программирования в режиме блокировки // Перед созданием двух потоков, // инициализируется общий буфер (data) // и создается сигнальное событие (hEvent) CRITICAL.SECTION data; HANDLE hEvent; TCHAR buff[MAX_BUFFER_SIZE]; int nbytes; // Считывающий поток void ReadThread(void) { int nTotal = 0, nRead = 0, nLeft = 0, nBytes = 0; while (!done) { nTotal = 0; nLeft = NUM_BYTES_REQUIRED; while (nTotal != NUM_BYTES_REQUIRED) { EnterCriticalSection(&data); nRead = recv(sock, &(buff[MAX_BUFFER_SIZE - nBytes]), nLeft); if (nRead == -1) { pnntf("error\n"); ExitThreadO; nTotal += nRead; nLeft -= nRead; nBytes += nRead; Ы см.след.стр- 2 0 8 ЧАСТЬ 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) { |