Навигация:
<< >> Оглавление Указатель

Руководство программиста

Глава 2. Создание моделей блоков

§2.15. Обмен данными по сети

Рассматриваются способы обмена данными между схемами, работающими на разных машинах. Приводятся примеры моделей блоков, осуществляющих такой обмен.

§2.15.1. Общие принципы обмена данными по сети в RDS

Рассматривается механизм обмена данными по сети между блоками RDS. Описываются основные сервисные функции и структуры и способ их использования.

Поскольку модели блоков в RDS являются просто функциями в составе динамически подключаемой библиотеки (DLL), им доступны все функции Windows API, и они могут самостоятельно создавать сетевые соединения и обмениваться данными друг с другом, как это делают обычные программы. Тем не менее, в RDS включен специализированный механизм сетевого обмена, облегчающий работу программистам, которые не хотят писать сетевые функции самостоятельно. Конечно, этот механизм менее гибок, чем универсальные функции API, но свою задачу он выполняет: с его помощью блок может передать произвольные данные одному или нескольким блокам в схемах, запущенных на других машинах.

В RDS используется клиент-серверная модель обмена данными. Одна из машин в сети (точнее, одна из запущенных на ней копий «rds.exe») должна стать сервером, через который будет проходить весь поток данных. Все остальные будут передавать ей данные и получать их от нее. RDS может работать в режиме выделенного сервера, то есть заниматься только организацией обмена данными между клиентами (для этого нужно запустить «rds.exe» с параметром командной строки «/server default» или «/ номер_порта») или обслуживать работу какой-либо схемы, выполняя при этом еще и функции сервера. Если в сети есть свободная машина, можно использовать первый вариант, если нет – второй. Технически, первый вариант предпочтительнее, поскольку во втором варианте критические ошибки в моделях блоков загруженной схемы могут привести к аварийному завершению RDS, при этом вся сеть лишится сервера.

Для того, чтобы блоки схемы могли обмениваться данными с другими схемами, в сетевых настройках RDS (рис. 108) должен быть разрешен сетевой обмен – в противном случае все попытки установить соединение с другими машинами или запустить сервер будут блокироваться. Разумеется, все это относится только к встроенному механизму сетевого обмена RDS: если модель блока установит соединение с помощью функций Windows API, никакие настройки RDS не смогут ей помешать. В настройках также обычно указывается имя или IP-адрес и порт сервера по умолчанию. Эти параметры будут использованы клиентом при создании соединения с сервером, если адрес сервера и порт не будут указаны моделью блока. Номер порта по умолчанию также используется при запуске RDS в режиме выделенного сервера с параметром командной строки «/ default». Для обмена данными между клиентом и сервером используются протоколы TCP и UDP: служебные данные всегда передаются по протоколу TCP, а данные, которыми обмениваются блоки, могут передаваться как по TCP, так и по UDP, в зависимости от настроек RDS (использование UDP должно быть разрешено) и флагов при вызове сервисных функций.

Окно настроек RDS: вкладка сеть – TCP

Рис. 108. Окно настроек RDS: вкладка «сеть – TCP»

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

Для установки соединения с каким-либо каналом сервера модель блока должна вызвать сервисную функцию rdsNetConnect:

  int  (
     Host,    // IP-адрес или имя сервера, или NULL для
                     // сервера по умолчанию  (UTF8)
    int Port,        // Порт сервера или -1 для порта по умолчанию
     Channel, // Имя канала передачи данных сервера (UTF8)
     Receive);   // Будет ли блок получать данные
  int  (
     Host,   // IP-адрес или имя сервера, или NULL для
                     // сервера по умолчанию (UTF16)
    int Port,        // Порт сервера или -1 для порта по умолчанию
     Channel,// Имя канала передачи данных сервера (UTF16)
     Receive);   // Будет ли блок получать данные
  // 
  int  (
     Host,   // IP-адрес или имя сервера, или NULL для
                     // сервера по умолчанию (кодировка по умолчанию)
    int Port,        // Порт сервера или -1 для порта по умолчанию
     Channel,// Имя канала передачи данных сервера (кодировка по умолчанию)
     Receive);   // Будет ли блок получать данные

В параметре Host передается строка с IP-адресом сервера (четыре числа, разделенные точками, например «192.168.1.1») или его именем. Если вместо строки будет передано значение NULL, адрес сервера будет взят из сетевых настроек RDS (см. рис. 108). В параметре Port передается номер порта, используемого сервером RDS для обмена данными, при передаче значения −1 номер порта будет взят из сетевых настроек. Лучше всего использовать именно значения по умолчанию, то есть NULL и −1 – в этом случае при переносе сервера на другую машину достаточно будет изменить сетевые настройки RDS на каждой клиентской машине. Если же адрес сервера и номер порта будут храниться в настройках блока, при изменении адреса или порта сервера потребуется изменить настройки всех блоков на всех клиентских машинах. Тем не менее, если схеме необходимо устанавливать соединения с двумя разными серверами, адрес и порт одного из них придется хранить в настройках блоков.

В параметре Channel передается имя канала передачи данных, с которым устанавливается соединение. Имя канала может быть произвольной строкой символов. В параметрах сервера каналы никак не настраиваются, они создаются автоматически при первом запросе на подключение к каналу с указанным именем. Типа данных у канала тоже нет, блоки передают в него двоичные данные произвольного размера, и сервер пересылает их получателям без какой-либо обработки. Чаще всего имя канала хранят в настройках блока, чтобы пользователь мог ввести его самостоятельно, указывая, какие группы блоков связываются друг с другом через этот канал. Для обеспечения уникальности можно добавлять к имени, введенному пользователем или жестко указанному в программе, какой-нибудь префикс – например, имя библиотеки, в которой находится модель блока. Логический параметр Receive определяет, должен ли блок, вызвавший функцию, получать данные из канала (TRUE) или он будет только передавать их (FALSE). Если в модели блока не предусмотрен прием данных по сети, а в параметре Receive передано значение TRUE, ничего страшного не случится – при поступлении данных модель блока будет вызвана, но, поскольку в ней нет соответствующей реакции, принятые данные будут проигнорированы. Тем не менее, в блоках, которые только передают данные, лучше указывать в параметре Receive значение FALSE: это не только предотвратит потери процессорного времени на лишние вызовы модели блока, но и снизит нагрузку на сеть. Если в схеме на клиентской машине нет ни одного блока, получающего данные из канала, сервер вообще не будет передавать данные этого канала на эту машину.

Функция возвращает целое число, являющееся уникальным идентификатором созданного соединения. Это число в дальнейшем используется в сервисных функциях передачи данных и в реакциях блока на получение данных из сети. Если сетевое соединение установить невозможно (например, если обмен данными по сети запрещен в настройках RDS), функция вернет −1.

Если нужно не подключиться к другому серверу, а включить функции сервера в RDS на данной машине, необходимо вызвать функцию rdsNetServer. Она одновременно запускает сервер RDS, если он еще не запущен, и устанавливает соединение с указанным каналом в нем:

  int  (
    int Port,         // Порт сервера или -1 для порта по умолчанию
     Channel,  // Имя канала передачи данных сервера (UTF8)
     Receive);    // Будет ли блок получать данные
  int  (
    int Port,         // Порт сервера или -1 для порта по умолчанию
     Channel, // Имя канала передачи данных сервера (UTF16)
     Receive);    // Будет ли блок получать данные
  // 
  int  (
    int Port,         // Порт сервера или -1 для порта по умолчанию
     Channel, // Имя канала передачи данных сервера (кодировка по умолчанию)
     Receive);    // Будет ли блок получать данные

Параметры этой функции совпадают с параметрами , за исключением отсутствующего параметра Host: поскольку сервер запускается на той же машине, на которой загружена схема с вызвавшим эту функцию блоком, адрес сервера, очевидно, указывать не нужно. Для запуска сервера достаточно одного блока, вызвавшего эту функцию, все остальные блоки схемы могут использовать как , так и . Функция тоже возвращает уникальный номер соединения, которым можно пользоваться при приеме и передаче данных.

После успешного соединении с сервером (не важно, на этой же машине, или на другой) модель блока вызывается в режиме RDS_BFM_NETCONNECT, при этом в параметре ExtParam передается указатель на структуру RDS_NETCONNDATA:

  typedef struct
  {  ConnId;  // Уникальный идентификатор соединения
     HostA;    // Сервер (только для клиента, UTF8)
     HostW;   // Сервер (только для клиента, UTF16)
    //  Host; // Сервер (только для клиента, )
     Port;    // Порт сервера
     ChannelA; // Имя канала (UTF8)
     ChannelW; // Имя канала (UTF16)
    //  Channel; // Имя канала ()
     ByServer;    // Соединение разорвано сервером (только при
                      // разрыве соединения)
  } ;
  typedef  *;

Поля этой структуры повторяют параметры функций и (кроме поля ByServer, которое в режиме вызова не используется). Этот вызов информирует блок о том, что соединение с сервером успешно установлено, и теперь можно передавать данные в указанный канал, используя уникальный номер соединения ConnId.

Для передачи данных всем блокам канала используется сервисная функция rdsNetBroadcastData, возвращающая TRUE при успешной передаче:

    (
    int ConnId,     // Идентификатор соединения
     Flags,    // Флаги RDS_NETSEND_*
    int id,         // Передаваемое целое число
     string, // Передаваемая строка или NULL (UTF8)
     buf,     // Указатель на передаваемый блок данных
                    // (или NULL)
     bufsize); // Размер передаваемого блока данных
    (
    int ConnId,     // Идентификатор соединения
     Flags,    // Флаги RDS_NETSEND_*
    int id,         // Передаваемое целое число
     string,// Передаваемая строка или NULL (UTF16)
     buf,     // Указатель на передаваемый блок данных
                    // (или NULL)
     bufsize); // Размер передаваемого блока данных
  // 
    (
    int ConnId,     // Идентификатор соединения
     Flags,    // Флаги RDS_NETSEND_*
    int id,         // Передаваемое целое число
     string,// Передаваемая строка или NULL (кодировка по умолчанию)
     buf,     // Указатель на передаваемый блок данных
                    // (или NULL)
     bufsize); // Размер передаваемого блока данных

В параметре ConnId передается уникальный номер соединения с каналом сервера, возвращенный функцией или при создании этого соединения. Функция позволяет одновременно передать в канал целое число id, строку текста string и блок двоичных данных buf произвольного размера bufsize. Целое число передается всегда, а строку и блок данных можно не передавать – в этом случае в соответствующем параметре следует указать NULL. Разбиение передаваемых данных на три части сделано для удобства программиста: в принципе, строки и числа тоже можно передавать в виде двоичных данных. Однако, во многих случаях приходится передавать данные сложного формата, и тут отдельная передача целых чисел и строк облегчает жизнь программисту, ликвидируя необходимость разбирать принятые данные. Например, число id можно использовать в качестве типа передаваемых двоичных данных или номера фрагмента, если данные передаются последовательными блоками; строка string может содержать имя файла, содержимое которого передается в двоичном блоке, и т.д.

Способом передачи данных серверу управляют битовые флаги в параметре Flags. Можно использовать сочетание следующих флагов:

При вызове модели в режиме в параметре ExtParam передается указатель на структуру RDS_NETACCEPTDATA:

  typedef struct
  {  ConnId;     // Идентификатор соединения
     HostA;       // Адрес сервера (только для клиента, UTF8)
     HostW;      // Адрес сервера (только для клиента, UTF16)
    //  Host;    // Адрес сервера (только для клиента, )

     Port;       // Порт сервера
     ChannelA;    // Канал передачи (UTF8)
     ChannelW;   // Канал передачи (UTF16)
    //  Channel; // Канал передачи ()

     Id;         // Целое число (id) из принятого блока
  } ;
  typedef  *;

В полях ConnId, Host (HostA, HostW), Port и Channel (ChannelA, ChannelW), как и в структуре , содержатся идентификатор соединения, адрес сервера, порт и имя канала соответственно. В поле Id записано целое число, которое было передано в параметре id при вызове . Таким образом, реагируя на вызов в режиме , модель блока сможет понять, прием какой именно порции данных подтвердил сервер.

Получив данные для какого-либо канала передачи, сервер рассылает их всем подключенным к нему клиентам, блоки которых создали соединение с этим каналом и изъявили желание получать данные (параметр Receive при вызове или был равен TRUE). Приняв данные, клиент вызывает модели всех этих блоков в режиме RDS_BFM_NETDATARECEIVED, передавая в параметре ExtParam указатель на структуру RDS_NETRECEIVEDDATA:

typedef struct
  { // Параметры соединения
     ConnId;     // Идентификатор соединения

     HostA;       // Адрес сервера (только для клиента, UTF8)
     HostW;      // Адрес сервера (только для клиента, UTF16)
    //  Host;    // Адрес сервера (только для клиента, )

     Port;       // Порт сервера

     ChannelA;    // Имя канала передачи данных (UTF8)
     ChannelW;   // Имя канала передачи данных (UTF16)
    //  Channel; // Имя канала передачи данных ()

    // Принятые данные
     Id;         // Принятое целое число

     StrA;        // Принятая строка (UTF8)
     StrW;       // Принятая строка (UTF16)
    //  Str;     // Принятая строка ()

     Buffer;       // Указатель на буфер с принятыми двоичными
                         // данными или NULL
     BufferSize;    // Размер принятого буфера

    // Идентификаторы отправителя
     SenderStation; // Идентификатор передавшей машины
     SenderBlock;     // Идентификатор передавшего блока
  } ;
  typedef  *;

Первые поля структуры, как всегда, описывают параметры соединения, по которому пришли данные, для обработки которых вызвана модель. Если блок установил сразу несколько соединений, по этим параметрам можно понять, какое из них приняло данные. В поле Id содержится принятое целое число, переданное другим блоком в параметре id при вызове . В поле Str – указатель на принятую строку во внутренней памяти RDS (ей соответствует параметр string в вызове ). Этот указатель никогда не будет равен NULL: если строка не передавалась, поле String будет указывать на пустую строку. Наконец, поле Buffer указывает на принятый блок двоичных данных (при этом в BufferSize записан размер этого блока), или содержит NULL, если двоичные данные не передавались. Таким образом, есть однозначное соответствие между параметрами функции и полями структуры : то, что один блок передал в параметрах сервисной функции, другой блок получает в полях структуры при вызове модели в режиме .

Два последних поля структуры, SenderStation и SenderBlock, содержат уникальные идентификаторы машины и блока в схеме на этой машине, отправившего данные в канал. Эти поля могут использоваться для того, чтобы получивший данные блок мог передать ответ только блоку, пославшему эти данные, а не всем блокам, подключенным к каналу передачи. Для передачи данных единственному блоку используется сервисная функция rdsNetSendData:

    (
    int ConnId,     // Идентификатор соединения
     Flags,    // Флаги RDS_NETSEND_*
    int id,         // Передаваемое целое число
     string, // Передаваемая строка или NULL (UTF8)
     buf,     // Указатель на передаваемый блок данных или NULL
     bufsize,  // Размер передаваемого блока данных
     station,  // Машина-получатель
     block);     // Блок-получатель
    (
    int ConnId,     // Идентификатор соединения
     Flags,    // Флаги RDS_NETSEND_*
    int id,         // Передаваемое целое число
     string,// Передаваемая строка или NULL (UTF16)
     buf,     // Указатель на передаваемый блок данных или NULL
     bufsize,  // Размер передаваемого блока данных
     station,  // Машина-получатель
     block);     // Блок-получатель
  // 
    (
    int ConnId,     // Идентификатор соединения
     Flags,    // Флаги RDS_NETSEND_*
    int id,         // Передаваемое целое число
     string,// Передаваемая строка или NULL (кодировка по умолчанию)
     buf,     // Указатель на передаваемый блок данных или NULL
     bufsize,  // Размер передаваемого блока данных
     station,  // Машина-получатель
     block);     // Блок-получатель

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

Таким образом, вызывая функции и и реагируя на вызовы в режиме , блоки на разных машинах могут обмениваться произвольными данными до тех пор, пока сетевое соединение не будет разорвано. Для завершения сетевого соединения модель блока должна вызвать сервисную функцию rdsNetCloseConnection, принимающую единственный параметр: уникальный идентификатор соединения, которое нужно закрыть. После этого, как только соединение будет разорвано, модель блока будет вызвана в режиме RDS_BFM_NETDISCONNECT, и в параметре ExtParam ей будет передан указатель на уже знакомую нам структуру , содержащую параметры конкретного разорванного соединения. В этом режиме модель вызывается не только при самостоятельном завершении соединения, но и при его разрыве по инициативе сервера, из-за отключения сети или по другим причинам. При разрыве соединения из-за отключения сервера в поле ByServer будет содержаться значение TRUE. В этом случае, а также при разрыве соединения по любым причинам, отличным от вызова , RDS будет пытаться самостоятельно восстановить связь до тех пор, пока эти попытки не увенчаются успехом, или пока для этого соединения не будет вызвана функция .

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


<< >> Оглавление Указатель