Программирование в сетях Windows
.pdf488 |
ЧАСТЬ 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, а в качестве номера порта, через который установлено соединение, отображается неотрицательное число. Кроме того, на сервере в список
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
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 |
|