和js的混合编程,如何正确使用Nodejs
分类:计算机网络

事情的起因是这样的, 因为某些原因, 最近在写 Nodejs 的 c++ module, 然后在js这边调用。  网络通信自然离不开ssl, 于是需要链接到Openssl的库。

如何正确使用Nodejs 的 c++ module 链接到 OpenSSL,nodejsopenssl

事情的起因是这样的, 因为某些原因, 最近在写 Nodejs 的 c++ module, 然后在js这边调用。  网络通信自然离不开ssl, 于是需要链接到Openssl的库。

我们本来的期望是,需要用户安装有Openssl的运行库, 然后我们的c++ module 动态链接到Openssl的so库上来运行。

起初一切看起来还不错,直到我们发现这个openssl的函数不能工作:

PKCS7_sign()
PKCS7_sign ( )
我们发现:

如果我们的 c++ 模块与Openssl库动态链接的话, 编译都没问题. 但是运行会出现: PKCS7_sign 符号无法找到的错误.
如果我们的 c++ 模块与Openssl库静态链接的话, 编译也没问题, 但是运行时,调用这个函数的地方没有效果, 这个函数返回值是 0. 按照文档表示出现错误, 但是用 Openssl的函数 ERR_get_error 获取错误码也是0. 表示没有错误码.
在linux上是这样, 那在Mac上呢? 用Mac试了一下, 发现Mac没有问题. 于是,想到这可能是Nodejs的一个bug. 然后就去 Nodejs 给它报了一个bug: []

同时, google上搜索了 nodejs linking to openssl 类似的关键字.

找到这样几篇文章:

通过搜索, 我们发现, 原来Nodejs自己也使用了Openssl 库, 推测nodejs自己的crypto模块也是使用Openssl lib实现的. 这点从Nodejs的源码中就能发现, 它包含了最新的Openssl的全部源码.

其中写上面第一篇文章: 的那个帅哥是Nodejs的开发人员.

基本结论:

Nodejs 自己使用了Openssl
在Nodejs 0.6之前, Nodejs是动态链接到 Openssl 库的. 而之后的版本都是静态链接的.
这时发现 Node 那边已经回复我的bug了:

Node 解释的原因:

Node 自己编译之后, 把自己没用到的符号清除, 所以我们在运行时就找不到符号了. 于是他们把这bug 修掉了. 保留了全部符号. 这导致 Node 的体积大了 400k.

感谢Node的快速回复, 不得不佩服Node的活跃程度. 赞.

图片 1
因为项目的原因,最近经常使用node.js搭RESTful接口。
性能还是很不错啦,感觉比Spring Boot之类的要快。而且在不错的性能之外,只要程序结构组织好,别让太多的回调把程序结构搞乱,整体开发效率比Java快的就太多了。

理解 NodeJs

Nodejs 自己使用了Openssl.
在Nodejs 0.6之前, Nodejs是动态链接到 Openssl 库的, 而之后的版本都是静态链接的.

  • nodejs 到底是什么?
    nodejs 官方 有一段解释: "Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices."

    注意其中的一些关键点:nodejs是一个平台, 它构建在chrome的v8引擎之上,能简易的构建快速,可扩展的网络应用程序...
    这里官方用的网络应用程序,整个描述没有提到webserver等等概念,这段描述中,有两个是重点,把握住这两点,就算掌握了nodejs的真谛:

    • chrome的v8引擎
    • 事件驱动的非阻塞io模型
  • nodejs核心
    nodejs只是libuv的一个应用.
    nodejs核心主要是由两部分组成的:

    • v8引擎, 它负责把javascript代码解释成本地的二进制代码运行.
    • libuv, 类似windows上的窗口消息机制,它主要负责订阅和处理系统的各种内核消息,而且它也实现了消息循环(这个几乎就和windows 的窗口消息循环是一个概念). 它的前身是linux上的libev, 专门封装linux上的内核消息机制,后来nodejs重写了它, 并在windows上使用iocp技术重新实现了一遍.所以nodejs现在能跨平台运行在windows上了.
    • 闭包回调,非阻塞的异步处理机制.
  • libuv
    nodejs 其实就是 libuv 的一个应用而已。

    你自己写程序也可以集成libuv进来, 这样你的c++程序就有了消息循环了. 不再是简单main函数了. 你可以订阅系统的事件, 然后当事件发生时, 系统会调用你的回调函数, 就跟windows上的button click事件一样方便. 而且是跨平台的哦. 是不是很酷. 你几乎可以订阅所有的系统事件, 比如socket事件, 文件读写事件等等.

    nodejs只是libuv的一个应用. 先了解libuv才能了解nodejs的实质和前世今生.

  • nodejs 汇总
    nodejs简单的说只是把javascript解释成c++的回调, 并挂在libuv消息循环上, 等待处理. 这样就实现了非阻塞的异步处理机制(non-blocking I/O model).

    那么为什么是javascript而不是其他的语言. 很简单, 因为javascript的闭包. 这非常适合做回调函数. 因为我们一般都希望当回调发生时, 它能记住它原来所在的上下文. 这就是闭包最好的应用场景.

  • nodejs到底做了什么?
    从另一个角度看,基础的工作都分别由v8libuv做了.那么nodejs到底做了什么呢? 先看一下nodejs的文档,除了用javascript封装libuv框架之外,nodejs实现了这些api功能. 这些api大部分是用javascript写的, 也有一部分是c++写的.

    nodejs官方的仓库中有很多nodejs的插件. 有了这些plugins,nodejs就可以实现非常丰富的功能了.

  • 参考资料

    • Neil: 你可能误解nodejs了–通俗的概括nodejs的真相

我们本来的期望是,需要用户安装有Openssl的运行库, 然后我们的c++ module 动态链接到Openssl的so库上来运行。

大家有在实际项目里使用nodejs?windows环境解答

即将在一个新项目中采用node.js。windows客户端也打算用node.js做,C-S间的通讯靠socket.io,差不多类似tcp的做法,基本上不算是B/S应用了。  

如果想进一步提高效率,使用c++来优化部分模块是不错的选择。尤其可贵的是nodejs对于同c++的混合编程支持的很好,个人感觉跟写Python的扩展模块处于同样的易用水平。

起初一切看起来还不错,直到我们发现这个openssl的函数不能工作:

直接打开html正确,但用nodejs打开就没显示图片

。。图片也属于资源啊。 你也要映射图片的url 到 路由里面,然后通过fs 读取 然后response写出来。  

的 c++ module 链接到 OpenSSL,nodejsopenssl 事情的起因是这样的, 因为某些原因, 最近在写 Nodejs 的 c++ module, 然后在js这边...

我们从Hello World开始:
首先要有一个空白的工作目录,在其中建立一个node包管理文件package.json,内容为:

PKCS7_sign()
PKCS7_sign ( )
我们发现:

{
  "name": "test-cpp-module",
  "version": "0.1.0",
  "private": true,
  "gypfile": true
}

如果我们的 c++ 模块与Openssl库动态链接的话, 编译都没问题. 但是运行会出现: PKCS7_sign 符号无法找到的错误.
如果我们的 c++ 模块与Openssl库静态链接的话, 编译也没问题, 但是运行时,调用这个函数的地方没有效果, 这个函数返回值是 0. 按照文档表示出现错误, 但是用 Openssl的函数 ERR_get_error 获取错误码也是0. 表示没有错误码.
在linux上是这样, 那在Mac上呢? 用Mac试了一下, 发现Mac没有问题. 于是,想到这可能是Nodejs的一个bug. 然后就去 Nodejs 给它报了一个bug: []

随后在目录中执行命令:npm install node-addon-api --save安装nodejs扩展模块的开发支持包。这里假设你已经安装配置好了nodejs和相应的npm包管理工具,还有xcode的相关命令行编译工具。我们不重复这些基本工具的安装配置,需要的话请参考官网相关文档。
上面命令执行完成,我们就完成了基本开发环境的配置。

同时, google上搜索了 nodejs linking to openssl 类似的关键字.

c++的模块由binding.gyp文件描述,并完成自动编译的相关配置工作,我们新建一个binding.gyp文件,内容为:

找到这样几篇文章:

{
  "targets": [
    {
      "target_name": "democpp",
      "sources": [
        "democpp.cc"
      ],
      "include_dirs": [
        "<!@(node -p "require('node-addon-api').include")"
      ],
      "dependencies": [
        "<!(node -p "require('node-addon-api').gyp")"
      ],
      "cflags!": ["-fno-exceptions"],
      "cflags_cc!": ["-fno-exceptions"],
      "defines": ["NAPI_CPP_EXCEPTIONS"],
      "xcode_settings": {
        "GCC_ENABLE_CPP_EXCEPTIONS": "YES"
      }
    }
  ]
}

  • 文件中首先使用target_name指定了编译之后模块的名称。
  • sources指明c++的源文件,如果有多个文件,需要用逗号隔开,放到同一个数组中。
  • include_dirs是编译时使用的头文件引入路径,这里使用node -p执行node-addon-api模块中的预置变量。
  • dependencies是必须的,不要改变。
  • 后面部分,cflags!/cflags_cc!/defines三行指定如果c++程序碰到意外错误的时候,由NAPI接口来处理,而不是通常的由c++程序自己处理。这防止因为c++部分程序碰到意外直接就退出了程序,而是由nodejs程序来捕获处理。如果是在Linux中编译使用,有这三行就够了。
  • 但如果是在macOS上编译使用,则还要需要最后一项xcode-settings设置,意思相同,就是关闭macOS编译器的意外处理功能。
    最后是c++的源码,democpp.cc文件:

#include <napi.h>

using namespace Napi;

String Hello(const CallbackInfo& info) {
  return String::New(info.Env(), "world");
}
Napi::Object  Init(Env env, Object exports) {
  exports.Set("hello", Function::New(env, Hello));
  return exports;
}
NODE_API_MODULE(addon, Init)

程序中引入napi.h头文件,使用Napi的namespace还有最后的NODE_API_MODULE(addon,Init)都是模板化的,照抄过来不用动。
Init函数中,使用exports.Set()引出要暴露给nodejs调用的函数。如果有多个需要引出的函数,就写多行。
Hello函数是我们主要完成工作的部分,本例中很简单,只是用字符串的方式返回一个“world”。

通过搜索, 我们发现, 原来Nodejs自己也使用了Openssl 库, 推测nodejs自己的crypto模块也是使用Openssl lib实现的. 这点从Nodejs的源码中就能发现, 它包含了最新的Openssl的全部源码.

以上democpp.cc/binding.gyp/package.json三个文件准备好之后,在命令行执行:npm install,顺利的话会得到这样的输出信息:

其中写上面第一篇文章: 的那个帅哥是Nodejs的开发人员.

$ npm install

> test-cpp-module@0.1.0 install /home/andrew/Documents/dev/html/nodejs/callcpp
> node-gyp rebuild

  SOLINK_MODULE(target) Release/nothing.node
  CXX(target) Release/obj.target/democpp/democpp.o
  SOLINK_MODULE(target) Release/democpp.node

基本结论:

这表示编译顺利完成了,如果碰到错误,可以根据错误信息去判断解决方案。通常都是环境配置缺少相关程序或者上述的三个文件有打字错误。
下面我们验证一下模块的编译结果,在命令行使用nodejs,引入编译的模块文件,然后调用hello函数来看看:

Nodejs 自己使用了Openssl
在Nodejs 0.6之前, Nodejs是动态链接到 Openssl 库的. 而之后的版本都是静态链接的.
这时发现 Node 那边已经回复我的bug了:

> $ node
> democpp=require("./build/Release/democpp.node")
{ hello: [Function] }
> democpp.hello()
'world'
> 

Node 解释的原因:

上面是最简单的一个范例,下面我们增加一点难度。在GNU的环境下,通常我们的程序都会包含很多第三方的扩展库,我们这里再举一个调用openssl的例子:
package.json文件不用修改,我们不需要在nodejs层面增加新的依赖包。
编译带第三方扩展库的c++程序,通常需要在编译时指定额外的头文件包含路径和链接第三方库,这些都是在binding.gyp中指定的,这些指定在nodejs自动编译的时候,会解析并应用在命令行的编译工具中。

Node 自己编译之后, 把自己没用到的符号清除, 所以我们在运行时就找不到符号了. 于是他们把这bug 修掉了. 保留了全部符号. 这导致 Node 的体积大了 400k.

{
  "targets": [
    {
      "target_name": "democpp",
      "sources": [
        "democpp.cc"
      ],
      "include_dirs": [
        "<!@(node -p "require('node-addon-api').include")"
      ],
      "libraries": [ 
        '-lssl -lcrypto',
      ],
      "dependencies": [
        "<!(node -p "require('node-addon-api').gyp")"
      ],
      "cflags!": ["-fno-exceptions"],
      "cflags_cc!": ["-fno-exceptions"],
      "defines": ["NAPI_CPP_EXCEPTIONS"],
      "xcode_settings": {
        "GCC_ENABLE_CPP_EXCEPTIONS": "YES"
      }
    }
  ]
}

感谢Node的快速回复, 不得不佩服Node的活跃程度. 赞.

在macOS和常用linux版本中,openssl的头文件会自动安装在系统的头文件路径中,比如/usr/local/include,所以这里头文件的引入路径并没有增加。如果使用了自己安装的扩展库,需要在include_dirs一节增加新的头文件包含路径。
接着增加了libraries一节,指定了Openssl扩展库的链接参数-lssl -lcrypto,这个是必须的。
最后是修改democpp.cc文件,添加一个使用openssl中的md5算法对字符串进行md5编码的函数:

您可能感兴趣的文章:

  • Linux中openssl/opensslv.h找不到问题的解决方法
  • linux系统中使用openssl实现mysql主从复制
  • linux下安装openssl、swoole等扩展的详细步骤
  • 浅析PKI加密解密 OpenSSL
  • Windows安装配置C/C++(VS2017)OpenSSL开发环境配置教程
#include <napi.h>
#include <openssl/md5.h>

using namespace Napi;

void openssl_md5(const char *data, int size, unsigned char *buf){
    MD5_CTX c;
    MD5_Init(&c);
    MD5_Update(&c,data,size);
    MD5_Final(buf,&c);
}

String GetMD5(const CallbackInfo& info) {
  Env env = info.Env();
  std::string password = info[0].As<String>().Utf8Value();
  //printf("md5 in str:%s %ldn",password.c_str(),password.size());
  unsigned char hash[16];
  memset(hash,0,16);
  openssl_md5(password.c_str(),password.size(),hash);
  char tmp[3];
  char md5str[33]={};
  int i;
    for (i = 0; i < 16; i++){
      sprintf(tmp,"%02x",hash[i]);
      strcat(md5str,tmp);
    }
  return String::New(env, md5str,32);
}

String Hello(const CallbackInfo& info) {
  return String::New(info.Env(), "world");
}
Napi::Object  Init(Env env, Object exports) {
  exports.Set("hello", Function::New(env, Hello));
  exports.Set("md5", Function::New(env, GetMD5));
  return exports;
}
NODE_API_MODULE(addon, Init)

为了工作方便,源码中增加了一个没有引出的openssl_md5函数,仅供程序内部使用。因为没有引出,nodejs并不知道这个函数的存在。
从nodejs传递参数给c++的函数,是使用info[0].As<String>().Utf8Value()这样的形式。返回值到nodejs在hello函数中就已经看过了。
各项修改完成,同样回到命令行使用npm install重新编译。编译的过程和信息略,我们直接看调用的测试:

> $ node
> democpp=require("./build/Release/democpp.node")
{ hello: [Function], md5: [Function] }
> democpp.hello()
'world'
> democpp.md5("abc")
'900150983cd24fb0d6963f7d28e17f72'
> 

想验证一下计算的正确性?可以直接执行openssl试试:

$ echo -n "abc" | openssl md5
900150983cd24fb0d6963f7d28e17f72

嗯,无悬念的相同。

参考文档


本文由美高梅网址发布于计算机网络,转载请注明出处:和js的混合编程,如何正确使用Nodejs

上一篇:js中直接声明一个对象的方法 下一篇:没有了
猜你喜欢
热门排行
精彩图文