Подробнее об асинхронной природе сокета

В разделах Установление исходящих соединений и Закрытие соединений мы уже касались асинхронной природы объекта sock. Здесь описано подробнее, что это значит для вашего приложения.

Выполнение методов sock.connect, sock.close, sock.reset или sock.discard не означает, что к моменту перехода программы к следующему оператору соединение уже установлено или разорвано. Эти операторы лишь сообщают Master Process, что делать с соединением. Установление и разрыв соединения могут занять некоторое время, и приложению не обязательно ждать их завершения. В разделе Проверка состояния соединения объяснялось, как в любой момент узнать фактическое состояние соединения (см. свойства только чтение sock.state и sock.statesimple).

Бывают ситуации, когда программе приходится это учитывать. Вот один пример. Допустим, нужно узнать MAC-адрес удалённого устройства, к которому мы устанавливаем исходящее соединение. Это можно сделать так:

'Correct code -- on startup we order the connection to be established and in the on_sock_event event handler we record the MAC  address of the 'other side'
Sub On_sys_init
   ...
   sock.targetip="192.168.100.40"   'we prepare for the connection
   sock.targetport=2000
   sock.connect             'and now we connect
End Sub
'------------------------------------------------------------------------------------
Sub On_sock_event(newstate As pl_sock_state,newstatesimple As pl_sock_state_simple)
   Dim s As String
   If newstatesimple=PL_SSTS_EST Then
      'OK, so connection is established, let's get this MAC!
      s=sock.remotemac
   End If
End Sub

Это хороший пример событийно-ориентированного программирования. Но иногда соединение нужно установить и «довести до конца» в том же обработчике события. Как это сделать? Вот простой, но НЕПРАВИЛЬНЫЙ пример кода:

'!!! BAD EXAMPLE !!!
Dim s As String
...
...
sock.targetip="192.168.100.40"   'we prepare for the connection
sock.targetport=2000
sock.connect             'and now we connect
s=sock.remotemac         'and now we try to check the MAC. WRONG! Connection may not be established yet!
...

А вот правильный способ. Для наглядности в этом примере предполагается, что соединение будет установлено наверняка.

'Correct, but simplified example (we do not handle possible connection failure).
Dim s As String
...
...
sock.targetip="192.168.100.40"   'we prepare for the connection
sock.targetport=2000
sock.connect             'and now we connect
While sock.statesimple<>PL_SSTS_EST 'we wait here until the connection is actually established
Wend
s=sock.remotemac         'Get the MAC!
...

Вот ещё более интересный пример. Допустим, TCP-соединение нужно закрыть и установить заново прямо в одном обработчике события. Неправильный способ:

'!!! BAD EXAMPLE !!! -- this just won't work!
...
...
sock.close
sock.connect
...

Дело в том, что выполнение sock.close на самом деле не закрывает соединение - оно лишь передаёт Master Process инструкцию закрыть его. Поэтому к моменту, когда выполнение программы доходит до метода sock.connect, прежнее соединение всё ещё активно!

Правильно дождаться фактического закрытия соединения и только потом выполнять sock.connect. Вот ещё один пример - тоже не вполне правильный, но уже ближе к истине.

'!!! 'BETTER' CODING !!! -- still not totally OK!
...
...
sock.close
   While sock.statesimple<>PL_SSTS_CLOSED 'here we wait for the connection to be closed
   Wend
sock.connect
...

Так лучше. Осталась одна последняя поправка, и код будет готов. В разделе Закрытие соединений (подраздел «Повторное использование сокета после закрытия соединения») мы объяснили, что TiOS гарантирует: on_sock_event успеет выполниться после закрытия старого соединения и до установления нового. В приведённом выше примере и sock.close, и sock.connect находятся в одном обработчике события - on_sock_event не вклинится между ними, если не использовать оператор doevents! Вот правильный код:

'100% CORRECT!
...
...
sock.close
   While sock.statesimple<>PL_SSTS_CLOSED 'here we wait for the connection to be closed
   Wend
   doevents ' Absolutely essential for this particular case!
sock.connect
...

Обратите внимание, что doevents стоит после цикла while-wend. Это принципиально важно! Конечно, раз в обработчике события теперь есть хотя бы один doevents, можно добавить doevents и во все остальные «места ожидания» - чтобы другие события выполнялись раньше.

'Even better code!
...
...
sock.close
   While sock.statesimple<>PL_SSTS_CLOSED 'here we wait for the connection to be closed
      doevents 'Not necessary but useful -- lets other events execute
   Wend
   doevents ' Absolutely essential for this particular case!
sock.connect
...
Внимание

Оператор doevents нужно ставить после цикла while-wend, причём даже в том случае, если в вашем приложении нет обработчика события on_sock_event!

Исходный материал Tibbo обновлён 12 сентября 2022Оригинал на docs.tibbo.com · Сообщить об ошибке