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

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

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

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

Параметр wVersionRequested задает версию загружаемой библиотеки Winsock. Старший и младший байты определяют дополнительный и основной номер версии библиотеки соответственно. Для получения значения параметра wVersionRequested можно использовать макрос MAKEWORD(x, у), где х

старший байт, а у — младший.

Параметр ipWSAData — указатель на структуру LPWSADATA, которая при вызове функции WSAStartup заполняется сведениями о версии загружаемой библиотеки:

typedef

struct

WSAData

WORD

 

 

 

«version;

WORD

 

 

 

wHighVersion;

char

 

 

 

szDescription[WSADESCRIPTION_LEN

char

 

 

 

szSystemStatus[WSASYS_STATUS_LEN

unsigned

short

IMaxSockets;

unsigned

short

iMaxUdpDg;

char

FAR

*

 

lpVendorlnfo;

} WSADATA, FAR

* LPWSADATA;

+1];

+1];

WSAStartup присваивает параметру wVersion значение загружаемой версии. Параметр wHighVersion содержит номер последней доступной версии Winsock. Помните, что в обоих полях старший байт определяет дополнительный, а младший — основной номер версии. Поля szDescription и szSystemStatus заполняются не во всех реализациях Winsock и практически не применяются.

Не используйте и поля iMaxSockets и iMaxUdpDg. Предполагается, что в них заданы максимальное количество одновременно открытых сокетов и максимальный размер дейтаграммы. Для определения последнего следует запросить сведения о протоколе, вызвав функцию WSAEnumProtocols. Максимальное количество одновременно открытых сокетов зависит от свободной физической памяти. Наконец, поле lpVendorlnfo зарезервировано для информации изготовителя реализации Winsock и не используется ни на одной из платформ Win32.

Разные платформы Windows поддерживают следующие версии Winsock: Windows 95—1.1 (2.2); Windows 98, NT 4.0, 2000 — 2.2; Windows СЕ — 1.1.

Важно различать основные версии библиотеки. Winsock 1.x не поддерживает многие расширенные возможности Winsock, описанные в этом разделе. К тому же, для использования в приложении Winsock 1 необходимо подключить файл Winsock.h, а для Winsock 2 — Winsock2.h.

ПРИМЕЧАНИЕ Обновление Winsock 2 для Windows 95 можно найти по адресу http://www.microsoft.com/windows95/downloads/.

Даже если платформа поддерживает Winsock 2, не обязательно использовать самую последнюю версию. Напротив, если необходимо, чтобы приложение поддерживалось несколькими платформами, возьмите за основу Winsock 1-1- Такое приложение будет отлично работать на платформе Windows NT 4.0, потому что все вызовы Winsock 1.1 имеются в Winsock 2 DLL.

Г Л А ВА 7 Основы Winsock

, 159

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

Например, в Windows NT 4.0 при использовании программой модели асинхронных оконных событий после каждого успешного выполнения функции send или WSASend асинхронно выдается сообщение FDWRITE, что указывает на возможность записи данных. Однако в спецификации говорится, что событие FDJWR1TE выдается, когда приложение готово отправлять данные (например, сразу после запуска), и что FD_WRITE означает: следует продолжать запись, пока не будет выдана ошибка WSAEWOULDBLOCK. В действительности, после того как система отправит все ожидающие обработки данные и приготовится обрабатывать очередные вызовы send и WSASend, она отправит окну приложения событие FDJWRITE-. значит, в этот момент вы можете возобновить запись данных в сеть (статья Q186245 в базе знаний). Эта проблема устранена в четвертом пакете обновлений для Windows NT 4.0 и 2000.

В большинстве случаев при написании новых приложений следует загружать последнюю доступную версию библиотеки Winsock. Если будет выпущена версия 3, приложение, использующее версию 2.2, должно выполняться корректно. При запросе более поздней версии Winsock, не поддерживаемой вашей платформой, WSAStartup вернет ошибку, а в поле wHighVersion структуры WSADATA появится номер последней версии библиотеки, поддерживаемой данной системой.

Проверка и обработка ошибок

Проверка и обработка ошибок играют весомую роль при написании Win- sock-приложения. Функции Winsock достаточно часто возвращают ошибки, но как правило, не критические — передачу информации можно продолжать. Большинство функций Winsock при ошибке вызова возвращают значение SOCKET_ERROR, но так происходит не всегда. При подробном рассмотрении API-вызовов мы обратим внимание на возвращаемые значения, соответствующие ошибкам. Константа SOCKET'J5RROR на самом деле равна - 1 . Для получения более информативного кода ошибки, возникшей после одного из вызовов Winsock, задействуйте функцию WSAGetLastError.

int WSAGetLastError (void);

Эта функция возвращает код последней ошибки. Всем кодам ошибок, возвращаемым WSAGetLastError, соответствуют стандартные константные значения. Они описаны в Winsockh или в Winsock2.h (в зависимости от версии insock). Единственное различие этих заголовочных файлов — Winsock2.h соДержит больше кодов ошибок новых API-функций. Константы, определенные

Для кодов ошибок директивой *define, обычно начинаются с префикса WSAE.

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

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

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

СерверныеAPI-функции

Сервер — это процесс, который ожидает подключения клиентов для обслуживания их запросов. Сервер должен прослушивать соединения на стандартном имени. В TCP/IP таким именем является IP-адрес локального интерфейса и номер порта. У каждого протокола своя схема адресации, а потому и свои особенности именования. Первый шаг установления соединения — привязка сокета данного протокола к его стандартному имени функцией bind. Второй — перевод сокета в режим прослушивания функцией listen. И наконец, сервер должен принять соединение клиента функцией accept или

WSAAccept.

Рассмотрим каждый API-вызов, необходимый для привязки, прослушивания и установления соединения с клиентом. Базовые вызовы, которые клиент и сервер должны сделать для установления канала связи, иллюстрирует рис. 7-1.

СерверWinsock

КлиентWinsock

sacket/WSASocket

i d

И

С i

 

emoeet/WSACotmect

Рис. 7-1. Основные этапы работы клиента и сервера Winsock

Функция bind

После создания сокета определенного протокола следует связать его со стандартным адресом, вызвав функцию bind:

int bind(

 

 

SOCKET

 

s,

const struct sockaddr FAR* name,

 

int

'

namelen

Г Л А ВА 7 Основы Wmsock

(161

Параметр s задает сокет, на котором вы ожидаете соединения клиентов. Второй параметр с типом struct sockaddr — просто универсальный буфер, фактически, в этот буфер вы должны поместить адрес, соответствующий стандартам используемого протокола, а затем при вызове bind привести его к типу struct sockaddr. В заголовочном файле Winsock определен тип SOCKADDR, соответствующий структуре struct sockaddr. Далее в главе этот тип будет использоваться для краткости Последний параметр задает размер переданной структуры адреса, зависящей от протокола. Например, следующий код иллюстрирует привязку при ТСР-соединении:

SOCKET

s;

struct sockaddr_in

tcpaddr;

i n t

port = 5150;

s = socket(AF_INET, SOCK.STREAM, IPPROTO_TCP);

tcpaddr.sin_family = AF_INET; tcpaddr.sin_port = htons(port); tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);

bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));

Подробнее о структуре sockaddrjn — в разделе, посвященном адресации TCP/IP, главы 6. Там приведен пример создания потокового сокета и последующей настройки структуры адреса TCP/IP для приема соединений клиентов. В данном случае сокет указывает на IP-интерфейс по умолчанию с номером порта 5150. Формально вызов blind связывает сокет с IP-интерфейсом и портом.

При возникновении ошибки функция bind возвращает значение SOCKET_ERROR. Самая распространенная ошибка при вызове bind WSAEADDRINUSE. В случае использования TCP/IP это означает, что с локальным IPинтерфейсом и номером порта уже связан другой процесс, или они находятся в состоянии TIMEJVA1T. При повторном вызове bind дня уже связанного сокета возвращается ошибка WSAEFAULT.

Функцияlisten

Теперь нужно перевести сокет в режим прослушивания. Функция bind только ассоциирует сокет с заданным адресом. Для перевода сокета в состояние ожидания входящих соединений используется API-функция listen-.

int listen(

SOCKET s,

int backlog

Первый параметр — связанный сокет. Параметр backlog определяет максимальную длину очереди соединений, ожидающих обработки, что важно Ри запросе нескольких соединений сервера. Пусть значение этого параметРавно 2, тогда при одновременном приеме трех клиентских запросов

1 62

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

первые два соединения будут помещены в очередь ожидания, и приложение сможет их обработать Третий запрос вернет ошибку WSAECONNREFUSED После того как сервер примет соединение, запрос удаляется из очереди, а другой — занимает его место Значение backlog зависит от поставщика протокола Недопустимое значение заменяется ближайшим разрешенным Стандартного способа получить действительное значение backlog нет

Ошибки, связанные с listen, довольно просты Самая частая из них — WSAEINVAL, обычно означает, что перед listen не была вызвана функция bind Иногда при вызове listen возникает ошибка WSAEADDRINUSE, но чаще она происходит при вызове bind

ФункцииacceptиWSAAccept

Итак, все готово к приему соединений клиентов Теперь вызовем функцию accept или WSAAccept Прототип accept

SOCKET accept( SOCKET s,

struct sockaddr FAR* addr, int FAR* addrlen

),

Параметр s — связанный сокет в состоянии прослушивания Второй параметр — адрес действительной структуры SOCKADDRJN, a addrlen — ссылка на длину структуры SOCKADDRJN Для сокета другого протокола замените SOCKADDR_IN на структуру SOCKADDR, соответствующую этому протоколу Вызов accept обслуживает первый находящийся в очереди запрос на соединение По его завершении структура addr будет содержать сведения об IPадресе клиента, отправившего запрос, а параметр addrlen — размер структуры

Кроме того, accept возвращает новый дескриптор сокета, соответствующий принятому клиентскому соединению Для всех последующих операций с этим клиентом должен применяться новый сокет Исходный прослушивающий сокет используется для приема других клиентских соединений и продолжает находиться в режиме прослушивания

В Winsock 2 есть функция WSAAccept, способная устанавливать соединения в зависимости от результата вычисления условия

SOCKETWSAAccept( SOCKET s,

struct sockaddr FAR * addr, LPINT addrlen,

LPCONDITIONPROC lpfnCondition, DWORD dwCallbackData

),

Первые три параметра — те же, что и в accept для Winsock 1 Параметр lpfnCondition — указатель на функцию, вызываемую при запросе клиента Она определяет возможность приема соединения и имеет следующий прототип

' '•"'

- эч-ммвф<чтг> -чп» ГЛАВА 7 Основы Winsock

163

\

\

int CALLBACK ConditionFunc( LPWSABUF lpCallerld, LPWSABUF lpCallerData,

( LPQOS lpSQOS,

,LPQOS lpGQOS, LPWSABUF lpCalleeld,

LPWSABUF lpCalleeData, 'f GROUP FAR * g,

DWORD dwCallbackData

),

Передаваемый по значению параметр lpCallerld содержит адрес соединяющегося объекта Структура WSABUF используется многими функциями Winsock 2 и определена так

typedef struct „WSABUF { u_long len,

char FAR • buf,

}WSABUF, FAR . LPWSABUF,

Взависимости от ее использования, поле len определяет размер буфера, на который ссылается поле buf или количество данных в буфере buf

Для lpCallerld параметр buf указывает на структуру адреса протокола, по которому осуществляется соединение Чтобы получить корректный доступ к информации, просто приведите указатель buf к соответствующему типу SOCKADDR При использовании протокола TCP/IP это должна быть структура SOCKADDR_IN, содержащая IP-адрес подключающегося клиента Большинство сетевых протоколов удаленного доступа поддерживают идентификацию абонента на этапе запроса

Параметр lpCallerData содержит данные, отправленные клиентом в ходе запроса соединения Если эти данные не указаны, он равен NULL Имейте в виду, что большинство сетевых протоколов, таких как TCP/IP, не используют данные о соединении Чтобы узнать, поддерживает ли протокол эту возможность, обратитесь к соответствующей записи в каталоге Winsock путем вызова функции WSAEnumProtocols (см также главу 5)

Следующие два параметра — lpSQOS и lpGQOS, задают уровень качества обслуживания, запрашиваемый клиентом Оба параметра ссылаются на структуру, содержащую сведения о требованиях пропускной способности для приема и передачи Если клиент не запрашивает параметры качества обслуживания (quality of service, QoS), то они равны NULL Разница между ними в Том, что ipSQoS используется для единственного соединения, a lpGQOS — для групп сокетов Группы сокетов не реализованы и не поддерживаются в Winsock 1 и 2 (Подробнее о QoS — в главе 12 )

Параметр lpCalleeld — другая структура WSABUF, содержащая локальный аДрес, к которому подключен клиент Снова поле buf указывает на объект SOCKADDR соответствующего семейства адресов Эта информация полезна, е<-ли сервер запущен на многоадресной машине Помните, что если сервер связан с адресом 1NADDR_ANY, запросы соединения будут обслуживаться на Любом сетевом интерфейсе, а параметр — содержать адрес интерфейса, ПРИНЯВШЕГО ГПРЛ1то,„„

1 64

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

Параметр ipCalleeData дополняет ipCallerData Он ссылается на структу ру WSABUF, которую сервер может использовать для отправки данных клиенту в ходе установления соединения Если поставщик услуг поддерживает эту возможность, поле len указывает максимальное число отправляемых байт В этом случае сервер копирует некоторое, не превышающее это значение, количество байт, в блок buf структуры WSABUF и обновляет поле len, чтобы показать, сколько байт передается Если сервер не должен возвращать данные о соединении, то перед возвращением условная функция приема соединения присвоит полю len значение 0 Если поставщик не поддерживает передачу данных о соединении, поле len будет равно 0 Опять же, большинство протоколов фактически, все, поддерживаемые платформами Win32

— не поддерживают обмен данными при установлении соединения Обработав переданные в условную функцию параметры, сервер должен

решить принимать, отклонять или задержать запрос соединения Если соединение принимается, условная функция вернет значение CF_ACCEPT, если отклоняется — CFJREJECT Если по каким-либо причинам на данный момент решение не может быть принято, возвращается CF_DEFER

Как только сервер готов обработать запрос, он вызывает функцию WSAAccept Заметьте, что условная функция выполняется в одном процессе с WSAAccept и должна работать как можно быстрее В протоколах, поддерживаемых платформами Win32, клиентский запрос задерживается, пока не будет вычислено значение условной функции В большинстве случаев базовый сетевой стек ко времени вызова условной функции уже может принять соединение А при возвращении значения CFJREJECT стек просто закрывает его Сейчас мы не будем углубляться в детали использования условной функции принятия соединения — см главу 12

При возникновении ошибки возвращается значение INVALID_SOCKET, чаще всего — WSAEWOULDBLOCK Оно возникает, если сокет находится в асинхронном или неблокирующем режиме и нет соединения для приема Если условная функция вернет CF_DEFER, WSAAccept генерирует ошибку

WSATRY_AGAIN,еслиCF_REJECT-WSAECONNREFUSED

API-функции клиента

Клиентская часть значительно проще и для установления соединения требуется всего три шага создать сокет функцией socket или WSASocket, разрешить имя сервера (зависит от используемого протокола), инициировать соединение функцией connect или WSAConnect

Из материалов главы 6 вы уже знаете, как создать сокет и разрешить имя IP-узла, так что единственным оставшимся шагом является установление соединения В главе 6 также рассматривались способы разрешения имен и для других семейств протоколов

Состояния TCP

Для работы с Winsock не обязательно знать о состояниях TCP, но с их помощью можно лучше понять, что происходит с протоколом при

ГЛАВА 7 Основы Wmsock

165

вызовах API-функций Winsock К тому же, многие программисты сталкиваются с одними и теми же проблемами при закрытии сокетов, состояния TCP при этом представляют наибольший интерес

Начальное состояние любого сокета — CLOSED Как только клиент инициирует соединение, серверу отправляется пакет SYN и клиентский сокет переходит в состояние SYN_SENT Получив пакет SYN, сервер отправляет пакет SYN-and-ACK, а клиент отвечает на него пакетом АСК С этого момента клиентский сокет переходит в состояние ESTABLISHED Если сервер не отправляет пакет SYN-ACK, клиент по истечении времени ожидания возвращается в состояние CLOSED

Если сокет сервера связан и прослушивает локальный интерфейс и порт, то он находится в состоянии LISTEN При попытке клиента установить соединение сервер получает пакет SYN и отвечает пакетом SYN-ACK Состояние сокета сервера меняется на SYN_RCVD Наконец, после отправки клиентом пакета АСК сокет сервера переводится в состояние ESTABLISHED

Существует два способа закрыть соединение Если этот процесс начинает приложение, то закрытие называется активным, иначе — пассивным На рис 7-2 изображены оба вида закрытия При активном закрытии соединения приложение отправляет пакет FIN Если приложение вызывает closesocket win shutdown (со вторым аргументом SD_ SEND), оно отправляет узлу пакет FIN, и состояние сокета меняется на FIN_WAIT_1 Обычно узел отвечает пакетом АСК и сокет переходит в состояние FIN_WAIT_2 Если узел тоже закрывает соединение, он отправляет пакет FIN, а компьютер отвечает пакетом АСК и переводит сокет в состояние TIME_WAIT

Активное закрытие сокета

Пассивное закрытие сокета

closesocket

recv

FIN

send FIN

send

ACK

V

T

 

 

I

T

 

Рис. 7-2. Состояния закрытия сокета TCP

Состояние TIMEWAIT также называется состоянием ожидания MSL MSL — максимальное время жизни сегмента (Maximum Segment rtetime), иными словами, время существования пакета в сети перед его Торасыванием У каждого IP-пакета есть поле времени жизни (time- °-live, TTL) Если оно равно О, значит, пакет можно отбросить Каждый

см след стр

166

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

маршрутизатор, обслуживающий пакет, уменьшает значение TTL на 1 и передает пакет дальше Перейдя в состояние TIMEWAIT, приложение остается в нем на протяжении двух периодов времени, равных MSL Это позволяет TCP в случае потери заключительного пакета АСК послать его заново, с последующей отправкой FIN По истечении 2MSL сокет переходит в состояние CLOSED

Результат двух других способов активного закрытия — состояние TIME_WAIT В предыдущем случае только одна сторона отправляла FIN и получала ответ АСК, а узел оставался свободным для передачи данных до момента своего закрытия Здесь и возможны два других способа В первом случае, при одновременном закрытии, компьютер и узел одновременно запрашивают закрытие компьютер отправляет узлу пакет FIN и получает от него пакет FIN

Затем в ответ на пакет FIN компьютер отправляет пакет АСК и изменяет состояние сокета на CLOSING После получения компьютером пакета АСК от узла сокет переходит в состояние TIME_WAIT

Второй случай активного закрытия является вариацией одновременного закрытия сокет из состояния FIN_WAIT_1 сразу переходит в состояние TIMEJWAIT Это происходит, если приложение отправляет пакет FIN и тут же после этого получает от узла пакет FIN-ACK В таком случае узел подтверждает пакет FIN приложения отправкой своего, на которое приложение отвечает пакетом АСК

Основной смысл состояния TIMEWAIT заключается в том, что пока соединение ожидает истечения 2MSL, сокетная пара, участвующая в соединении, не может быть использована повторно Сокетная пара — это комбинация локального и удаленного IP портов Некоторые реализации TCP не позволяют повторно использовать любой из портов сокетной пары, находящейся в состоянии TIME_WAIT В реализации Microsoft этого дефекта нет Впрочем, при попытке соединения с сокетной парой, находящейся в состоянии TIMEWAIT, произойдет ошибка WSAEADDRINUSE Одно из решений проблемы (кроме ожидания окончания состояния TIME_WAIT пары сокетов, использующей локальный порт) — использовать параметр сокета SO_REUSEADDR Более подробно SOJREUSEADDR рассматривается в главе 9

И наконец, рассмотрим пассивное закрытие По этому сценарию приложение получает от узла пакет FIN и отвечает пакетом АСК. В этом случае сокет приложения переходит в состояние CLOSE_WAIT Так как узел закрыл свою сторону, он больше не может отправлять данные, но приложение вправе это делать, пока не закроет свою сторону соединения Для закрытия своей стороны приложение отправляет пакет FIN, после чего ТСР-сокет приложения переводится в состояние LAST_ACK После получения от узла пакета АСК сокет приложения возвращается в состояние CLOSED

Подробне о протоколе TCP/IP — в RFC 793 Этот и другие RFC доступны по адресу http //wivwrfc-editororg

/

~ , - „ , й р

ГЛАВА 7 Основы Winsock

167

 

функцииconnectиWSAConnect

Нам осталось обсудить собственно установление соединения Оно осуществляется вызовом connect или WSAConnect Сначала рассмотрим версию Winsock 1 этой функции

int connect( SOCKET s,

const struct sockaddr FAR* name, int namelen

),

Параметры практически не требуют пояснений s — действительный ТСР-сокет для установления соединения, пате — структура адреса сокета (SOCKADDRIN) для TCP, описывающая сервер к которому подключаются, namelen — длина переменной пате Версия Winsock 2 этой функции определена так

int WSAConnect( SOCKET s,

const struct sockaddr FAR * name, int namelen,

LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS,

LPQOS lpGQOS

),

Первые три параметра такие же, как и в функции connect Следующие два lpCallerData и lpCalleeData, — это строковые буферы, используемые для приема и отправки данных в момент установления соединения Параметр lpCallerData указывает на буфер, содержащий данные, отправляемые клиентом серверу вместе с запросом на соединение, lpCallerData — на буфер с данными, возвращаемыми сервером в ходе установления соединения Обе переменные являются структурами WSABUF, и для lpCallerData поле len должно указывать длину данных передаваемого буфера buf В случае lpCalleeData поле len определяет размер буфера buf куда принимаются данные от сервера Два последних параметра lpSQOS и lpGQOS, — ссылаются на структуры QoS, определяющие требования пропускной способности отправки и приема данных устанавливаемого соединения Параметр lpSQOS указывает требования к сокету 5, a lpGQOS — к группе сокетов На данный момент группы сокетов не поддерживаются Нулевое значение lpSQOS означает, что прило Жение не предъявляет требований к качеству обслуживания

Если на компьютере, к которому вы подключаетесь, не запущен процесс, прослушивающий данный порт, функция connect вернет ошибку WSAECONNREFUSED Другая ошибка — WSAETIMEDOUT, происходит, когда вызываемый аДресат недоступен, например, из-за отказа коммуникационного оборудовали на пути к узлу или отсутствия узла в сети