Erläutern Sie ausführlich, wie Sie C-Programme in JavaScript verwenden :
JavaScript ist eine flexible Skriptsprache, die problemlos mit Geschäftslogik umgehen kann. Wenn Kommunikation übertragen werden muss, wählen wir meist JSON- oder XML-Formate.
Aber wenn die Datenlänge sehr anspruchsvoll ist, ist die Effizienz des Textprotokolls sehr gering und es muss das Binärformat verwendet werden.
An diesem Tag im letzten Jahr stieß ich auf dieses Problem, als ich eine WAF herumwarf, die Front-End und Back-End kombinierte.
Da das Front-End-Skript viele Daten sammeln muss, die letztendlich in einem Cookie verborgen sind, ist die verfügbare Länge sehr begrenzt und beträgt nur einige Dutzend Bytes.
Wenn Sie JSON ohne nachzudenken verwenden, nimmt nur ein Tag-Feld {"enableXX": true}
die Hälfte der Länge ein. Im Binärformat ist die Markierung von „wahr“ oder „falsch“ jedoch nur eine Frage von 1 Bit, wodurch Hunderte Male Platz gespart werden können.
Gleichzeitig müssen die Daten auch einer Überprüfung, Verschlüsselung usw. unterzogen werden. Nur durch die Verwendung des Binärformats können diese Algorithmen einfach aufgerufen werden.
JavaScript unterstützt jedoch keine Binärdateien.
Das „nicht unterstützt“ bedeutet hier nicht „nicht umsetzbar“, aber es kann nicht „elegant umsetzbar“ sein. Sprache wurde erfunden, um Probleme elegant zu lösen. Auch ohne Sprache können Menschen mithilfe von Maschinenanweisungen Programme schreiben.
Wenn Sie JavaScript verwenden müssen, um Binärdateien zu manipulieren, sieht es am Ende so aus:
var flags = +enableXX1 << 16 | +enableXX2 << 15 | ...
Obwohl es implementiert werden kann, ist es sehr hässlich. Verschiedene harte Codierungen, verschiedene Bitoperationen.
Für Sprachen, die von Natur aus Binärdateien unterstützen, sieht es jedoch sehr elegant aus:
union { struct { int enableXX1: 1; int enableXX2: 1; ... }; int16_t value; } flags; flags.enableXX1 = enableXX1; flags.enableXX2 = enableXX2;
Der Entwickler muss nur eine Beschreibung definieren. Wenn Sie es verwenden, müssen Sie sich nicht um die Details kümmern, um wie viel das Feld versetzt ist und wie es gelesen und geschrieben wird.
Um einen ähnlichen Effekt zu erzielen, wurde zunächst eine JS-Version der Struktur gekapselt:
// 最初方案:封装一个 JS 结构体 var s = new Struct([ {name: 'month', bit: 4, signed: false}, ... ]); s.set('month', 12); s.get('month');
Die Details sind ausgeblendet und es sieht viel eleganter aus.
Das fühlt sich jedoch nicht immer perfekt an. Dinge wie Strukturen sollten von der Sprache bereitgestellt werden, aber jetzt müssen sie mit zusätzlichem Code implementiert werden, und das noch zur Laufzeit.
Darüber hinaus ist die Backend-Dekodierung in C implementiert, sodass zwei Sätze von Codes gepflegt werden müssen. Sobald sich die Datenstruktur oder der Algorithmus ändert, ist es sehr mühsam, JS und C gleichzeitig zu aktualisieren.
Also dachte ich: Kann ich einen Satz C-Code sowohl für das Front-End als auch für das Backend teilen?
Mit anderen Worten, Sie müssen in der Lage sein, C in JS zu kompilieren, damit es ausgeführt werden kann.
Es gibt viele Tools, die C in JS kompilieren können, das professionellste ist emscripten.
Die Verwendung von emscripten ist sehr einfach, ähnlich dem herkömmlichen C-Compiler, außer dass es JS-Code generiert.
./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; }
Sie können es nach der Kompilierung ausführen:
Es ist sehr interessant~ Sie können es ausprobieren, ich werde es hier nicht vorstellen.
Was uns jedoch am Herzen liegt, ist nicht der Spaß, sondern die Praktikabilität.
Tatsächlich umfasst sogar ein Hello World kompiliertes JS über 10.000 Zeilen, bis zu Hunderten von KB. Selbst wenn es mit GZIP komprimiert wird, sind es immer noch Dutzende KB.
Gleichzeitig verwendet emscripten die asm.js-Spezifikation und der Speicherzugriff wird über TypedArray implementiert.
Das bedeutet, dass Benutzer unter IE10 es nicht ausführen können. Auch das ist inakzeptabel.
Daher müssen wir folgende Verbesserungen vornehmen:
Größe reduzieren
Kompatibilität erhöhen
Verlassen wir uns zunächst auf emscripten selbst, um zu sehen, ob wir unser Ziel durch das Festlegen von Parametern erreichen können.
Nach einigen Versuchen gelang es jedoch nicht. Das kann nur man selbst erreichen.
Warum ist das endgültige Skript so groß und was ist darin enthalten? Nach der Analyse des Inhalts gibt es ungefähr diese Teile:
Hilfsfunktionen
Schnittstellensimulation
Initialisierungsvorgang
Laufzeitfunktion
Programmlogik
Zum Beispiel String- und Binärkonvertierung, Bereitstellung von Callback-Paketen usw. Diese sind grundsätzlich unnötig, wir können uns eine spezielle Callback-Funktion schreiben.
Bietet Datei-, Terminal-, Netzwerk-, Rendering- und andere Schnittstellen. Ich habe bereits Client-Spiele gesehen, die mit Emscripten portiert wurden, und es scheint, dass viele Schnittstellen simuliert werden.
Initialisierung des globalen Speichers, der Laufzeit und verschiedener Module.
Reines C kann nur einfache Berechnungen durchführen, und viele Funktionen basieren auf Laufzeitfunktionen.
Allerdings ist die Implementierung hinter einigen häufig verwendeten Funktionen äußerst komplex. Zum Beispiel hat malloc und free das entsprechende JS fast 2000 Zeilen!
Dies ist der echte JS-Code, der dem C-Programm entspricht. Da LLVM es während der Kompilierung optimiert, kann es sein, dass die Logik nicht mehr erkennbar ist.
Dieser Teil des Codes ist nicht groß und genau das, was wir wirklich wollen.
Tatsächlich kann das Programm, wenn es einige spezielle Funktionen nicht verwendet, dennoch ausgeführt werden, wenn die logische Funktion separat extrahiert wird!
Angesichts der Tatsache, dass unser C-Programm sehr einfach ist, ist es kein Problem, es einfach und grob zu extrahieren.
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,调试起来更容易。
每一门语言都有各自的优缺点。将不同语言的优势相互结合,可以让程序变得更优雅、更完美。
Das obige ist der detaillierte Inhalt vonErfahren Sie ausführlich, wie Sie C-Programme in JavaScript verwenden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!