5.5 Client development


  • 5.5.6 Lua/C++ client

    1. When generating the server source code, generate the lua template code example.lua via -luaTemplate.

    java –jar <path to limax.jar> xmlgen –script –luaTemplate example.server.xml
    

    2. Create the C++ solution and the applicate project for the client.

    3. Add the limax project under the cpp directory, the limax.lua project and the liblua project of the Limax source code under the lua directory into the solution.

    4. Add the reference for the application project, referring to the above three projects.

    5. Edit the application project's attribute, and the limax source code directory, lualib header file directory, lualib source code directory as the "Additional Include Directories".

    The source code of the main function:


    #include "stdafx.h"
    #include <limax.h>
    #include <iostream>
    #include <lua.hpp>
    #include <limax.lua.h>
    using namespace limax;
    class MyLuaApp : public EndpointListener
    {
        lua_State* L;
    public:
        MyLuaApp()
        {
            L = luaL_newstate();
            luaL_openlibs(L);
            int e = luaL_dofile(L, "callback.lua");
            if (e != LUA_OK)
            {
                std::cout << "lua load 'callback.lua' failed! " << lua_tostring(L, -1) << std::endl;
                exit(-1);
            }
            auto sehptr = LuaCreator::createScriptEngineHandle(L, -1, false, [this](int s, int e, const std::string& m){ onErrorOccured(s, e, m); });
            if (!sehptr)
                exit(-1);
            lua_pop(L, 1);
            Endpoint::openEngine();
            auto config = Endpoint::createEndpointConfigBuilder(
                    "127.0.0.1", 10000, LoginConfig::plainLogin("testabc", "123456", "test"))
                ->scriptEngineHandle(sehptr)
                ->build();
            Endpoint::start(config, this);
        }
        ~MyLuaApp(){
            std::mutex mutex;
            std::condition_variable_any cond;
            std::lock_guard<std::mutex> l(mutex);
            Endpoint::closeEngine([&](){
                std::lock_guard<std::mutex> l(mutex);
                cond.notify_one();
            });
            cond.wait(mutex);
        }
        void run()  { Sleep(2000); }
        void onManagerInitialized(EndpointManager*, EndpointConfig*) { std::cout << "onManagerInitialized" << std::endl; }
        void onManagerUninitialized(EndpointManager*) { std::cout << "onManagerUninitialized" << std::endl; }
        void onTransportAdded(Transport*) { std::cout << "onTransportAdded" << std::endl; }
        void onTransportRemoved(Transport*){ std::cout << "onTransportRemoved" << std::endl; }
        void onAbort(Transport*) { std::cout << "onAbort" << std::endl; }
        void onSocketConnected() { std::cout << "onSocketConnected" << std::endl; }
        void onKeyExchangeDone() { std::cout << "onKeyExchangeDone" << std::endl; }
        void onKeepAlived(int ping) { std::cout << "onKeepAlived " << ping << std::endl; }
        void onErrorOccured(int errorsource, int errorvalue, const std::string& info) { std::cout << "onErrorOccured " << errorsource << " " << errorvalue << " " << info << std::endl; }
        void destroy() {}
    };
    int _tmain(int argc, _TCHAR* argv[])
    {
        MyLuaApp().run();
        return 0;
    }    
    

    The EndpointListener message code has no any difference with the other C++ version.

    The major difference is that the constructor function initiates the Lua virtual machine before initiating the engine, loads the script code callback.lua on the virtual machine, creates the handler of the script engine and sets it when creating the configuration. The destructor stops the Lua virtual machine after stopping the engine.

    callback.lua:

    The callback.lua comes from the generated example.lua code when generating the server, and adds the ctx.send line to implement the function in the example.


    v100.share.MyTemporaryView.onopen = function(this, instanceid, memberids)
        this[instanceid].onchange = this.onchange
        print('v100.share.MyTemporaryView.onopen', this[instanceid], instanceid, memberids)
        ctx.send(v100.share.MySessionView, "99999")
    end    
    

    Known from here, except for the language feature, there is no any other difference with the javascript version.

    The path of the callback.lua should be noted. If running in the console, it should be placed in the same directory as the exe files; if running in the VS2013, it should be placed in the root directory of the project.


    Launch the server, run the client, and get the result:

    onManagerInitialized

    onSocketConnected

    onKeyExchangeDone

    onTransportAdded

    v100.share.MyTemporaryView.onopen table: 00674C50 3 table: 00674C00

    v100.share.MyTemporaryView.onchange table: 00674C50 61440 _var0 Hello 61440 NEW

    onKeepAlived 16

    v100.share.MySessionView.onchange table: 00674840 61440 var0 Hello 61440 NEW

    v100.share.MySessionView.onchange table: 00674840 61440 var0 99999 REPLACE

    v100.share.MyTemporaryView.onchange table: 00674C50 61440 _var0 99999 REPLACE

    onTransportRemoved

    limax close

    onManagerUninitialized

    The result looks like the same as the previous versions, except for the table: XXXXXXXX because that the object of the lua is the table. The print here does not resolve it.


    Matters need attention:

    1. The header file limax.lua.h should be included behind the lua.hpp.

    2. The lua virtual machine is created before the openEngine to give an example. In the detailed project application, the virtual machine provided by the application should be directly used to the control operatoin implemented in the onXXX of the View could directly drive the application logic.

    3. When creating the configure, .executor method is normally called to assign the thread expected by the application.


  • 5.5.7 Lua/C# client

    1. When generating the server source code, generate the lua template code example.lua through the -luaTemplate

    java –jar <path to limax.jar> xmlgen –script –luaTemplate example.server.xml
    

    2. First, the client creates the C# solution and the application project.

    3. Add the limax project under the csharp directory, the luacs project, the clrlua project and the liblua_s project under the lua directory of the Limax source code into the solution.

    4. Add the project reference for the application project, reference to the limax project, the luacs project and the clrlua project.

    5. Change the object platform of the current project in the project attributes from the AnyCPU to the x86, and is consistent with the others as the x86.

    source code:


    using System;
    using System.Text;
    using System.IO;
    using System.Threading;
    using limax.net;
    using limax.script;
    using limax.endpoint;
    using limax.endpoint.script;
    
    namespace ConsoleApplicationLuaCS
    {
        class MyListener : EndpointListener
        {
            public void onAbort(Transport transport)
            {
                Exception e = transport.getCloseReason();
                Console.WriteLine("onAbort " + transport + " " + e);
            }
            public void onManagerInitialized(Manager manager, Config config)
            {
                Console.WriteLine("onManagerInitialized " + config.GetType().Name + " " + manager);
            }
            public void onManagerUninitialized(Manager manager)
            {
                Console.WriteLine("onManagerUninitialized " + manager);
            }
            public void onTransportAdded(Transport transport)
            {
                Console.WriteLine("onTransportAdded " + transport);
            }
            public void onTransportRemoved(Transport transport)
            {
                Exception e = transport.getCloseReason();
                Console.WriteLine("onTransportRemoved " + transport + " " + e);
            }
            public void onSocketConnected()
            {
                Console.WriteLine("onSocketConnected");
            }
            public void onKeyExchangeDone()
            {
                Console.WriteLine("onKeyExchangeDone");
            }
            public void onKeepAlived(int ms)
            {
                Console.WriteLine("onKeepAlived " + ms);
            }
            public void onErrorOccured(int source, int code, Exception exception)
            {
                Console.WriteLine("onErrorOccured " + source + " " + code);
            }
        }
    
        class Program
        {
            private static void start()
            {
                string callback = File.ReadAllText("callback.lua", Encoding.UTF8);
                Endpoint.openEngine();
                EndpointConfig config = Endpoint.createEndpointConfigBuilder(
                        "127.0.0.1", 10000, LoginConfig.plainLogin("testabc", "123456", "test"))
                    .scriptEngineHandle(new LuaScriptHandle(new Lua((string msg)=>Console.WriteLine(msg)), callback))
                    .build();
                Endpoint.start(config, new MyListener());
            }
            private static void stop()
            {
                object obj = new object();
                Action done = () => { lock (obj) { Monitor.Pulse(obj); } };
                lock (obj)
                {
                    Endpoint.closeEngine(done);
                    Monitor.Wait(obj);
                }
            }
            static void Main(string[] args)
            {
                start();
                Thread.Sleep(2000);
                stop();
            }
        }
    }
    

    Actually, this code is not much different from the code of the other C# versions, except for using the LuaScriptHandle when creating the configure, which also could compare with the script mode of the Java version.


    callback.lua:

    The callback.lua comes from the generated example.lua code when generating the server code, and adds the ctx.send line to implement the function of the sample.


      v100.share.MyTemporaryView.onopen = function(this, instanceid, memberids)
        this[instanceid].onchange = this.onchange
        print('v100.share.MyTemporaryView.onopen', this[instanceid], instanceid, memberids)
        ctx.send(v100.share.MySessionView, "99999")
      end
    

    Seen from the above code, the callback.lua is identical with the Lua/C++ version.

    It should be noted that the callback.lua should be placed in the same directory as the exe.


    Launch the server, run the client and obtain the result:

    onManagerInitialized DefaultEndpointConfig EndpointManagerImpl

    onSocketConnected

    onKeyExchangeDone

    onTransportAdded limax.net.StateTransportImpl

    v100.share.MyTemporaryView.onopen table: 048E98F0 31 table: 048E9940

    v100.share.MyTemporaryView.onchange table: 048E98F0 36864 _var0 Hello 36864 NEW

    v100.share.MySessionView.onchange table: 048E6820 36864 var0 Hello 36864 NEW

    onKeepAlived 10

    v100.share.MySessionView.onchange table: 048E6820 36864 var0 99999 REPLACE

    v100.share.MyTemporaryView.onchange table: 048E98F0 36864 _var0 99999 REPLACE

    onTransportRemoved limax.net.StateTransportImpl System.Exception: channel closed manually

    limax close nil

    onManagerUninitialized EndpointManagerImpl

    This result is identical with the previous versions.


  • 5.5.8 Javascript/C++ client (SpiderMonkey)

    • Prepare the javascript engine library

      1. Download the javascript engine source code package from the website https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Releases/45.

      2. Create the binary package following the procedure described in the document https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Build_Documentation.

      3. Edit the js/public/CharacterEncoding.h file in the source code directory, search the UTF8CharsToNewTwoByteCharsZ method, and change the prefix extern TwoByteCharsZ to JS_PUBLIC_API(TwoByteCharsZ), which is necessary in the glue code provided by the Limax. The SpiderMonkey APIprovides the conversion from the javascript string to the UTF8 string. Except for this method, there is no way to convert the UTF8 string to the javascript string, which is a defect.

      4. The following example needs to create the x64 version package by using the parameter --target=x86_64-pc-mingw32 --host=x86_64-pc-mingw32. At the same time, the parameter --disable-jemalloc should be added, or there is error when the when the program runs with the version compiled by the vs2013. If the cl reports the internal error when when compiling the current vs2013 version, the vs2013 should be upgraded to the latest version. In addition, the modules directory after the source package is expanded should be noted. It's internal directory named src must be rename to zlib. At least, this bug existes in the version 45.0.2.

      5. After the binary package has been created, copy the build_OPT.OBJ directory created in the operating steps and the directory name totally to the javascript/SpiderMonkey/mozjs45 directory in the limax source code directory.

      6. When the binary package is built to DEBUG build, the application must be built to DEBUG build; and when the binary package is built to RELEASE build, the application also must be built to RELEASE build, or there might be the CRT error during the application running.


    • Example (as with the previous example, using vs2013)

      1. Copy the definition of the two variables var providers, var limax in the example.html, past to the example.js, and replace the console.log and console.err with print.

      2. Create the C++ solution and console application project, and configure the platform of the application project as x64.

      3. Add the limax project in the cpp directory with limax.js project in the javascript/SpiderMonkey directory of the Limax source code into the solution.

      4. Add the reference of the current project, refer to the limax and limax.js projects.

      5. Correctly configure the header file search path of the current application project, including the header file search path of the javascript engine (reference to the configuration of the limax.js), cpp/limax/include and javascript/SpiderMonkey/include in the Limax source code directory.

      6. Correctly configure the library search path and dependent library (reference to the configuration of the limax.js).


      source code


      #include "stdafx.h"
      #include <limax.h>
      #include <iostream>
      #include <jsapi.h>
      #include <js/Conversions.h>
      #include <limax.js.h>
      using namespace limax;
      
      class MyJsApp : public EndpointListener
      {
          std::shared_ptr<JsEngine> engine;
      public:
          MyJsApp()
          {
              engine = JsEngine::create(1048576);
              engine->execute([&](JSRuntime* rt, JSContext* cx, JS::HandleObject global)
              {
                  JS_SetErrorReporter(rt, [](JSContext *cx, const char *message, JSErrorReport *report){
                      runOnUiThread([message, report](){
                          fprintf(stderr, "%s:%u:%s\n", report->filename ? report->filename : "[no filename]", (unsigned int)report->lineno, message);
                      });
                  });
                  JS_DefineFunction(cx, global, "print", [](JSContext *cx, unsigned argc, JS::Value *vp){
                      JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
                      std::string s;
                      for (unsigned i = 0; i < argc; i++)
                      {
                          char *p = JS_EncodeString(cx, JS::RootedString(cx, JS::ToString(cx, args[i])));
                          s += p;
                          s += ' ';
                          JS_free(cx, p);
                      }
                      if (s.length() > 0)
                          s.pop_back();
                      runOnUiThread([s](){ puts(s.c_str()); });
                      args.rval().setUndefined();
                      return true;
                  }, 0, JSPROP_READONLY | JSPROP_PERMANENT);
              });
              auto sehptr = JsCreator::createScriptEngineHandle(engine, "example.js");
              Endpoint::openEngine();
              auto config = Endpoint::createEndpointConfigBuilder(
                      "127.0.0.1", 10000, LoginConfig::plainLogin("testabc", "123456", "test"))
                  ->scriptEngineHandle(sehptr)
                  ->build();
              Endpoint::start(config, this);
          }
          ~MyJsApp(){
              std::mutex mutex;
              std::condition_variable_any cond;
              std::lock_guard<std::mutex> l(mutex);
              Endpoint::closeEngine([&](){
                  std::lock_guard<std::mutex> l(mutex);
                  cond.notify_one();
              });
              cond.wait(mutex);
          }
          void run()  { for (int i = 0; i < 200; i++) { Sleep(10); uiThreadSchedule(); } }
          void onManagerInitialized(EndpointManager*, EndpointConfig*) { std::cout << "onManagerInitialized" << std::endl; }
          void onManagerUninitialized(EndpointManager*) { std::cout << "onManagerUninitialized" << std::endl; }
          void onTransportAdded(Transport*) { std::cout << "onTransportAdded" << std::endl; }
          void onTransportRemoved(Transport*){ std::cout << "onTransportRemoved" << std::endl; }
          void onAbort(Transport*) { std::cout << "onAbort" << std::endl; }
          void onSocketConnected() { std::cout << "onSocketConnected" << std::endl; }
          void onKeyExchangeDone() { std::cout << "onKeyExchangeDone" << std::endl; }
          void onKeepAlived(int ping) { std::cout << "onKeepAlived " << ping << std::endl; }
          void onErrorOccured(int errorsource, int errorvalue, const std::string& info) { std::cout << "onErrorOccured " << errorsource << " " << errorvalue << " " << info << std::endl; }
          void destroy() {}
      };
      
      int _tmain(int argc, _TCHAR* argv[])
      {
          {
              MyJsApp().run();
          }
          uiThreadSchedule();
          return 0;
      }
      

      If the program runs on the vs2013, the created example.js should be placed into the project directory. If the program runs on the console, it should be placed into the the same directory as the exe.


      Launch the server, run the client, and get the result:

      onManagerInitialized

      onSocketConnected

      onKeyExchangeDone

      onTransportAdded

      onKeepAlived 1

      v100.share.MyTemporaryView.onopen [object Object] 3 36864

      v100.share.MyTemporaryView.onchange [object Object] 36864 _var0 Hello 36864 NEW

      v100.share.MySessionView.onchange [object Object] 36864 var0 Hello 36864 NEW

      v100.share.MySessionView.onchange [object Object] 36864 var0 99999 REPLACE

      v100.share.MyTemporaryView.onchange [object Object] 36864 _var0 99999 REPLACE

      onTransportRemoved

      onManagerUninitialized

      limax close

      This running result is consistent with the previous versions.


    • Description of the code

      1. JsEngine::create(1048576) creates the javascript engine and uses 1M memory. The appropriate memory size must be estimated based on the application size, or the out of memory might cause the javascipt virtual machine OutOfMemory.

      2. JsEngine encapsulates the SpiderMonkey engine through the thread server and provides two methods: execute and wait. Execute submits the javascript task to JsEngine and returns immediately, wait submits the javascript task to JsEngine and returns after this task finishs. Before the JsEngine releases, all the tasks submitted by the execute are ensured to be completed.

      3. The ErrorHandle of the javascript engine is immediately installed after the JsEngine is created; the print method is added into the global space, and the print method is used to output the result in the example.js.

      4. Please pay attention to the several runOnUIThread call of the code, the display result is output through UI thread, not the JsEngine thread.

      5. The main thread is used as the UI thead and the MyJsApp.run method should be noted, which is different with the previous code, not directly Sleep 2000ms, but schedule once every 10ms through uiThreadSchedule() to ensure the output method submitted by the JsEngine thread is executed. The implementation of the _tmain function is a little different with the previous code, which ensures the "limax close" could be correctly output. The cause is that after the MyJsApp.run is executed, MyJsApp Endpoint.closeEngine when destructing the object and causes the ctx.close() in the example.js is executed, the print task is arranged in the UI thread task queue and finally needs to schedule an UI thread to finish these tasks.


  • 5.5.9 Javascript/C# client (SpiderMondey)

    • Refer to the previous section to prepare the javascript engine


    • Development example

      1. Copy the definition of the variables var providers and var limax of the example.html, paste to the example.js, and replace the console.log and console.err with print.

      2. Create the C# solution and console application project, and set the application project's platform as x64.

      3. Add the limax project in the csharp directory, jscs project, clrjs project and nativejs project in the javascript/SpiderMonkey of the Limax source code to the solution.

      4. Add the reference of the current project, reference to the limax project, jscs project and clrjs project.


      Source code:


      using System;
      using System.IO;
      using System.Text;
      using System.Threading;
      using limax.net;
      using limax.script;
      using limax.endpoint;
      using limax.endpoint.script;
      
      namespace ConsoleApplicationJsCS
      {
          class MyListener : EndpointListener
          {
              public void onAbort(Transport transport)
              {
                  Exception e = transport.getCloseReason();
                  Console.WriteLine("onAbort " + transport + " " + e);
              }
              public void onManagerInitialized(Manager manager, Config config)
              {
                  Console.WriteLine("onManagerInitialized " + config.GetType().Name + " " + manager);
              }
              public void onManagerUninitialized(Manager manager)
              {
                  Console.WriteLine("onManagerUninitialized " + manager);
              }
              public void onTransportAdded(Transport transport)
              {
                  Console.WriteLine("onTransportAdded " + transport);
              }
              public void onTransportRemoved(Transport transport)
              {
                  Exception e = transport.getCloseReason();
                  Console.WriteLine("onTransportRemoved " + transport + " " + e);
              }
              public void onSocketConnected()
              {
                  Console.WriteLine("onSocketConnected");
              }
              public void onKeyExchangeDone()
              {
                  Console.WriteLine("onKeyExchangeDone");
              }
              public void onKeepAlived(int ms)
              {
                  Console.WriteLine("onKeepAlived " + ms);
              }
              public void onErrorOccured(int source, int code, Exception exception)
              {
                  Console.WriteLine("onErrorOccured " + source + " " + code);
              }
          }
      
          class Program
          {
              private static void start(JsContext jsc)
              {
                  string init = File.ReadAllText("example.js", Encoding.UTF8);
                  Endpoint.openEngine();
                  EndpointConfig config = Endpoint.createEndpointConfigBuilder(
                          "127.0.0.1", 10000, LoginConfig.plainLogin("testabc", "123456", "test"))
                      .scriptEngineHandle(new JavaScriptHandle(jsc, init))
                      .build();
                  Endpoint.start(config, new MyListener());
              }
              private static void stop()
              {
                  object obj = new object();
                  Action done = () => { lock (obj) { Monitor.Pulse(obj); } };
                  lock (obj)
                  {
                      Endpoint.closeEngine(done);
                      Monitor.Wait(obj);
                  }
              }
              static void Main(string[] args)
              {
                  JsContext jsc = new JsContext();
                  start(jsc);
                  Thread.Sleep(2000);
                  stop();
                  jsc.shutdown();
              }
          }
      }
      

      Put the previous created example.js and all dll in the javascript/SpiderMonkey/mozjs45/build_OPT.OBJ/dist/bin/ directory to the same directory as the exe.


      Launch the server, run the client, and get the result:

      onManagerInitialized DefaultEndpointConfig EndpointManagerImpl

      onSocketConnected

      onKeyExchangeDone

      onTransportAdded limax.net.StateTransportImpl

      v100.share.MySessionView.onchange [object Object] 36864 var0 Hello 36864 NEW

      v100.share.MyTemporaryView.onopen [object Object] 190 36864

      v100.share.MyTemporaryView.onchange [object Object] 36864 _var0 Hello 36864 NEW

      onKeepAlived 12

      v100.share.MySessionView.onchange [object Object] 36864 var0 99999 REPLACE

      v100.share.MyTemporaryView.onchange [object Object] 36864 _var0 99999 REPLACE

      onTransportRemoved limax.net.StateTransportImpl System.Exception: channel closed manually

      limax close null

      onManagerUninitialized EndpointManagerImpl

      This result is consistent with the previous versions.


    • Description of the code

      1. Restricted by the thread model of the SpiderMonkey, have to provide a JsContext encapsulated class, the JsContext creates a thread to associate with the Js operating object. Js operating object supports the interaction between the C# and javascript. Refer the appendix CLR/Javascript for the details. After all the operations have been finished, it needs to shutdown JsContext object.


  • 5.5.10 The conclusion of the client development

    1. The Limax client library covers the most common development environment.

    2. In the usage of the thread, in the most conditions, it needs to call the .executor to set the message notification thread for the application its own when creating the configure. All the EndpointListener message and the View message would be notified to this thread.

    3. The change of the view in the server could drive the Listener in all kinds of clients and report the data change of the view. A problem should be noted that if multiple Listener of the same field of the same View need be registered, the relationship between the notification order and register order of the Listener has no guarantee.

    4. Emphasize agian, that the static mode, the Variant mode, and the script mode could be mixedly used in various typed language implementation. The mixed usage has no much actual meaning and could lead to the confusion of the development. So unless it is necessary, we suggest that do not use like this.


Prev Next