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

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

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

488

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

прослушивающим сокетом Затем процедура отключает кнопку Close Listen, которая активируется, лишь когда сервер снова начнет прослушивать клиентские соединения

Последняя часть процедуры настраивает элемент управления LtstView с именем istStates Он отображает состояние каждого используемого в настоящий момент элемента управления Winsock Код добавляет записи для клиентского и серверного элемента управления, и они становятся элементами 1 и 2, соответственно Все прочие динамически загружаемые элементы управления Winsock добавляются после этих двух записей Имя записи о серверном элементе управления — Local Server Как и в примере с протоколом UDP, процедура настраивает таймер для управления частотой обновления состояний сокетов По умолчанию триггеры таймеров обновляются каждые полсекунды

Теперь давайте рассмотрим две кнопки, используемые сервером Первая — Listen, инициирует несложное действие Обработчик этой кнопки — cmdListen, задает свойству значение, введенное пользователем в поле txtServerPort Поле с номером локального порта наиболее важно для прослушивающего сокета Именно к порту с этим номером клиенты пытаются подключиться, чтобы установить соединение Задав свойство LocalPort, код вызывает метод Listen Как только обработчик кнопки Listen переведет элемент управления sockServer в прослушивающее состояние, программа начнет ожидать, когда для элемента управления sockServer наступит событие ConnectionRequest, указывающее, что клиент пытается установить соединение Кнопка Close Listen закрывает элемент управления sockServer Обработчик кнопки Close Listen вызывает метод Close для элемента sockServer(0), запрещая установление соединений клиентами

Наиболее важное событие для TCP-сервера — ConnectionRequest, оно обрабатывает входящие запросы клиентов Поступивший от клиента запрос на соединение можно обработать двумя способами Первый — воспользоваться непосредственно сокетом сервера Вызовите метод Accept для элемента управления сервера с параметром requestID, который передается обработчику события — это пример действий SockTCP Недостаток данного метода — прослушивающий сокет будет закрыт и другие клиенты не смогут установить соединение с сервером

Помните, что нулевой элемент массива элементов управления Winsock является прослушивающим сокетом Прежде всего просмотрите массив и найдите элемент управления с состоянием «закрыт» (например, в запросе укажите, что значение свойства State должно равняться sckClosed) Конечно, на первом этапе вы не обнаружите свободных элементов управления, поскольку они вообще не загружены В этом случае первый цикл не выполняется и значение переменной freeSock будет по-прежнему равно О, сообщая, что свободные элементы управления не обнаружены Следующие этапы динамически загружают новый элемент управления Winsock, увеличивая значение счетчика Serverlndex (место в массиве, куда следует загрузить элемент управления), и затем выполняют следующий оператор

Load sockServer(Serverlndex)

Г Л А ВА 15 Элемент управления Winsock

489

Теперь, когда новый элемент Winsock загружен, процедура может вызвать метоц Accept с указанным идентификатором запроса (request ID) Оставшиеся операторы добавляют новую запись в элемент управления ListView с именем IstStates, чтобы приложение могло отображать текущее состояние нового элемента управления Winsock

Если состояние уже загруженного элемента Winsock — «закрыт>, процедура повторно воспользуется данным элементом управления, вызвав для него метод Accept Непрерывная загрузка и выгрузка элементов управления занимает много процессорного времени и снижает производительность Кроме того, при выгрузке элемента управления Winsock может возникнуть утечка памяти (подробней — далее в этой главе)

Оставшиеся серверные функции просты Событие sockServer_Close наступает, когда клиент вызывает метод Close Тогда сервер закрывает со своей стороны сокет и обнуляет запись об IP-адресе в элементе управления ListView, присваивая ей значение «- •> и задавая номер порта как -1 Функция sockServer_DataAmval выделяет буфер для приема данных, а затем вызывает метод GetData для считывания информации В результате в список IstMessages добавляется сообщение Последняя серверная функция — обработчик события Error При ошибке обработчик выводит соответствующее сообщение и закрывает элемент управления

ТСР-клиент

Теперь рассмотрим код клиентской части Единственная инициализация, выполняемая клиентом в процедуре Form_Load, — выбор протокола TCP для элемента управления sockdient He входящие в код инициализации три обработчика командных кнопок связаны с клиентом и некоторыми обработчиками событий Первая кнопка — Connect, ее обработчику — cmdConnect_Chck LocalPort, задано значение 0, поскольку номер порта, выбранного на локальном компьютере, не важен Параметры RemoteHost и RemotePort задаются в соответствии со значениями полей txtServerName и txtPort Это вся информация, необходимая для установки TCP-соединения с сервером

Единственное, что осталось сделать — вызвать метод Connect После этого элемент управления Winsock будет находиться в процессе разрешения имени или установки соединения (состояние элемента управления — sckResolvingHost, sckResolved ъиы sckConnectmg) Когда соединение установлено, состояние изменяется на sckConnected и наступает событие Connect Далее рассматриваются различные состояния элемента управления и переходы между ними

После установления соединения вызывается обработчик sockChent_Connect, который активирует кнопки Send Data и Disconnect Кроме того, для записи Local Client в элементе управления ListView с именем IstStates будет обновлен номер порта локального компьютера, через который устанавливается соединение Теперь вы можете принимать и передавать данные

Существуют и два других обработчика событий sockChent Close и sockChent_Error Обработчик события sockChent_Close лишь закрывает элемент управления Winsock, а обработчик события sockChent JLrror — выводит окно сообщения с описанием ошибки и затем закрывает элемент управления

490

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

Две оставшихся части кода клиента — командные кнопки Send Data и Disconnect. Кнопку Send Data обрабатывает подпроцедура cmdSendData_ Click. Если состояние элемента управления Winsock — «подключен», процедура вызывает метод SendData, передавая ему строку из поля txtSendData. Кнопку Disconnect обрабатывает подпроцедура cmdDisconnect_Click. Данный обработчик лишь закрывает клиентский элемент управления, возвращает некоторые кнопки в исходное состояние, а также обновляет запись Local Client в элементе управления istStates.

Получение информации о состоянии элемента управленияWinsock

Последняя часть примера, использующего протокол TCP, — рамка группы Winsock Information. Мы уже обсуждали подобную рамку, однако для ясности вкратце рассмотрим ее повторно.

Как и в примере с UDP, таймер управляет обновлением информации о текущем состоянии всех загруженных элементов управления Winsock. Периодичность обновления по умолчанию — 500 миллисекунд. При загрузке в элемент управления Listview с именем IstStates добавляются две записи. Первая — надпись Local Client, соответствующая клиентскому элементу управления Winsock sockClient. Вторая — Local Server, относится к прослушивающему сокету. При установлении нового клиентского соединения динамически загружается новый элемент управления Winsock и в элемент управления sckStates добавляется новая запись — IP-адрес клиента. После отключения клиента запись возвращается в исходное состояние — IP-адрес «—.—.—.—», номер порта — (-1). Конечно, подключающийся новый клиент повторно использует неиспользуемые элементы из серверного массива. Так же отображаются IP-адрес и имя локального компьютера.

Запуск ТСР-приложения

Запуск ТСР-приложения не вызывает каких-либо трудностей. Запустите по экземпляру приложения на трех отдельных компьютерах. При использовании TCP не имеет значения, сколько сетевых адаптеров установлено на компьютерах, поскольку оптимальный интерфейс для данного ТСР-соединения выбирает таблица маршрутизации. В одном из приложений щелкните кнопку Listen. Вы увидите, что значение записи Local Server в элементе управления ListView с именем State Information изменилось с sckClosed на sckListening, а номер порта задан как 5150. Теперь сервер может принимать клиентские запросы на соединение.

На одном из клиентов задайте полю Server Name значение, соответствующее имени компьютера, на котором выполняется первый экземпляр приложения (прослушивающий сервер) и затем щелкните кнопку Connect. Элемент управления, которому в клиентском приложении соответствует запись Local Client из списка State Information, теперь находится в состоянии sckConnected, а в качестве номера порта, через который установлено соединение, отображается неотрицательное число. Кроме того, на сервере в список

Г Л А В А 15 Элемент управления Winsock

491

State Information добавляется запись — IP-адрес только что подключившегося клиента. Состояние новой записи — sckConnected, она также содержит номер порта сервера, с которым установлено соединение.

Теперь введите текст в поле Message клиентского приложения и несколько раз щелкните кнопку Send Data. Вы увидите сообщения, появляющиеся в окне списка Messages серверного приложения. После этого отключите клиент, щелкнув кнопку Disconnect. На клиентском компьютере записи Local Client из списка State Information будет назначено состояние sckClosed, a номер порта задан как — 1. На сервере запись, соответствующая клиенту, не удаляется, а лишь помечается как неиспользуемая. Ее имя задается, как IP-ад- рес «—.—.---.—», номер порта — как -1 и состояние — как sckClosed.

На третьем компьютере введите в поле Server Name имя прослушивающего сервера и установите клиентское подключение. Вы получите те же результаты, что и в случае с первым клиентом, но для обработки второго клиента сервер будет использовать тот же элемент управления Winsock. Если состояние элемента Winsock — «закрыт», он может применяться для принятия любых входящих соединений. Последнее, что мы советуем сделать, — используйте клиент на стороне сервера, чтобы установить соединение локально. После того, как соединение будет установлено, в список Socket Information добавится новая запись. Единственное отличие в том, что теперь указан IPадрес, соответствующий IP-адресу сервера. Поработайте с клиентскими и серверными приложениями, чтобы понять, как они взаимодействуют и какие результаты дает выполнение каждой из команд.

СостояниеТСР-сокетов

Использовать элемент управления Winsock с протоколом TCP значительно сложнее, чем работать с UDP-сокетами, поскольку здесь гораздо больше стояний сокетов. На рис. 15-4 приведена диаграмма состояний ТСР-сокета. Начальное состояние по умолчанию — sckClosed. Переходы между состояниями просты и не требуют каких-либо комментариев, за исключением состояния sckClosing.

Из-за частичного закрытия TCP метод SendData может перейти из этого состояния в другое двумя способами. Как только одна из сторон ТСР-соеди- нения вызовет метод Close, она больше не сможет передавать данные. Другая сторона соединения получает событие Close и переходит в состояние sckClosing, но по-прежнему может передавать информацию. Именно поэтому для метода SendData существует два пути перехода из состояния sckClosing. Если сторона, вызвавшая метод Close, пытается вызвать SendData, генерируется ошибка и состояние элемента управления Winsock изменяется на sckError. Сторона, получившая событие Close, может свободно передавать информацию и принять все оставшиеся данные.

Ограничения

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

492

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

тичных приложений Эти ошибки имеются в последней версии Winsock для Visual Basic 5.0, которая представляет собой новую версию элемента управления из второго пакета обновлений.

-Close

Close

Accept

Bind

Connect

Listen

SendData

GetData

PeekData

Рис. 15-4. Диаграмма состояний ТСР-сокета

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

Вторая ошибка связана с закрытием сокетного соединения, до того как в сеть будут переданы все запрошенные данные. В некоторых случаях при вызове метода Close после события SendData (когда Close обрабатывается раньше SendData) имеет место потеря данных, по крайней мере, с точки зрения получателя. Чтобы обойти эту проблему, перехватывайте событие SendComplete (оно наступает, после того как метод SendData завершит передачу данных в сеть). Кроме того, транзакции приема-передачи можно организовать так, чтобы получатель, приняв все ожидаемые данные, вызывал метод Close первым. В результате на принимающем компьютере наступит событие Close, извещающее, что все данные получены и соединение можно закрыть.

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

Г Л А ВА 15 Элемент управления Winsock

493

давать данные блоками, размер которых не превышает 1000 байт. Передав буфер, подождите, пока наступит событие SendComplete, прежде чем передать следующий буфер с данными. Это неудобно, но значительно повышает надежность элемента управления.

В новейшей версии Winsock, поставляемой с Visual Basic 6.0, исправлены все упомянутые ошибки, за исключением второй. Если вызвать команду Close после вызова метода SendData, сокет немедленно закроется, не передав ка- кие-либо данные. Было бы просто здорово, если бы программисты Microsoft исправили и эту ошибку, хотя она наименее серьезная и сложная.

Типичные ошибки

Как мы уже не раз говорили, в процессе работы приложение может столкнуться с достаточно ограниченным числом ошибок Winsock. Мы не будем рассматривать их все, а обсудим лишь наиболее распространенные: Local address in use (Локальный адрес уже используется) и Invalid operation at current state (Неверное действие в текущем состоянии).

Ошибка Local address in use

Эта ошибка наступает, если связать элемент управления с локальным портом при помощи методов Bind или Connect, при том что порт уже используется. Это наиболее распространенная ошибка TCP-сервера, который всегда связывается с конкретным портом, чтобы клиенты могли обнаружить службу.

Если перед завершением работы приложения, использующего сокет, последний не будет должным образом закрыт, он на короткий период времени перейдет в состояние TIME_WAIT, чтобы гарантировать, что через этот порт отправлены (переданы) все данные. При попытке связать другой элемент Winsock с этим портом генерируется ошибка Local address in use.

Распространенная ошибка на стороне клиента приводит к такой же ситуации. Если свойству LocalPort задается значение 0 и затем устанавливается соединение, свойству LocalPort задается значение, соответствующее номеру локального порта, через который клиент установил соединение. Если вы собираетесь в дальнейшем установить новое соединение, используя данный элемент управления, не забудьте вернуть свойству LocalPort значение 0. В противном случае, если предыдущее соединение не будет корректно закрыто, возможна ошибка Local address in use.

Ошибка Invalid Operation at Current State

Эта ошибка также широко распространена и возникает, если вызвать метод элемента управления Winsock, выполнение которого не допускается текущим состоянием элемента. На рис. 15-2 и 15-4 приведены диаграммы состояний UDP- и ТСР-сокетов. Для создания устойчивого кода всегда проверяйте состояние сокета, прежде чем вызвать какой-либо метод.

Ошибки Winsock генерируются в результате наступления события Error. Это те же ошибки, что возникают при прямом программировании Winsock.

494

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

Подробное описание ошибок Winsock см. в главе 7 или в приложении С, где перечислены коды.

ЭлементуправленияWindowsCEWinsock

В комплекте инструментов Visual Basic Toolkit for Windows CE (VBCE) имеется элемент управления Winsock, предоставляющий большинство функций «стандартного» элемента управления Winsock, поставляемого с Visual Basic. Основное его отличие — протокол UDP не поддерживается, но обеспечивается поддержка протокола IrDA. Кроме того, требуются некоторые незначительные изменения в коде приложения.

Как упоминалось в главе 7, Windows CE не поддерживает асинхронную модель Winsock, и элемент управления Windows CE Winsock не исключение из этого правила. Главное различие при программировании — метод Connect является блокирующим, а события Connect — нет. Как только вы попытаетесь установить соединение, вызвав метод Connect, вызов будет блокирован вплоть до установления соединения или возвращения ошибки.

Кроме того, комплект инструментов VBCE 1.0 не поддерживает массивы элементов управления, а значит, потребуется изменить код серверной части, приведенный в листинге 15-2. В результате единственный простой способ обработки нескольких соединений — разместить на форме несколько элементов управления Windows CE Winsock. На деле это ограничивает максимальное число параллельных клиентских соединений, которое приложение может обработать, поскольку такое решение вообще не масштабируется.

Кроме того, у события ConnectionRequest нет параметра RequestlD, что может показаться странным. В результате придется вызвать метод Accept для элемента управления, которому будет передано соединение. Запрос на соединение, вызывающий событие ConnectionRequest, обрабатывается получающим этот запрос элементом управления.

Пример

Рассмотрим вкратце приложение, использующее элемент управления Windows СЕ Winsock. Элемент Windows CE Winsock работает аналогично стандартному элементу управления Winsock, за исключением описанных различий. В листинге 15-3 приведен код, использующий элемент управления Windows СЕ Winsock.

Листинг 15-3. Пример приложения, использующего элемент управления Windows CE Winsock

Option Explicit

Эта глобальная переменная позволяет сохранить текущее состояние переключателей. Значение 0 соответствует протоколу TCP, a 2 - протоколу IrDA (инфракрасный). Учтите, что на данный момент элемент

управления не поддерживает протокол UDP. Public SocketType

 

 

 

 

 

 

 

 

 

.Элемент управления

 

495

Листинг 15-3.

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

 

 

-'•-vf Ъ

 

 

 

 

 

 

 

 

 

 

 

 

Private Sub cmdCloseListen_Click()

)4ol

 

-во dug •

' Закройте прослушивающий сокет, и верните Я»тим кнопкам

волani

'

начальное

состояние

 

 

 

 

»н >

з i

 

 

 

WinSocki.Close

 

 

 

 

 

 

 

 

 

 

 

cmdConnect.Enabled

=

True

 

 

 

 

 

 

cmdListen.Enabled

=

True

 

 

 

 

>яэ

 

cmdDisconnect.Enabled

=

False

 

 

 

 

 

 

 

 

 

cmdSendData.Enabled =

False

«ei

 

 

 

cmdCloseListen.Enabled

=

False

 

 

 

МУ

End Sub

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Private

Sub

cmdConnect_Click()

 

 

 

 

 

'

Проверьте

выбранный

тип

сокета и инициируйте данное

соединение

 

,о »i

 

If SocketType

= 0 Then

 

 

 

 

 

 

 

 

 

' Задайте

протокол,

а также имя и номер

порта

удаленного компьютера

 

 

WinSocki.Protocol

=

0

 

 

 

 

 

 

 

WinSocki.RemoteHost

=

txtServerName.Text

nertT S « ^

 

ПьгВ

 

 

WinSocki.RemotePort

= Clnt(txtPort.Text)

S » Joooio

 

 

 

 

WinSocki.LocalPort

=

0

 

 

 

 

 

 

WinSocki.Connect

 

 

 

 

 

 

 

 

 

Elself SocketType = 2 Then

 

i »м «отчдохян

 

 

 

 

- Выберите протокол IrDA и задайте имя службы

 

 

 

 

 

WinSocki.Protocol

= 2

 

 

>rtT (S

» eJs*a Г

 

 

'WinSocki.LocalPort

=0

 

^ a fte£dsfi3.lor

 

 

'WinSocki.ServiceName

= txtServerName.Text le'j

я beliiuoi,m<

 

50

 

 

 

J3

 

 

WinSockLRemoteHost

=

txtServerName.Text

 

b©io«n3.n«J«iJ(,

 

 

 

 

 

 

WinSocki.Connect

 

 

 

 

 

• !пв**л

о? sidenU

 

End If

 

 

 

 

 

 

 

 

 

 

f l

 

'

Убедитесь,

что

соединение успешно установлено;

вели ето так,

 

 

 

1

включите/отключите некоторые команды

 

 

 

 

HsgBox WinSocki.State

If (WinSocki.State = 7) Then

cmdConnect.Enabled = False

cmdListen.Enabled = False

cmdDisconnect.Enabled = True

cmdSendData.Enabled = True

Else

MsgBox "Connect failed"

WinSocki.Close

End If

End Sub

-9Т 01 ылгкь^ s«

t:*»Itn(!f>»fJt>ns8*xJ >3i

. стр.

496

ЧАСТЬ II

Интерфейс прикладндиММЙкаммирования Winsock

Листинг 15-3.

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

iti\vt.»

Private Sub

cmdDisconnect_Click()

 

' Закройте

соединение

текущего клиента

 

1 и верните кнопкам начальное состояние WinSocki.Close

cmdConnect.Enabled = True cmdListen.Enabled = True cmdDisconnect.Enabled = False cmdSendData.Enabled = False cmdCloseListen.Enabled = False

End Sub

Private Sub cmdListen_Click()

' Переведите сокет в режимы прослушивания для данного типа протокола

If SocketType = 0 Then

WinSocki.Protocol = О

WinSocki.LocalPort = Clnt(txtLocalPort.Text)

WinSocki.Listen

Elself SocketType = 2 Then

WinSocki.Protocol = 2

WinSocki.ServiceName = txtServerName.Text

WinSocki.Listen

EndIf

'Если сокет находится не в режиме прослушивания,

'возникает ошибка

If (WinSocki.State = 2) Then cmdConnect.Enabled = False cmdListen.Enabled = False cmdCloseListen.Enabled = True

 

Else

 

 

 

 

MsgBox "Unable to listen!"

 

 

EndIf

 

 

 

End

Sub

 

 

 

Private Sub cmdSendData_Click()

 

' Передайте данные

из

рамки по текущему соединению

'-

 

 

 

 

I

 

WinSocki.SendData

txtSendData.Text

 

End

Sub

 

 

 

 

 

 

 

.i

Private Sub Form_Load()

.

Задайте начальные

значения для кнопок, таймера и т.д.

'3

 

optTCP.Value = True

 

 

SocketType = О

 

 

 

 

Timeri.Interval

=

750

 

ГЛАВА 15 Элемент управления Winsock

497

Чистинг 15-3. (продолжение)

Timer1.Enabled = True

 

cmdConnect.Enabled

=

True

 

 

cmdListen.Enabled

=

True

 

I

cmdDisconnect.Enabled

=

False

II

cmdSendData. Enabled

=

False

c

cmdCloseListen.Enabled

=

False

lblLocallP.Caption = WinSocki.LocallP End Sub

Private Sub optIRDA_Click()

'

Задайте тип

сокета

как IrDA

К

 

 

 

1

optlRDA.Value =

True

 

SocketType

= 2

 

End Sub

 

 

Private Sub optTCP_Click()

1

Задайте тип

сокета

как TCP

optTCP.Value = True SocketType = 0 cmdConnect.Caption = "Connect"

End Sub

Private Sub Timer1_Timer()

 

 

 

 

'

Это - событие, вызываемое

при

срабатывании

таймера.

'

Обновляем

надпись информацией

о состоянии

сокета

 

Select Case WinSocki.State

 

 

 

 

Case

0

 

 

 

 

 

 

lblState.Caption

=

"sckClosed"

 

 

Case 1

 

 

 

 

 

 

lblState.Caption

=

"sckOpen"

 

 

Case

2

 

 

 

 

 

 

lblState.Caption

=

"sckListening"

 

Case

3

 

 

 

 

 

 

lblState.Caption

=

"sckConnectionPending"

 

Case

4

 

 

 

 

 

 

lblState.Caption

=

"sckResolvingHost"

 

Case

5

 

 

 

 

 

 

lblState.Caption

=

"sckHostResolved"

 

Case

6

 

 

 

 

 

 

lblState.Caption

=

"sckConnecting"

 

Case

7

 

 

 

 

 

 

lblState.Caption

=

"sckConnected"

ЫЩ

eeoXO

d i .

•Щ

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