Maison  >  Article  >  interface Web  >  Vous apprendre une explication détaillée de la façon d'utiliser les programmes C en JavaScript

Vous apprendre une explication détaillée de la façon d'utiliser les programmes C en JavaScript

黄舟
黄舟original
2017-03-09 14:40:371742parcourir

Vous apprendre une explication détaillée de la façon d'utiliser les programmes C en JavaScript  :

JavaScript est un langage de script flexible qui peut facilement gérer la logique métier. Lorsque nous devons transmettre des communications, nous choisissons principalement les formats JSON ou XML.

Mais lorsque la longueur des données est très exigeante, l'efficacité du protocole texte est très faible et le format binaire doit être utilisé.

Ce jour-là l'année dernière, j'ai rencontré ce problème alors que je lançais un WAF combinant front-end et back-end.

Comme le script front-end doit collecter beaucoup de données, qui sont finalement cachées dans un cookie, la longueur disponible est très limitée, seulement quelques dizaines d'octets.

Si vous utilisez JSON sans réfléchir, un seul champ de balise {"enableXX": true} occupera la moitié de la longueur. Cependant, en binaire, marquer vrai ou faux n'est qu'une question de 1 bit, ce qui peut économiser des centaines de fois d'espace.

Dans le même temps, les données doivent également passer par une vérification, un cryptage, etc. Ce n'est qu'en utilisant le format binaire que ces algorithmes peuvent être facilement appelés.

Implémentation élégante

Cependant, JavaScript ne prend pas en charge le binaire.

Le "non pris en charge" ici ne signifie pas "impossible à implémenter", mais il ne peut pas être "implémenté avec élégance". Le langage a été inventé pour résoudre les problèmes avec élégance. Même sans langage, les humains peuvent écrire des programmes à l’aide d’instructions machine.

Si vous devez utiliser JavaScript pour manipuler le binaire, cela finira par ressembler à ceci :

var flags = +enableXX1 << 16 | +enableXX2 << 15 | ...

Bien qu'il puisse être implémenté, il est très moche. Divers codages en dur, diverses opérations sur les bits.

Cependant, pour les langages qui prennent intrinsèquement en charge le binaire, cela semble très élégant :

union {
    struct {
        int enableXX1: 1;
        int enableXX2: 1;
        ...
    };
    int16_t value;
} flags;

flags.enableXX1 = enableXX1;
flags.enableXX2 = enableXX2;

Le développeur n'a qu'à définir une description. Lorsque vous l'utilisez, vous n'avez pas à vous soucier des détails du décalage du champ ni de la manière de le lire et de l'écrire.

Afin d'obtenir un effet similaire, une version JS de la structure a d'abord été encapsulée :

// 最初方案:封装一个 JS 结构体
var s = new Struct([
    {name: &#39;month&#39;, bit: 4, signed: false},
    ...
]);

s.set(&#39;month&#39;, 12);
s.get(&#39;month&#39;);

Les détails sont cachés et cela semble beaucoup plus élégant.

Élégant mais pas parfait

Cependant, cela ne semble pas toujours être le plus parfait. Des éléments tels que les structures devraient être fournis par le langage, mais ils doivent maintenant être implémentés avec du code supplémentaire, et cela se fait toujours pendant l'exécution.

De plus, le décodage back-end est implémenté en C, donc deux ensembles de codes doivent être conservés. Une fois la structure des données ou l'algorithme modifié, il est très difficile de mettre à jour JS et C en même temps.

Alors je me demandais : puis-je partager un ensemble de code C pour le front-end et le back-end ?

En d’autres termes, vous devez être capable de compiler C en JS pour fonctionner.

Apprenez à connaître emscripten

Il existe de nombreux outils capables de compiler C en JS, le plus professionnel est emscripten.

L'utilisation d'emscripten est très simple, similaire au compilateur C traditionnel, sauf qu'il génère du code JS.

./emcc hello.c -o hello.html

// hello.c
#include <stdio.h>
#include <time.h>  

int main() {
    time_t now;
    time(&now);
    printf("Hello World: %s", ctime(&now));
    return 0;
}

Vous pouvez l'exécuter après compilation :

C'est très intéressant~ Vous pouvez l'essayer, je ne le présenterai pas ici.

Défauts pratiques

Cependant, ce qui nous importe n'est pas le plaisir, mais l'aspect pratique.

En fait, même un JS compilé Hello World compte plus de 10 000 lignes, jusqu'à des centaines de Ko. Même compressé avec GZIP, il reste encore des dizaines de Ko.

Dans le même temps, emscripten utilise la spécification asm.js et l'accès à la mémoire est implémenté via TypedArray.

Cela signifie que les utilisateurs inférieurs à IE10 ne peuvent pas l'exécuter. C’est également inacceptable.

Par conséquent, nous devons apporter les améliorations suivantes :

  • Réduire la taille

  • Augmenter la compatibilité

Tout d’abord, comptons sur emscripten lui-même pour voir si nous pouvons atteindre notre objectif en définissant des paramètres.

Cependant, après quelques tentatives, cela n’a pas abouti. Cela ne peut être réalisé que par vous-même.

Réduire la taille

Pourquoi le script final est-il si gros et qu'est-ce qu'il contient ? Après analyse du contenu, on retrouve grosso modo ces parties :

  • Fonctions auxiliaires

  • Simulation d'interface

  • Opération d'initialisation

  • Fonction d'exécution

  • Logique du programme

Fonction auxiliaire

Par exemple, conversion de chaînes et binaires, fourniture d'un packaging de rappel, etc. Celles-ci sont fondamentalement inutiles, nous pouvons écrire une fonction de rappel spéciale pour nous-mêmes.

Simulation d'interface

Fournit des interfaces de fichier, de terminal, de réseau, de rendu et autres. J'ai déjà vu des jeux clients portés avec emscripten, et il semble que de nombreuses interfaces soient simulées.

Opération d'initialisation

Initialisation de la mémoire globale, du runtime et de divers modules.

Fonctions d'exécution

Pure C ne peut effectuer que des calculs simples, et de nombreuses fonctions reposent sur des fonctions d'exécution.

Cependant, la mise en œuvre de certaines fonctions couramment utilisées est extrêmement complexe. Par exemple, malloc et free, le JS correspondant compte près de 2000 lignes !

Logique du programme

C'est le vrai code JS correspondant au programme C. Étant donné que LLVM l'optimise lors de la compilation, la logique peut devenir méconnaissable.

Cette partie du code n'est pas grande et c'est ce que nous voulons vraiment.

En fait, si le programme n'utilise pas certaines fonctions spéciales, il peut quand même s'exécuter si la fonction logique est extraite séparément !

Considérant que notre programme C est très simple, ce n'est pas un problème de l'extraire simplement et grossièrement.

C 程序对应的 JS 逻辑位于 // EMSCRIPTEN_START_FUNCS 和 // EMSCRIPTEN_END_FUNCS 之间。过滤掉运行时函数,剩下的就是 100% 的逻辑代码了。

增加兼容

接着解决内存访问的兼容性问题。

首先了解下,为何要用 TypedArray。

emscripten 申请了一大块 ArrayBuffer 来模拟内存,然后关联了一些 HEAP 开头的变量。

这些不同类型的 HEAP 共享同一块内存,这样就能高效的指针操作。

然而不支持 TypedArray 的浏览器,显然无法运行。所以得提供个 polyfill 兼容下。

但经分析,这几乎不可能实现 —— 因为 TypedArray 和数组一样,是通过索引来访问的:

var buf = new Uint8Array(100);
buf[0] = 123;     // set
alert(buf[0]);    // get

然而 [] 操作符在 JS 里是无法重写的,因此难以将其变成 setter 和 getter。况且不支持 TypedArray 的都是低版本 IE,更不用考虑 ES6 的那些特征。

于是琢磨 IE 的私有接口。比如用 onpropertychange 事件来模拟 setter。不过这样做效率极低,而且 getter 仍不易实现。

经过一番考虑,决定不用钩子的方式,而是直接从源头上解决 —— 修改语法!

我们用正则,找出源码中的赋值操作:

HEAP[index] = val;

替换成:

HEAP_SET(index, val);

类似的,将读取操作:

HEAP[index]

替换成:

HEAP_GET(index)

这样,原先的索引操作,就变成函数调用了。我们就能接管内存的读写,并且没有任何兼容性问题!

然后实现 8、16、32 位有无符号的版本。通过 JS 的 Array 来模拟,非常简单。

麻烦的是模拟 Float32 和 Float64 两个类型。不过本次 C 程序中并未用到浮点,所以就暂不实现了。

到此,兼容性问题就解决了。

大功告成

解决了这些缺陷,我们就可以愉快的在 JS 中使用 C 逻辑了。

作为脚本,只需关心采集哪些数据。这样 JS 代码就非常的优雅:

数据的储存、加密、编码,这些底层数据操作,则通过 C 实现。

编译时使用 -Os 参数优化体积。最终的 JS 混淆压缩之后,还不到 2 KB,十分小巧精炼。

更完美的是,我们只需维护一份代码,即可同时编译出前端和后端两个版本。

于是,这个「前后端 WAF」开发就容易多了。

所有的数据结构和算法,都由 C 实现。前端编译成 JS 代码,后端编译成 lua 模块,供 nginx-lua 使用。

前后端的脚本,都只需关注业务功能即可,完全不用涉及数据层面的细节。

测试版

事实上,还有第三个版本 —— 本地版。

因为所有的 C 代码都在一起,因此可以方便的编写测试程序。

这样就无需启动 WebServer、打开浏览器来测试了。只需模拟一些数据,直接运行程序即可测试,非常轻量。

同时借助 IDE,调试起来更容易。

小结

每一门语言都有各自的优缺点。将不同语言的优势相互结合,可以让程序变得更优雅、更完美。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn