RustでWebSocketサーバをクロスコンパイルしてZynqで動かした

以前に、 HaskellでWebSocketのサーバを動作させ、バイナリデータをブラウザで受け取ってCanvasで描画する実験を行いました。

最終的には、ADCのデータをブラウザに送って、ブラウザ上で各種処理を行うことを目論んでいます。

現在はADCのデータは次のように取得しています。

Cで作成したTCPのサーバプログラムをZynq上で動作させ、ADCのデータをmmap等により取得し、TCP経由で送信します。 クライアント側は、C#で作成したプログラムでデータを受信して、各種処理を行っています。

これだと、どうしてもWindows専用のプログラムになってしまいます(Monoを使ってLinuxでも動くのかも知れませんが)。 近頃はかなりの事がブラウザでできるようになっていますし、 そうすればクロスプラットフォームで使えますので、そちらの方が望ましいと思います。

それで上記のような実験を行っていました。 ただ、Haskellでarmv7にクロスコンパイルするのは少し面倒そうでした。

Rustは、かなり簡単にクロスコンパイルが行えるということが分かり、 上記の目的が達成できるか、少しずつ試しています。

Rustでクロスコンパイル

上記の記事に従うことで、簡単にクロスコンパイル環境が構築できました。

次に、前回Haskellで作成したプログラムとほぼ同機能のプログラムを作りました。


extern crate ws;

use ws::{listen, Sender, Handler, Result, Message, CloseCode, Handshake};
use ws::Message::Text;
use ws::Message::Binary;
use ws::util::Token;

struct Server {
    out: Sender,
}

impl Handler for Server {
    fn on_open(&mut self, _shake: Handshake) -> Result<()> {
        println!("on_open");
        Ok(())
    }
    fn on_message(&mut self, msg: Message) -> Result<()> {
        let mut vec: Vec = vec![0; 1920*1080];
        match msg {
            Text(_) => {
                let vec: Vec = vec![0; 1920*1080];
                self.out.send(vec)
            },
            Binary(v) => {
                for j in 0..1080 {
                    for i in 0..1920 {
                        vec[j*1920+i] = (i+j+v[0] as usize) as u8;
                    }
                }
                self.out.send(Binary(vec))
            }
        }
    }
}

fn main() {
   listen("192.168.0.90:3012", |out| Server { out: out }).unwrap();
}

Cargo.tomlには、

[dependencies]
ws = "*"

を追加します。

$ cargo build --target armv7-unknown-linux-gnueabihf --release

とすることで、x86環境のUbuntuでarmv7ターゲットのバイナリができます。

ちなみに、Rustはdebugビルドとreleaseビルドでの実行速度の差がかなり大きいようです。 自分の印象だと、よほどカリカリに数値計算などを行っていなければ、 C/C++などの言語では、体感2,3倍程度の速度差ですが、Rustは10倍位に感じます (あくまで印象です)。

$ cp target/armv7-unknown-linux-gnueabihf/debug/ws-rust /srv/root/

出来上がったバイナリを、NFSマウントされた場所にコピーし、Zynq上で実行しました。

HTMLは少し修正して、次のようにしました。

<html>
    <body>
        <script
            src="https://code.jquery.com/jquery-3.2.1.js"
            integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE="
            crossorigin="anonymous"></script>
        <p><div id="time">Time[ms]</div></p>
        <canvas id="canvas" width="1920" height="1080"></canvas>
        <script type="text/javascript">
            $(window).on('load', function(){
                var tm = 0;
                var canvas = $('#canvas').get(0);
                var width = canvas.width;
                var height = canvas.height;
                var ctx = canvas.getContext('2d');
                var imageData = ctx.getImageData(0, 0, width, height);

                var buf = new ArrayBuffer(imageData.data.length);
                var buf8 = new Uint8ClampedArray(buf);
                var data = new Uint32Array(buf);
                var start_ms = performance.now();
                var elapsed_ms;
                var cn = new WebSocket('ws://192.168.0.90:3012/');

                cn.addEventListener('message', function(ev){
                    var reader = new FileReader();
                    reader.addEventListener("loadend", function(){
                        var ary = new Uint8Array(reader.result);
                        for (var j = 0; j < height; j++){
                            for (var i = 0; i < width; i++){
                                var val = ary[j*width+i];
                                data[j*width+i] = (255<<24)| (val<<16) | (val<<8) | val;
                            }
                        }
                        imageData.data.set(buf8);
                        ctx.putImageData(imageData, 0, 0);
                        if (tm % 60 == 0){
                            var current = performance.now();
                            elapsed_ms = current - start_ms;
                            $("#time").text(elapsed_ms/60.0 + "[ms]");
                            start_ms = current;
                        }
                    });
                    reader.readAsArrayBuffer(ev.data);
                    getData();
                });
                cn.addEventListener('error', function(ev){
                    console.log(ev);
                });

                var getData = function(){
                    var abuf = new ArrayBuffer(1);
                    var view = new Uint8Array(abuf);
                    view[0] = tm & 0xFF;
                    cn.send(abuf);
                    tm++;
                }
                cn.addEventListener('open', function(){
                    getData();
                });
            });
        </script>
    </body>
</html>

こちらをFirefoxで開くと、データ転送が始まって、画像が表示されました。

“動作画面”

200Mbps(25MB/s)程度のデータレートで転送されています。まぁまぁでしょうか。

これでWebSocketのサーバは簡単に作成できることが分かりましたので、 後はmmapやuioの制御のためのreadなどのsyscallがRustで実行できれば、 ADCのデータをブラウザまで送り込むことができそうです。

comments powered by Disqus