В системе CRW-DAQ можно организовывать распределенные системы управления, построенные по принципу клиент-сервер, с использованием Web-технологий.
Допустим, имеется сервер, то есть программа, которая умеет делать что-то содержательное, скажем, измерять и управлять физ. установкой. Допустим, также, что имеется несколько удаленных клиентов, то есть пользователей, которые могут располагаться как рядом с установкой и работать на локальной машине, так и быть удаленными пользователями в сети. В ряде случаев по условиям эксплуатации пользователи находиться рядом с компьютером просто не могут (радиация, химия, шум и т.д.). Возможно также, что несколько операторов в разных местах хотят наблюдать результаты измерений или управлять установкой. Таким образом, возникает СЕРВЕР - процесс, в котором работает программа измерений и произвольное число КЛИЕНТОВ - процессов, в которых запущен интерфейс пользователя. Клиент может быть локальным (на той же машине) или удаленным (на другой машине в сети). Задача состоит в том, чтобы клиенты могли получать от сервера измеряемые данные, а также посылать серверу команды для управления установкой. Такова, вкратце, формулировка задачи.
Клиентская часть - интерфейс пользователя - может быть построена на основе HTML - агента, например, Internet Explorer. Главным достоинством такого подхода является то, что клиентское программное обеспечение писать не надо, оно уже готово, причем практически все системы гарантированно поддерживают просмотр HTML. Всю содержательную работу при этом выполняет сервер. Хотя написание серверной части существенно усложняется, в целом задача становится проще, потому что на один сервер обычно приходится много клиентов. С каждым клиентом связана необходимость установки и настройки клиентского программного обеспечения, все это время.
Правда, надо сказать, что HTTP - довольно медленный протокол, он не пригоден для систем реального времени. Его можно применять, когда речь идет о длительных процессах, с временем реакции в несколько секунд. В этом смысле Web - технологии не конкурируют, а дополняют технологии DIM и OPC, которые присутствуют в CRW-DAQ. Кроме того, протокол HTTP предполагает, что активной стороной при обмене данными всегда выступает клиент. Клиент посылает запрос, а сервер только отвечает на клиентские запросы. Для систем реального времени характерна обратная ситуация - сервер, по мере получения результатов измерений и наступления разных событий рассылает клиентам сообщения и обновляет данные. Именно так работают, например, DIM и OPC. Единственной возможностью автоматического обновления клиентских данных при использовании Web является периодическая посылка серверу HTTP запросов на обновление HTML страницы. При этом реакция составляет несколько секунд. Это накладывает ограничение на задачи, в которых Web технологии вообще применимы.
Возможность сетевого доступа к установке приводит к тому, что к серверу предъявляются особые требования. Сервер должен распознавать клиентов и иметь систему безопасности, то есть права клиентов должны быть разными, одним доступен только просмотр данных, другие могут управлять установкой.
Если бы речь шла только о локальном клиенте, все было бы просто, события идентифицировались бы просто кнопкой и именем нажатого сенсора (clickbutton, clicksensor). Вся конфигурация и программа заняла бы пару десятков строк. На эту тему есть много примеров. С распределенной системой все сильно усложняется. Клиентов много, они работают одновременно на разных машинах, под разными пользователями, которые имеют разные права и жмут на все кнопки подряд во всех доступных окнах. Понятие события и его идентификация существенно усложняется. Этот факт не зависит от применяемой системы программирования, просто распределенные многопользовательские системы вообще по сути своей всегда сложнее локальных.
Когда пользователь вызывает HTML страницу в Internet Explorer, Web-сервер получает HTTP запрос. Обычно Web-сервер не обрабатывает запрос сам (кроме самых тривиальных), а передает его CGI - скрипту (от Common Gateway Interface). CGI-скрипт обрабатывает запрос и формирует HTML - текст страницы, которую Web-сервер возвращает клиенту. Так что клиенту увидит то, что ему предоставил CGI - скрипт.
Такая технология принята потому, что написание Web-сервера намного сложнее,
чем написание CGI-скрипта. Обычно используется готовый Web-сервер.
В CRW-DAQ используется TinyWeb - сервер:
С CGI - скриптом дело сложнее. Собственно CGI - скрипт написан раз и навсегда (он находится в дистрибутиве CRW-DAQ). Он тоже сам по себе делает довольно мало, подключается по именованному каналу к CRW-DAQ, передает ему HTTP запрос и ждет ответа. Запрос принимает программа WebSrv.pas, которая принадлежит устройству &WebSrv. Имя устройства предлагается считать фиксированным. Сама программа WebSrv.pas тоже считается стандартной и не меняется. Далее, устройство анализирует и расшифровывает запрос и помещает его в текст. Затем формируется сообщение
@HTTP_REQUEST_ACCEPTED=sender,reqtime,request,reply
sender - имя устройства, то есть &WebSrv
reqtime - время запроса по часам msecnow
request - ссылка на текст запроса, которую нужно использовать в вызовах
функций типа text_getln(...) для извлечения информации о HTTP запросе.
reply - ссылка на текст ответа, которую нужно использовать в вызовах
функций типа text_getln(...) для формирования HTML ответа.
Example:
@HTTP_REQUEST_ACCEPTED=&WEBSRV,63295844422438,1049397,1049374
Это сообщение рассылается клиентам (см. далее).
Клиент должен сформировать в тексте, ссылку на которую разослал WebSrv,
текст HTML страницы.
Последней строчкой страницы должна быть строка dump(0).
Эта строчка служит для WebSrv признаком того, что страница сформирована
и ее можно отсылать. То есть код обработки будет примерно такой:
readln(Line);
if ExtractWord(1,Line)='@HTTP_REQUEST_ACCEPTED' then begin
Reply:=Val(ExtractWord(5,Line));
b:=text_addln(Reply,'<html>');
b:=text_addln(Reply,'<head><title>Example: hello.</title></head>');
b:=text_addln(Reply,'<body> HELLO, WORLD! </body>');
b:=text_addln(Reply,'</html>');
b:=text_addln(Reply,dump(0)); {end of text marker}
end;
При этом элемент <html> ... </html> строго обязателен.
Со стороны клиента это все. Дальше программа WebSrv увидит маркер конца текста и передаст страницу CGI-скрипту, тот передаст его Web-серверу, который передаст его в Internet Explorer. То есть цепочка такая:
Client(IE) ---> TinyWeb ---> WebSrv.cgi ---> WebSrv.Pas(CRW-DAQ)
Click URL HTTP CGI Pipe `->send the message to clients
@HTTP_REQUEST_ACCEPTED=...
Wait until Reply ready ...
Somebody put HTML text to Reply
and Chr(0) as EndOfText marker.
Client(IE) <--- TinyWeb <--- WebSrv.cgi <--- WebSrv.Pas(CRW-DAQ)
See HTML HTTP CGI Pipe
Все это выглядит замысловато, но ведь примерно так работает большинство
Web-серверов в мире, с той разницей, что вместо DaqPascal скрипт пишется
на PHP или Perl. Важно, что Web-сервер писать не надо,
это сильно облегчает работу.
Вот, собственно, и все. Дальше идут детали и нюансы, и их очень много.
;*************************
;*** WEB SERVER CONFIG ***
;*************************
[DeviceList] ; Fix=Fixed, Var=Variable params
&WebSrv = device software program ; Fix &WebSrv device declaration
[&WebSrv] ; Fix &WebSrv device section
Comment = WEB server for CRW-DAQ ; Fix &WebSrv device description
InquiryPeriod = 1 ; Var &WebSrv device poll period
DevicePolling = 10, tpNormal ; Var &WebSrv thread poll period
ProgramSource = ~~\Resource\DaqSite\WebServer\websrv.pas
WebScriptExe = ~~\Resource\DaqSite\WebServer\websrv.exe
WebScriptPath = ..\cgi-bin\websrv.cgi ; Fix Location of cgi-bin script
WebServerPath = ..\cgi-bin\web4daq.exe ; Fix Location of Web server bin
WebRoot = ..\index.html ; Fix Location of main index.html
WebPort = 80 ; Var Web server port, HTTP = 80
WebPoll = 60000 ; Var Web server poll period, ms
PipeSection = [&WebSrv.Pipe] ; Fix Section where pipe options
PipeName = WebSrv%Pid% ; Fix Pipe name uses for CGI I/O
PipeIniFile = .\WebSrv.ini ; Fix Pipe file, to set PipeName
ClientList = &WebSrv ; Var List of clients (self too)
OpenConsole = 2 ; Var 0/1/2=NO/SHOW/HIDE console
DebugFlags = 3 ; Var 1/2/4/8= : ! > < messages
StdInFifo = 128 ; Var StdIn FIFO size (console)
StdOutFifo = 128 ; Var StdOut FIFO size (console)
StartingOrder = -1024 ; Var Starting order
StoppingOrder = +1024 ; Var Stopping order
[&WebSrv.Pipe] ; Fix Section to set pipe params
FifoSize = 128 ; Var Pipe uses FIFO size, in kB
TimeOut = 5000 ; Var Timeout to wait CGI answer
PipePolling = 10, tpHigher ; Var Pipe polling thread params
ListenPeriod = 10 ; Pipe listen polling period, ms
[ConfigFileList] ; Fix Should include PipeIniFile
ConfigFile = .\WebSrv.ini ; Fix File uses to init PipeName
[]
Здесь фиксированные параметры помечены как Fix, Var-параметры
могут меняться. Например, фиксировано имя каталога cgi-bin, это
общее соглашение для CGI-скриптов.
Наиболее важные параметры:
;********************************** ;*** WEB CGI SCRIPT DEMO CONFIG *** ;********************************** [DeviceList] ; Fix=Fixed, Var=Variable params &WebCgi = device software program ; Var &WebCgi device declaration [&WebCgi] ; Var &WebCgi device section Comment = WEB CGI script DEMO ; Var &WebCgi device description InquiryPeriod = 1 ; Var &WebCgi device poll period DevicePolling = 10, tpNormal ; Var &WebCgi thread poll period ProgramSource = ..\DaqPas\webcgi.pas TrustedUsers = [TrustedUsers] ; Fix Section where access table [&WebSrv] ; Fix Section of Web ClientList. ClientList = &WebCgi ; Var Add himself to ClientList. []
Самой сложной частью конфигурации будет таблица прав доступа. Она сделана общей для DIM и WEB, только вместо поля MAC используется Password. Права доступа задаются в таблице примерно такого вида:
[TrustedUsers] ;level user host IP password ;--------------------------------------------------- web-user sergey localhost 127.0.0.1 123 ; локальный user sergey на localhost, пароль 123 web-root alex crwbox * 321 ; удаленный root alex на crwbox без проверки IP, пароль 321Каждая строка таблицы содержит набор (Level, User, Host, IP, Password). Level может быть web-guest, web-user, web-root по возрастанию уровня прав. Это поле строго обязательно. Далее указывается имя пользователя, имя компьютера, его IP-адрес, а также пароль пользователя. Пароль хранится открыто; предполагается, что на серверную машину доступа нет (она ведь удаленная). Считается, что условие доступа выполнено, если все параметры в какой-либо строке удовлетворяют заданному шаблону. При этом в шаблоне символ (*) звездочки означает, что параметр может иметь любое значение, а символ (.) точки - значение параметра на машине сервера. Если пользователь удовлетворяет хотя бы одной строке таблицы, он получает права доступа этого уровня. Если прав доступа не получено, сервер посылает уведомление об отказе в обслуживании запроса.
Таким образом, клиентская программа может использовать три уровня доступа для определения, к каким ресурсам есть доступ, а к каким нет.
QUERY.COUNT=1 QUERY.ITEM0=action=echo COOKIE.COUNT=2 COOKIE.ITEM0=RemoteUserName=fhJW7Y1eSfmCNvuGEe2w9eiQC0C/85woHa+uirM8qlM COOKIE.ITEM1=RemotePassword=GXY0lxYhBnDAYD5w77TaASAf6F+//gI50OXam/QBVoM CONTENT.COUNT=2 CONTENT.ITEM0=RemoteUserName=root CONTENT.ITEM1=RemotePassword=123 GATEWAY_INTERFACE=CGI/1.1 REQUEST_METHOD=GET QUERY_STRING=action=echo CONTENT=RemoteUserName=root&RemotePassword=123 CONTENT_LENGTH=26 SERVER_NAME=RITLABS S.R.L. SERVER_PORT=23816 SERVER_PROTOCOL=HTTP/1.1 SERVER_SOFTWARE=TinyWeb/1.93 REMOTE_HOST=localhost REMOTE_ADDR=127.0.0.1 SCRIPT_NAME=/cgi-bin/websrv.cgi PATH_INFO=/websrv.cgi PATH_TRANSLATED=D:\DAQ32\DEMO_WEB4DAQ\websrv.cgi WEBSRV.NAME=&WEBSRV WEBSRV.SITE=http://main/ WEBSRV.ROOT=D:\DAQ32\DEMO_WEB4DAQ WEBSRV.PORT=80 WEBSRV.INDEX=d:\daq32\demo_web4daq\index.htmlНаиболее важные поля здесь:
http://localhost/websrv.cgi?action=echo&color=redТо, что идет после знака ? - это строка запроса QUERY_STRING, в которой символ & служит для разделения элементов списка, а данные закодированы алгоритмом URL-encode. Обычно строка запроса указывает, что надо делать. Предлагается считать Action=fun стандартным способом указать требуемую функцию fun, а запросы Action=Login и Action=Echo - стандартными именами процедуры авторизации и просмотра эхо-вывода HTTP запроса.
http://host/cgi-bin/websrv.cgi?Action=Loginсервер получает QUERY_STRING = Action=Login и переходит к авторизации. Если в Cookie нет полей RemoteUserName, RemotePassword, то пользователю выдается страница для заполнения формы с именем и паролем. После посылки формы, когда сервер увидит в полях CONTENT параметры UserName, Password, он формирует страницу типа "Access granted!", в которую вставляются Cookie через meta-тег, типа такого
<meta http-equiv="Set-Cookie" content="RemoteUserName=Alex"> <meta http-equiv="Set-Cookie" content="RemotePassword=123">Пароль, конечно, кодируется (тут он показан для наглядности). После этого клиент (пока не закроет окно Internet Explorer) будет включать в HTTP запрос Cookie, содержащие посланные ранее поля RemoteUserName, RemotePassword. Сервер использует эти поля для ответа на вопрос "ты хто такой" и предоставления прав доступа. Каждый запрос, таким образом, анализирует Cookie. Если предоставленная там информация неверна, сервер инициирует сброс Cookie и возврат к авторизации. Для обеспечения безопасности имя пользователя и пароль шифруются. Ключ шифрования знает только сервер, он имеет ограниченный срок действия и периодически меняется.
Следует помнить, что в распределенной системе приходится работать с разными копиями одних и тех же данных. Клиентские копии данных могут устаревать. Удаленный клиент, вводящий данные в форму, видит состояние переменных сервера на момент предыдущего запроса. Поэтому код CGI скрипта должен внимательно анализировать данные, присланные клиентом, так как они могут уже не соответствовать реальной ситуации.
<html>
<header>
<title>
</title>
</header>
<body>
</body>
</html>
<meta http-equiv="Refresh" content="5">для периодического авто-обновления страницы (время в секундах). Напоминаю, что HTTP - односторонний протокол, инициатором запроса всегда является клиент, сервер может только отвечать. Периодическое авто-обновление помогает организовать псевдо-реальное время, чтобы данные сервера автоматически обновлялись на экране клиента. В DIM все наоборот - сервер является активным элементом и сам решает, когда обновлять данные.
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">для динамических страниц, чтобы удаленный Internet Explorer не пытался кэшировать данные. Ведь динамические страницы существуют только в памяти и постоянно меняются.
Оценивая возможности и уровень сложности предложенной технологии, следует учитывать, что основная часть кода как клиента, так и сервера уже написана, в "порожденных" системах основная часть кода будет наследоваться с небольшими изменениями. Пример может быть достаточно легко расширен, при увеличении сложности клиента\сервера накладные расходы (код, обслуживающий обмен данными) расти сильно не будут. Поэтому, если один раз "продраться" через довольно непростой код примера, дальше все будет быстро.