- PVSM.RU - https://www.pvsm.ru -
СУБД Caché для взаимодействия через TCP/IP с удалёнными процессами посредством сокетов предоставляет низкоуровневые команды [1], что может представлять собой сложность для новичков.
А есть ли возможность использовать сокеты «по-другому», не теряя при этом в гибкости, скорости и удобстве разработки?
Конечно, есть: достаточно написать объектную обёртку вокруг существующих команд open, use, close и т.д. В терминах ООП — это инкапсуляция [2].
К счастью такой класс, даже классы — один для серверной части и один для клиентской — уже написаны и поставляются по крайней мере с версии Caché 5.2, а именно:
Рассмотрим два простых примера с их использованием.
#dim sock As %IO.ServerSocket = ##class(%IO.ServerSocket).%New()
set sock.TranslationTable="UTF8"
write #,"Запуск сервера...", !
#; параметры: pPort, pTimeout, pSC
do sock.Open(7001, 1, .sc)
if $$$ISOK(sc) {
do sock.Listen()
do{
#; параметры: pMaxReadLen, pTimeout
set s=sock.ReadAny(250, 5)
write s,!
#; параметры: pData, pFlush
do sock.Write($$$FormatText("Сообщение с сервера %1",$$$quote(s)), $$$YES)
}while(s'="bye!")
do sock.Close()
write "Останов сервера...", !
}
#dim sock As %IO.ServerSocket = ##class(%IO.Socket).%New()
set sock.TranslationTable="UTF8"
write #,"Запуск клиента...", !
#; параметры: pHost, pPort, pTimeout, pSC
do sock.Open("127.0.0.1","7001",1,.sc)
if $$$ISOK(sc) {
#; параметры: pData, pFlush
do sock.Write("Команда с клиента", $$$YES)
#; параметры: pMaxReadLen, pTimeout
write sock.ReadAny(250, 5),!
hang 2
do sock.Write("bye!", $$$YES)
write sock.ReadAny(250, 5),!
do sock.Close()
write "Останов клиента...", !
}
Запуск сервера...
Команда с клиента
bye!
Останов сервера...
Запуск клиента...
Сообщение с сервера "Команда с клиента"
Сообщение с сервера "bye!"
Останов клиента...
Останавливаться подробно на примере не имеет смысла, так как всё достаточно прозрачно.
Замечу лишь, что при получении от клиентской части стоп-строки "bye!" серверная часть завершает свою работу.
Код:
Class demo.socket Extends %RegisteredObject
{
ClassMethod Server2()
{
#; do ##class(demo.socket).Server2()
#dim sock As %IO.ServerSocket = ##class(%IO.ServerSocket).%New()
write #,"Запуск сервера...", !
do sock.Open(10081, 1, .sc)
if $$$ISOK(sc) {
do sock.Listen(-1, .sc)
do{
set s=sock.ReadAny($$$MaxLocalLength,, .sc)
write s,!
do sock.Write(s, $$$YES, .sc)
}while(s'="bye!")
do sock.Close()
write "Останов сервера...", !
}
}
ClassMethod Client2(end As %Boolean = {$$$NO})
{
#; do ##class(demo.socket).Client2(1)
#dim sock As %IO.ServerSocket = ##class(%IO.Socket).%New()
write #,"Запуск клиента...", !
do sock.Open("127.0.0.1","10081",1,.sc)
if $$$ISOK(sc) {
set time=$zhorolog
for i=1:1:10 {
do sock.Write("1234567890", $$$YES, .sc)
set s=sock.ReadAny($$$MaxLocalLength,, .sc)
write s,!
}
write "Время= ",$zhorolog-time," сек.",!
do:end sock.Write("bye!", $$$YES, .sc)
do sock.Close()
write "Останов клиента...", !
}else{
write $system.Status.GetErrorText(sc,"ru"),!
}
}
}
Два примера выше довольно просты, поскольку серверная часть позволяла обрабатывать одновременно лишь одно клиентское соединение.
Как быть, если нам необходимо обрабатывать одновременно десятки/сотни клиентских подключений?
Для этого следует воспользоваться методом %IO.ServerSocket:ListenJob()
Давайте напишем пример посложнее, например, добавим в СУБД Caché поддержку протокола WebSocket.
Это будет особенно актуально, учитывая что СУБД Caché предоставляет технологию создания веб-приложений (CSP [5]), а также имеет готовый фреймворк с богатым набором визуальных, MVC и других компонент (ZEN [6]).
А вспомнив о том, что СУБД Caché помимо поддержки ООП и SQL, может выступать ещё и как NoSQL хранилище…
О самом протоколе WebSocket [7] в интернете написано немало. На его основе пишут чаты, онлайн-игры и всё то, где требуется быстрый обмен данными с сервером (базой данных).
Отмечу, что наш демонстрационный пример будет поддерживать лишь две версии протокола: 76 и 7.
Для тестов нам понадобится один из современных браузеров, имеющих поддержку WebSocket.
Для старых браузеров, например IE 8, можно воспользоваться специальным плагином [8], который работает поверх Flash.
Технические подробности реализации протокола интересующиеся могут найти в исходниках в конце статьи.
Здесь же мы рассмотрим лишь небольшой пример, реализующий простейший веб-чат.
Примечание: Исходный код реализации протокола WebSocket на языке Caché ObjectScript служит только для ознакомительных целей.
Итак:
Например:
/// Демонстрационный класс обработки серверных событий.
Class demo.Server Extends net.WebSocketEvents
{
/// обработчик события onbeforeconnect.
/// <br> Здесь мы можем отклонить подключение для некоторых пользователей.
Method onbeforeconnect() As %Boolean
{
set ^tmp($i(^tmp),"onbeforeconnect")=$lb(..WebSocketGET,..WebSocketHost,..WebSocketOrigin,..WebSocketVer)
q $$$YES
}
/// обработчик события onconnect.
Method onconnect()
{
set ^tmp($i(^tmp),"onconnect")=""
}
/// обработчик события onmessage.
/// <br> Параметры:
/// <br><var>msg</var> - данные, пришедшие от клиента.
Method onmessage(msg As %String)
{
set ^tmp($i(^tmp),"onmessage")=msg
#; анализируем данные от клиента и в зависимости от этого выполняем те или иные действия на сервере.
/*
do ..send("Cachéйцуasd"_$random(1000)):msg="get",
..send($replace($j("",32000)," ","é")):msg="getBig",
..sendBroadcast("from Caché to All"):msg="toAll",
..sendBroadcast($$$FormatText("Подключился пользователь: %1",$$$quote($p(msg,"^",2)))):$e(msg)="^"
*/
if msg="get" {
do ..send("Cachéйцуasd"_$random(1000))
}elseif msg="getBig" {
do ..send($replace($j("",32000)," ","é"))
}elseif msg="toAll" {
do ..sendBroadcast("from Caché to All")
}elseif $e(msg)="^" {
do ..sendBroadcast($$$FormatText("Подключился пользователь: %1",$$$quote($p(msg,"^",2))))
}
}
/// обработчик события onclose.
Method onclose()
{
set ^tmp($i(^tmp),"onclose")=""
}
}
Примечание: Не забудьте включить WebSocket в браузерах, в которых они по умолчанию отключены, например в Opera.
SAMPLES>do ##class(net.WebSocketServer).Start("demo.Server")
Class demo.webclient Extends %ZEN.Component.page
{
/// If true, then attempt to refresh this page when its session timeout period has expired.
/// This will cause a login page to display if the current session has ended
/// and security is set to require login.
Parameter AUTOLOGOUT As BOOLEAN = 0;
/// Comma-separated list of additional JS include files for the page.
Parameter JSINCLUDES As STRING = "websocket/swfobject.js,websocket/web_socket.js";
XData Contents [ XMLNamespace = "www.intersystems.com/zen" [11]]
{
<page xmlns="www.intersystems.com/zen" [11]title="">
<vgroup labelPosition="left">
<label id="lb" label="Статус подключения:"/>
<text id="usr" label="Логин:"/>
<button caption="Старт" onclick="zenPage.start();"/>
<button caption="Команда 1" label="Hello123xcf789фйбоз" onclick="ws.send('Hello123xcf789фйбоз');"/>
<button caption="Команда 2" label="toAll" onclick="ws.send('toAll');"/>
<button caption="Команда 3" label="get" onclick="ws.send('get');"/>
<button caption="Команда 4" label="getBig" onclick="ws.send('getBig');"/>
<button caption="Останов" onclick="ws.close();"/>
</vgroup>
</page>
}
ClientMethod start() [ Language = javascript ]
{
ws = new WebSocket("ws://127.0.0.1:10081/asd s HTTP/1.1///");
ws.onopen = function() {
zenSetProp('lb','value','open');
ws.send('^'+zenGetProp('usr','value'));
};
ws.onmessage = function(e) {
zenAlert('onmessage',' length=',e.data.length,' data=',e.data);
};
ws.onclose = function() {
zenSetProp('lb','value','close');
};
ws.onerror = function() {
zenAlert('onerror');
};
}
/// This client event, if present, is fired when the page is loaded.
ClientMethod onloadHandler() [ Language = javascript ]
{
// Let the library know where WebSocketMain.swf is:
WEB_SOCKET_SWF_LOCATION = "websocket/WebSocketMain.swf";
WEB_SOCKET_DEBUG = false;
}
}
Давайте посмотрим содержимое журнала на сервере:
USER>zw ^tmp
^tmp=8
^tmp(1,"onbeforeconnect")=$lb("/asd%20s%20HTTP/1.1///","127.0.0.1:10081","","hybi-10")
^tmp(2,"onconnect")=""
^tmp(3,"onmessage")="^sdsdf"
^tmp(4,"onmessage")="Hello123xcf789фйбоз"
^tmp(5,"onmessage")="toAll"
^tmp(6,"onmessage")="get"
^tmp(7,"onmessage")="getBig"
^tmp(8,"onclose")=""
USER>zw ^tmp
^tmp=8
^tmp(1,"onbeforeconnect")=$lb("/asd s HTTP/1.1///","127.0.0.1:10081","","hybi-10")
^tmp(2,"onconnect")=""
^tmp(3,"onmessage")="^dfg"
^tmp(4,"onmessage")="Hello123xcf789фйбоз"
^tmp(5,"onmessage")="toAll"
^tmp(6,"onmessage")="get"
^tmp(7,"onmessage")="getBig"
^tmp(8,"onclose")=""
Для этого вам понадобится:
Примечание: Детали по настройке SSL в СУБД Caché можно посмотреть в одной из предыдущих статей [13] данного блога.
SAMPLES>do ##class(net.WebSocketServer).Start("demo.Server",,"WebSocketSSL")
SAMPLES>do ##class(net.WebSocketServer).StartPolicy()
"ws://127.0.0.1:10081 ..."
на
"wss://127.0.0.1:10081 ..."
В итоге мы имеем веб-приложение, серверную часть и базу данных, реализованные исключительно в рамках СУБД Caché.
Исходный проект [14]
Автор: servitRM
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/web-razrabotka/8466
Ссылки в тексте:
[1] команды: http://docs.intersystems.com/cache20121/csp/docbook/DocBook.UI.Page.cls?KEY=GIOD_tcp
[2] инкапсуляция: http://ru.wikipedia.org/wiki/Инкапсуляция_(программирование)
[3] %IO.ServerSocket: http://docs.intersystems.com/cache20121/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25IO.ServerSocket
[4] %IO.Socket: http://docs.intersystems.com/cache20121/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25IO.Socket
[5] CSP: http://docs.intersystems.com/cache20121/csp/docbook/DocBook.UI.Page.cls?KEY=GCSP_intro
[6] ZEN: http://docs.intersystems.com/cache20121/csp/docbook/DocBook.UI.Page.cls?KEY=GZEN_intro
[7] WebSocket: http://ru.wikipedia.org/wiki/WebSocket
[8] плагином: https://github.com/gimite/web-socket-js
[9] Студии: http://docs.intersystems.com/cache20121/csp/docbook/DocBook.UI.Page.cls?KEY=GSTD_Intro
[10] отсюда: http://techdows.com/2011/10/flash-player-11-offline-installer.html
[11] www.intersystems.com/zen" : http://www.intersystems.com/zen"
[12] Портале: http://docs.intersystems.com/cache20121/csp/docbook/DocBook.UI.Page.cls?KEY=GSA_using_portal#GSA_using_portal_pages
[13] статей: http://habrahabr.ru/company/intersystems/blog/144310/
[14] Исходный проект: http://db.tt/8TaPFjPF
Нажмите здесь для печати.