js 服务器

这是为了方便使用javascript进行web开发准备的。

它与nodejs的不同在于它使用duktape引擎。虽然该引擎的执行效率比不上v8引擎,但是它是轻量级的,效率虽不够好,但也不太差。其执行效率方面的劣势可通过多进程化服务器、开启lru缓存等多种途径来弥补。压测显示,多进程化后,其并发性能高于nodejs。

duktapeVSnode

上图是一个简单的hello,world测试,node在8888端口,js_server在9090端口。第一次压测js_server为单进程,第三次压测时采用多进程化js_server。

特别需要说明的是,在测试比较中发现,nodejs(v11)完成上述测试通常需要很多的内存消耗——50MB-120MB,与并发数量正相关。而js_server的内存消耗近通常不会超过1.8MB,与并发数量没什么关系。

来看例子:


#include <mongols/js_server.hpp>

int main(int, char**) {
    int port = 9090;
    const char* host = "127.0.0.1";
    mongols::js_server
    server(host, port, 5000, 8096, 0/*2*/);
    server.set_root_path("html/js");
    server.set_enable_bootstrap(true);
    server.run("html/js/package", "html/js/package");
}

run方法的第一个参数表示js模块的搜索路径,第二个参数则表示c/c++模块的搜索路径。

//index.js

/*
var foo = require('foo')
mongols_res.header('Content-Type','text/plain;charset=UTF-8')
mongols_res.content(foo.hello())
mongols_res.status(200)
*/

/*
var math = require('math/math')
var v=math.minus(1,3)/math.add(100,3)
mongols_res.header('Content-Type','text/plain;charset=UTF-8')
mongols_res.content(v.toString())
mongols_res.status(200)
*/


/*
var loaded = mongols_module.require('adder/libadder','adder')
mongols_res.header('Content-Type','text/plain;charset=UTF-8')
mongols_res.content(loaded?adder(1,2).toString():'failed load c module.')
mongols_res.status(200)
*/

/*
var loaded = mongols_module.require('concat/libconcat','concat')
mongols_res.header('Content-Type','text/plain;charset=UTF-8')
mongols_res.content(loaded?concat('Hello,','world'):'failed load c module.')
mongols_res.status(200)
*/



///*
mongols_res.header('Content-Type','text/plain;charset=UTF-8')
mongols_res.content('hello,world')
mongols_res.status(200)
//*/

模块

js 模块

require加载

c/c++ 模块

动态库模块

动态库可以用mongols_module.require加载:

#include <mongols/lib/dukglue/duktape.h>

#ifdef __cplusplus
extern "C" {
#endif

    duk_ret_t adder(duk_context *ctx) {
        int i;
        int n = duk_get_top(ctx); /* #args */
        double res = 0.0;

        for (i = 0; i < n; i++) {
            res += duk_to_number(ctx, i);
        }

        duk_push_number(ctx, res);
        return 1; /* one return value */
    }


#ifdef __cplusplus
}
#endif

编译为libadder.so——编译时需使用pkg-config --libs --cflags mongols,然后使用:


 var loaded = mongols_module.require('adder/libadder','adder')
 mongols_res.header('Content-Type','text/plain;charset=UTF-8')
 mongols_res.content(loaded?adder(1,2).toString():'failed load c module.')
 mongols_res.status(200)

mongols_module.require方法的第一个参数加载路径,第二个参数是函数名。返回布尔值。

也可以在动态库中注册c++函数或者类。方法是先如上注册普通的c函数,然后在该函数中注册c++函数和类。例如:


#include <string>
#include <mongols/lib/dukglue/dukglue.h>
#include <mongols/lib/dukglue/duktape.h>
#include <mongols/js_server.hpp>

#include "dukglue/duktape.h"

class demo : public mongols::js_object {
public:
    demo() = default;
    virtual~demo() = default;

    std::string echo(const std::string& text) {
        return text;
    }

    static bool loaded;
};

bool demo::loaded = false;

#ifdef __cplusplus
extern "C" {
#endif

    duk_ret_t cpp(duk_context *ctx) {
        if (!demo::loaded) {
            dukglue_register_constructor<demo>(ctx, "demo");
            dukglue_register_method(ctx, &demo::echo, "echo");
            demo::loaded = true;
        }
        if (demo::loaded) {
            duk_push_true(ctx);
        } else {
            duk_push_false(ctx);
        }
        return 1; /* one return value */
    }

#ifdef __cplusplus
}
#endif

调用方法,先用mongols_module.require加载动态库,然后调用c函数注册c++类和函数:


var loaded = mongols_module.require('cpp/libcpp', 'cpp')
var registered = cpp()
if (loaded && registered) {
    var a = new demo()
    mongols_res.header('Content-Type', 'text/plain;charset=UTF-8')
    mongols_res.content(a.echo('hello,cpp class'))
    mongols_res.status(200)
    mongols_module.free(a)
}

类和函数注入

js_server还支持直接注入c/c++函数和类到服务器:


class person : public mongols::js_object {
public:

    person() : mongols::js_object(), name("Tom"), age(0) {
    }
    virtual~person() = default;

    void set_name(const std::string& name) {
        this->name = name;
    }

    void set_age(unsigned int age) {
        this->age = age;
    }

    const std::string& get_name() const {
        return this->name;
    }

    unsigned int get_age() {
        return this->age;
    }
private:
    std::string name;
    unsigned int age;
};

class studest : public person {
public:

    studest() : person() {
    }
    virtual~studest() = default;

    double get_score() {
        return this->score;
    }

    void set_score(double score) {
        this->score = score;
    }
private:
    double score;
};
// some code


server.register_class_constructor<person>("person");
server.register_class_method(&person::set_age, "set_age");
server.register_class_method(&person::get_age, "get_age");
server.register_class_method(&person::set_name, "set_name");
server.register_class_method(&person::get_name, "get_name");

server.register_class_constructor<studest>("studest");
server.register_class_method(&studest::get_score, "get_score");
server.register_class_method(&studest::set_score, "set_score");
server.set_base_class<mongols::js_object, person>();
server.set_base_class<person, studest>();

server.register_function(&mongols::md5, "md5");
server.register_function(&mongols::sha1, "sha1");

var handlebars = require('handlebars')
var s=new studest()
s.set_name("Jerry")
s.set_age(14)
s.set_score(74.6)
var text='hello,world'
var tpl=handlebars.compile('name: {{name}}\nage: {{age}}\nscore: {{score}}\ntext:{{text}}\ntext_md5: {{md5}}\ntext_sha1: {{sha1}}')
var content=tpl({name:s.get_name(),age:s.get_age(),score:s.get_score(),text:text,md5:md5(text),sha1:sha1(text)})
mongols_module.free(s)
mongols_res.header('Content-Type','text/plain;charset=UTF-8')
mongols_res.content(content)
mongols_res.status(200)

就这一点而言,js_server与lua_server是基本一致的。不过,lua的执行效率远高于duktape——如果不开启lru缓存。

特别注意:

  • 所有需要注册的c++类必须继承mongols::js_object类且不允许多重继承。
  • 所有注册的c++类,用new创建、使用完毕后,一定要使用mongols_module.free方法进行垃圾回收,因为duktape不管理c++类实例生命期。

API

mongols_req

  • uri
  • method
  • client
  • param
  • user_agent
  • has_header
  • get_header
  • has_form
  • get_form
  • has_session
  • get_session
  • has_cookie
  • get_cookie
  • has_cache
  • get_cache

    mongols_res

  • status
  • content
  • header
  • session
  • cache

单文件入口

js_server支持单文件入口模式。方法是引入route模块,与lua_server的使用方法类似:


var route = require('route').get_instance()

route.add(['GET'], '^\/(hello|test)?\/?$', function (req, res, param) {
    res.header('Content-Type', 'text/plain;charset=UTF8')
    res.content(param.toString())
    res.status(200)
})

route.add(['POST', 'PUT'], '^\/.*$', function (req, res, param) {
    res.header('Content-Type', 'text/plain;charset=UTF8')
    res.content(req.uri())
    res.status(200)
})


route.run(mongols_req, mongols_res)

其他

mongols_module对象实例下有个read方法,可完整读取文件。

比较

v8比duktape快,这是毫无疑义的。单纯执行复杂一点的代码,前者的效率可能是后者的几倍甚至十几倍。

然而,对web应用而言,承载js引擎的服务器并发性能,具有更大的意义。例如,当js_server和nodejs都使用LRU缓存提升吞吐率时,js_server的吞吐率就会数倍于nodejs。这是由服务器性能决定的。比如:


var http = require('http');
var md5=require('md5')
var sha1=require('sha1')
var studest=require('./studest')
var handlebars = require('./handlebars')
const QuickLRU = require('quick-lru');
const lru = new QuickLRU({maxSize: 1000});


http.createServer(function (request, response) {
    response.writeHead(200, {'Content-Type': 'text/plain;charset=UTF-8'});

    if(lru.has('key')){
        response.end(lru.get('key'));
    }else{
        var s=new studest()
        s.set_name("Jerry")
        s.set_age(14)
        s.set_score(74.6)
        var text='hello,world'
        var tpl=handlebars.compile('name: {{name}}\nage: {{age}}\nscore: {{score}}\ntext:{{text}}\ntext_md5: {{md5}}\ntext_sha1: {{sha1}}')
        var content=tpl({name:s.get_name(),age:s.get_age(),score:s.get_score(),text:text,md5:md5(text),sha1:sha1(text)})
        lru.set('key',content)
        response.end(content)
    }
}).listen(8888);

以上是个略复杂的nodejs例子。开启LRU缓存可使得吞吐率2千多提升至2万多。如果开启多进程模式,nodejs的吞吐率可达到4万多。

但是,同样的逻辑,用js_server实现:


var handlebars = require('handlebars')
var s=new studest()
s.set_name("Jerry")
s.set_age(14)
s.set_score(74.6)
var text='hello,world'
var tpl=handlebars.compile('name: {{name}}\nage: {{age}}\nscore: {{score}}\ntext:{{text}}\ntext_md5: {{md5}}\ntext_sha1: {{sha1}}')
var content=tpl({name:s.get_name(),age:s.get_age(),score:s.get_score(),text:text,md5:md5(text),sha1:sha1(text)})
mongols_module.free(s)
mongols_res.header('Content-Type','text/plain;charset=UTF-8')
mongols_res.content(content)
mongols_res.status(200)

开启LRU缓存(仅仅1秒的缓存期)即可使得吞吐率从3百多提升至8万多——nodejs多进程也没有js_server轻快。如果同时多进程化,吞吐率可高达13万以上。如果再比较内存消耗,则js_server的优势会更加明显:每个nodejs进程都需要40MB+的内存,而js_server每个进程只需3MB+的内存。 因此,完全不必纠结于v8与duktape的比较:决定性的因素是承载脚本引擎的服务器,而非脚本引擎本身。

results matching ""

    No results matching ""