zsh(とtar)を使ってネットワーク経由のファイル転送を効率的に行う方法についてです。
ARMボード(RaspberryPiとかPCduino等)を使っていると、scpなどでホスト間で大きなファイル転送をする際に、CPUネックになりがち。rsyncやscpするときに暗号をarcfour等にする*1ことでCPU使用率を低減する方法もありますが、そもそもLAN内等で暗号化しなくても良い場合もあります。こういうとき、単一のファイルであれば、
受信側(host1): nc -l 12345 >file 送信側(host2): nc host1 12345 <file
とncを使えばOK。(ポートがファイアウォールで塞がれていないことが前提ですが。)
複数のファイルだと
受信側(host1): nc -l 12345 | tar vxf - 送信側(host2): tar vcf - files/* | nc host1 12345
のように転送する方法がありますが、これだとパイプ経由でのデータコピーのためにncによってCPU使用率が上がってしまい、単発の場合と比べて性能が悪くなってしまいます。
bashであれば、送信側についてはncを経由せずに、
tar vcf - files >/dev/tcp/host1/12345
とすることで直接TCPソケットに書き出せます。が、受信側は知る限りこういった方法がありません。
zshであれば、標準でついてくるnet/tcpモジュールを使って、送受信側とも直接TCPソケットに読み書きさせることができます。
ただ、net/tcpが提供する各コマンドはシェルスクリプトでTCPサーバを書くことを念頭に作られているようで、今回の目的にはローレベルのztcpコマンドを操作しないといけないようです。手動では若干面倒なので、.zshrc内にあらかじめ以下の関数を作っておきます。
function file_recv { if [ $# = 0 ]; then echo file_recv listen_port return fi ( autoload -U tcp_open; tcp_open >/dev/null 2>/dev/null # define ztcp ztcp -l "$1" fd_listen=$REPLY echo "Waiting on port $1 (fd $fd_listen) ..." ztcp -a $fd_listen || echo failed. fd_accept=$REPLY echo "Connected. (fd $fd_accept)" tar vxf - <&$fd_accept ztcp -c $fd_listen ztcp -c $fd_accept ) } function file_send { if [ $# -lt 3 ]; then echo file_send host port files ... return fi ( autoload -U tcp_open; tcp_open >/dev/null 2>/dev/null # define ztcp ztcp "$1" "$2" shift 2 tar vcf - "$@" >&$REPLY ztcp -c $REPLY ) }
あとは、
受信側(host1): file_recv 12345 送信側(host2): file_send host1 12345 files/*
とすればOKです。送信側は上記bashを使ってもOKです、その方が事前に準備要らないですからね。*2
しかしなんというか、ここまでするなら、tarも使わずsendfile(2)とかsplice(2)等を使ってユーザ空間でのコピーも避けられるようなさらに効率的な転送コマンドを用意する方がいい気がしてきた。
あと、tar vcf - ... の部分に任意のコマンドを指定できるようにした方が利用シーンが広がって有用かもしれない(効率のいいncとして使う)。
*1:参考: http://d.hatena.ne.jp/rx7/20101025/p1
*2:どちらから接続するかを入れ替えれば、"受信側は上記関数の準備不要"とするということもできるはず。