RustでWebSocketサーバをクロスコンパイルしてZynqで動かした
以前に、 HaskellでWebSocketのサーバを動作させ、バイナリデータをブラウザで受け取ってCanvasで描画する実験を行いました。
最終的には、ADCのデータをブラウザに送って、ブラウザ上で各種処理を行うことを目論んでいます。
現在はADCのデータは次のように取得しています。
Cで作成したTCPのサーバプログラムをZynq上で動作させ、ADCのデータをmmap等により取得し、TCP経由で送信します。 クライアント側は、C#で作成したプログラムでデータを受信して、各種処理を行っています。
これだと、どうしてもWindows専用のプログラムになってしまいます(Monoを使ってLinuxでも動くのかも知れませんが)。 近頃はかなりの事がブラウザでできるようになっていますし、 そうすればクロスプラットフォームで使えますので、そちらの方が望ましいと思います。
それで上記のような実験を行っていました。 ただ、Haskellでarmv7にクロスコンパイルするのは少し面倒そうでした。
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のデータをブラウザまで送り込むことができそうです。