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,与并发数量没什么关系。

来看例子:

  1. #include <mongols/js_server.hpp>
  2. int main(int, char**) {
  3. int port = 9090;
  4. const char* host = "127.0.0.1";
  5. mongols::js_server
  6. server(host, port, 5000, 8096, 0/*2*/);
  7. server.set_root_path("html/js");
  8. server.set_enable_bootstrap(true);
  9. server.run("html/js/package", "html/js/package");
  10. }

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

  1. //index.js
  2. /*
  3. var foo = require('foo')
  4. mongols_res.header('Content-Type','text/plain;charset=UTF-8')
  5. mongols_res.content(foo.hello())
  6. mongols_res.status(200)
  7. */
  8. /*
  9. var math = require('math/math')
  10. var v=math.minus(1,3)/math.add(100,3)
  11. mongols_res.header('Content-Type','text/plain;charset=UTF-8')
  12. mongols_res.content(v.toString())
  13. mongols_res.status(200)
  14. */
  15. /*
  16. var loaded = mongols_module.require('adder/libadder','adder')
  17. mongols_res.header('Content-Type','text/plain;charset=UTF-8')
  18. mongols_res.content(loaded?adder(1,2).toString():'failed load c module.')
  19. mongols_res.status(200)
  20. */
  21. /*
  22. var loaded = mongols_module.require('concat/libconcat','concat')
  23. mongols_res.header('Content-Type','text/plain;charset=UTF-8')
  24. mongols_res.content(loaded?concat('Hello,','world'):'failed load c module.')
  25. mongols_res.status(200)
  26. */
  27. ///*
  28. mongols_res.header('Content-Type','text/plain;charset=UTF-8')
  29. mongols_res.content('hello,world')
  30. mongols_res.status(200)
  31. //*/

模块

js 模块

require加载

c/c++ 模块

动态库模块

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

  1. #include <mongols/lib/dukglue/duktape.h>
  2. #ifdef __cplusplus
  3. extern "C" {
  4. #endif
  5. duk_ret_t adder(duk_context *ctx) {
  6. int i;
  7. int n = duk_get_top(ctx); /* #args */
  8. double res = 0.0;
  9. for (i = 0; i < n; i++) {
  10. res += duk_to_number(ctx, i);
  11. }
  12. duk_push_number(ctx, res);
  13. return 1; /* one return value */
  14. }
  15. #ifdef __cplusplus
  16. }
  17. #endif

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

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

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

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

  1. #include <string>
  2. #include <mongols/lib/dukglue/dukglue.h>
  3. #include <mongols/lib/dukglue/duktape.h>
  4. #include <mongols/js_server.hpp>
  5. #include "dukglue/duktape.h"
  6. class demo : public mongols::js_object {
  7. public:
  8. demo() = default;
  9. virtual~demo() = default;
  10. std::string echo(const std::string& text) {
  11. return text;
  12. }
  13. static bool loaded;
  14. };
  15. bool demo::loaded = false;
  16. #ifdef __cplusplus
  17. extern "C" {
  18. #endif
  19. duk_ret_t cpp(duk_context *ctx) {
  20. if (!demo::loaded) {
  21. dukglue_register_constructor<demo>(ctx, "demo");
  22. dukglue_register_method(ctx, &demo::echo, "echo");
  23. demo::loaded = true;
  24. }
  25. if (demo::loaded) {
  26. duk_push_true(ctx);
  27. } else {
  28. duk_push_false(ctx);
  29. }
  30. return 1; /* one return value */
  31. }
  32. #ifdef __cplusplus
  33. }
  34. #endif

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

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

类和函数注入

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

  1. class person : public mongols::js_object {
  2. public:
  3. person() : mongols::js_object(), name("Tom"), age(0) {
  4. }
  5. virtual~person() = default;
  6. void set_name(const std::string& name) {
  7. this->name = name;
  8. }
  9. void set_age(unsigned int age) {
  10. this->age = age;
  11. }
  12. const std::string& get_name() const {
  13. return this->name;
  14. }
  15. unsigned int get_age() {
  16. return this->age;
  17. }
  18. private:
  19. std::string name;
  20. unsigned int age;
  21. };
  22. class studest : public person {
  23. public:
  24. studest() : person() {
  25. }
  26. virtual~studest() = default;
  27. double get_score() {
  28. return this->score;
  29. }
  30. void set_score(double score) {
  31. this->score = score;
  32. }
  33. private:
  34. double score;
  35. };
  36. // some code
  37. server.register_class_constructor<person>("person");
  38. server.register_class_method(&person::set_age, "set_age");
  39. server.register_class_method(&person::get_age, "get_age");
  40. server.register_class_method(&person::set_name, "set_name");
  41. server.register_class_method(&person::get_name, "get_name");
  42. server.register_class_constructor<studest>("studest");
  43. server.register_class_method(&studest::get_score, "get_score");
  44. server.register_class_method(&studest::set_score, "set_score");
  45. server.set_base_class<mongols::js_object, person>();
  46. server.set_base_class<person, studest>();
  47. server.register_function(&mongols::md5, "md5");
  48. server.register_function(&mongols::sha1, "sha1");
  1. var handlebars = require('handlebars')
  2. var s=new studest()
  3. s.set_name("Jerry")
  4. s.set_age(14)
  5. s.set_score(74.6)
  6. var text='hello,world'
  7. var tpl=handlebars.compile('name: {{name}}\nage: {{age}}\nscore: {{score}}\ntext:{{text}}\ntext_md5: {{md5}}\ntext_sha1: {{sha1}}')
  8. var content=tpl({name:s.get_name(),age:s.get_age(),score:s.get_score(),text:text,md5:md5(text),sha1:sha1(text)})
  9. mongols_module.free(s)
  10. mongols_res.header('Content-Type','text/plain;charset=UTF-8')
  11. mongols_res.content(content)
  12. 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的使用方法类似:

  1. var route = require('route').get_instance()
  2. route.add(['GET'], '^\/(hello|test)?\/?$', function (req, res, param) {
  3. res.header('Content-Type', 'text/plain;charset=UTF8')
  4. res.content(param.toString())
  5. res.status(200)
  6. })
  7. route.add(['POST', 'PUT'], '^\/.*$', function (req, res, param) {
  8. res.header('Content-Type', 'text/plain;charset=UTF8')
  9. res.content(req.uri())
  10. res.status(200)
  11. })
  12. route.run(mongols_req, mongols_res)

其他

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

比较

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

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

  1. var http = require('http');
  2. var md5=require('md5')
  3. var sha1=require('sha1')
  4. var studest=require('./studest')
  5. var handlebars = require('./handlebars')
  6. const QuickLRU = require('quick-lru');
  7. const lru = new QuickLRU({maxSize: 1000});
  8. http.createServer(function (request, response) {
  9. response.writeHead(200, {'Content-Type': 'text/plain;charset=UTF-8'});
  10. if(lru.has('key')){
  11. response.end(lru.get('key'));
  12. }else{
  13. var s=new studest()
  14. s.set_name("Jerry")
  15. s.set_age(14)
  16. s.set_score(74.6)
  17. var text='hello,world'
  18. var tpl=handlebars.compile('name: {{name}}\nage: {{age}}\nscore: {{score}}\ntext:{{text}}\ntext_md5: {{md5}}\ntext_sha1: {{sha1}}')
  19. var content=tpl({name:s.get_name(),age:s.get_age(),score:s.get_score(),text:text,md5:md5(text),sha1:sha1(text)})
  20. lru.set('key',content)
  21. response.end(content)
  22. }
  23. }).listen(8888);

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

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

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

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

原文: https://mongols.hi-nginx.com/doc/js.html