📜 ⬆️ ⬇️

Work with sockets in Caché DBMS. An example of the implementation of the server side of the protocol WebSocket

Caché DBMS for communicating via TCP / IP with remote processes via sockets provides low-level commands , which can be a challenge for beginners.

Is it possible to use sockets “differently” without losing the flexibility, speed and convenience of development?


Of course, there is: it is enough to write an object wrapper around existing commands of open , use , close , etc. In terms of OOP, this is encapsulation .
')
Fortunately, such a class, even classes - one for the server part and one for the client part - have already been written and are delivered at least with the Caché version 5.2, namely:


Consider two simple examples with their use.

Example №1


Server side code

#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 " ..." , !
}


Client Code

#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 " ..." , !
}

  • Server Results:
      ...    bye!  ... 
  • Results on the client:
      ...    "  "    "bye!"  ... 

To dwell in detail on an example does not make sense, since everything is quite transparent.
I will note only that when receiving the stop line " bye! " From the client side, the server part completes its work.

Example 2


Code:

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" ),!
}
}

}


Example of the implementation of the server part to support the protocol WebSocket


The two examples above are quite simple, since the server part allowed only one client connection to be processed at a time.

What if we need to handle tens / hundreds of client connections at the same time?
To do this, use the % IO.ServerSocket method : ListenJob ()

Let's write an example more complicated, for example, add support for the WebSocket protocol to the Caché DBMS.

This will be especially true, given that Caché DBMS provides web application creation technology ( CSP ), and also has a ready-made framework with a rich set of visual, MVC and other components ( ZEN ).

And remembering that Caché, in addition to supporting OOP and SQL, can also act as NoSQL storage ...

Much has been written about the WebSocket protocol on the Internet. Based on it, they write chats, online games and everything that requires fast data exchange with the server (database).
I note that our demo will support only two versions of the protocol: 76 and 7.

For the tests, we need one of the modern browsers that have WebSocket support.
For older browsers, such as IE 8, you can use a special plugin that runs on top of Flash.

Technical details of the implementation of the protocol can be found in the source at the end of the article

Here we consider only a small example that implements the simplest web chat.
Note: The source code for implementing the WebSocket protocol in Caché ObjectScript is for informational purposes only.

So:
  1. in Studio, import the project into an area with customized web application support, for example, “SAMPLES”;
  2. create your server-side event handler class, inherit from the net.WebSocketEvents class , and redefine the corresponding methods in it.

    For example:

    /// .
    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" )= ""
    }

    }
  3. If you use a plugin that uses Flash (see above), then pre-install the latest version of Flash Player, which can be downloaded from here .
    Note: Do not forget to enable WebSocket in browsers in which they are disabled by default, for example in Opera.
  4. Launch the WebSocket connection processing daemon on the server:
     SAMPLES>do ##class(net.WebSocketServer).Start("demo.Server") 
  5. launch in your browser, for example, FireFox, the demo.webclient page (class), the source code of which is the following:

    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" ]
    {
    <
    page xmlns = "www.intersystems.com/zen" 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;
    }

    }
  6. Press the "Start" button first. If the connection of the browser to the Caché DBMS is successful, then you will see the status “open”.
    If there is no connection, then check the Troubleshooting section. On the local machine, everything should work without problems.
  7. when the connection is successfully established, you can try different tests - in the example there are four of them - in any order.
  8. At the end of the test, do not forget to press the “Stop” button.
  9. Try running multiple browsers and repeat steps 6-8.

Let's look at the contents of the log on the server:
  • Firefox 12
     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")="" 
  • IE 9
     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")="" 

Using the secure wss protocol instead of ws


For this you will need:
  1. set up a SSL server configuration in the Portal with the name, for example, WebSocketSSL and set the " Peer certificate verification level " to " None ".
    Note: Details on setting up SSL in Caché DBMS can be found in one of the previous articles in this blog.
  2. When starting the WebSocket daemon, you need to specify our SSL configuration:
     SAMPLES>do ##class(net.WebSocketServer).Start("demo.Server",,"WebSocketSSL") 
  3. If you are using a plugin that uses Flash (see above), then first run the policy file processing daemon on port 843 :
     SAMPLES>do ##class(net.WebSocketServer).StartPolicy() 
  4. recompile the demo.webclient class before changing the string
      "ws: //127.0.0.1: 10081 ..." 
    on
      "wss: //127.0.0.1: 10081 ..." 
  5. further see above.

As a result, we have a web application, a server part and a database implemented exclusively within the Caché DBMS.

Original project

Source: https://habr.com/ru/post/144311/


All Articles