Perl: 2012年2月アーカイブ

昨日の深夜にサーバ接続テストをやったんだけど、23:30から準備を始めているはずなのに、全然先方からの連絡無し。
お客さんとこに入った業者が、俺の作ったサーバに向けてデータを投げてくるテストなんだけど、深夜1時になっても 2時になっても連絡無し。
で、2:30頃、やっと「データ流してるんで、届いてるか見てもらえますか」との連絡が。

まあ、こういうテストでは、開始時間がずれてしまうのはよくあることなので、仏のような心で俺は対応したよ。

で、テスト結果なんだけど、一部のデータだけうまいことヘッダ部とデータ部を分離できなくてエラーになってる。
あらら・・・と思って、ログの検証とか、データの解析とかしてたら、「4時までにテスト環境を本番環境に戻さないといけないので・・・」と 3:30にはテスト終了。翌日(つまり今夜)、再度テストを行うことになった。

いやあ、こっちの検証時間が 1時間しかないというのはどうなの?(^^;

まあ、原因は俺のコーディングミスだったんだけど(笑)

バイナリデータを正規表現を使って分割してるんだけど、例えば、

if ($data =~ /^(.{2})(.{2})\x00(.{740}).*$/) {
$key1 = $1;
$key2 = $2;
$body = $3;
}

みたいな処理で、760バイトあるデータなのに、$body になにもセットされないという状況。
ま、一眠りしてソース見たらすぐに間違いに気づいたんだけど、この時は深夜に延々待たされて、頭がぼーっとしてたんでね、すぐにミスに気づかなかった。

Perl 書ける人、わかる?

そう、s オプションが抜けてるんだね(^^;
s オプションは「ワイルドカードのドット( . )が改行にもマッチするようにする」オプションね。
このオプション付けないと、例えば LF(\x0A)にマッチングしてくれないんだよね(^^;
だから改行コードと同じコードが混ざることが想定されるバイナリデータのマッチングじゃ、必ず s オプションは付けないといかんよね。

ああ、しょうもないミスで今夜も寝不足確定だ(^^;
つーか、昨夜(というか今朝未明か)、もう少しうちの調査時間くれれば気づいたと思うのに・・・
とあるバイナリデータがある。
そのデータは、1バイト(8ビット)を2ビットずつに区切って、全部で60個ある装置の状態をヘルスデータとして流してくる。データの大きさは 2ビット x 60装置 = 15バイト。

その 2ビットの内容は、

0(00)...NG
1(01)...WRN(警告)
2(10)...OK

という具合。(カッコ内は 2進数)

その 1バイトの中で、装置番号の若いものが下位ビットにセットされる。
だから、4台の装置の状態が、

No.1 10(OK)
No.2 01(WRN)
No.3 10(OK)
No.4 00(NG)

となっていれば、'10011000'ではなく、下位から順にセットしていき '00100110' となる。16進であれば、0x26 だ。

この情報を装置の番号順に取っていきたい。つまり、15バイトのデータから1バイトずつ抜き出して、さらにその 1バイトの中の下位ビットから 2ビットずつ取っていきたい。

'00100110' というデータであれば、

1.下位 2ビットだけを残す論理積計算をおこなう。
 ('00000011' という 2進数と論理積を取れば、下位 2ビットの情報だけを抜き出せる)
2.論理積の計算後、次の処理のため 2ビット下位にシフト。
 ('00100110'を、2ビット右にシフトして'00001001'に)

この処理を、1バイト分(つまり 4回)繰り返し・・・となる。

$v1 = "\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x00"; # データ 15byte(60装置分)
$v2 = 3; # 00000011

for ($i = 0; $i < 15; $i++) {

# 1byte 抜き出す
if ($v1 !~ /^(.{1})(.*)$/) {
last; # データが無くなれば15byte未満でもループ終了
}

$v3 = $1; # 抜き出した 1byte をセット
$v1 = $2; # 残りは次の処理に使う

$w1 = hex(unpack "H*", $v3); # 10進数に変換

# 抜き出した 1byte の下位から 2bitずつ見ていく
for ($j = 0; $j < 4; $j++) {

$w2 = $w1; # 論理積計算用に退避
$w2 = $w2 & $v2; # 論理積
$w1 = $w1 >> 2; # 右(下位)に 2bit シフト

print "$i.$j $w2\n";

}

}

まあ、こんな感じで。

ポイントは、「シフトする値は、予め10進数に変換しておくこと」だな。
「整数」でないと駄目なので、16進のまま、

$v1 = "\x26";
$v2 = "\x03";

$v1>>2; # 右(下位に 2bit シフト)

$v1 = $v1 & $v2;

なんてやってもまともな結果は出ない。
('00100110'をシフトして'00001001'になってほしいけど、何故か'01001000'になっちゃうんだよな)

そこに気づくのに時間がかかってハマってしまった(^^;
ビット単位でシフトするだけなんだから 16進のままでええやん・・・と思うのだが、俺の知らない問題が色々あるんだろうね。プログラミング言語の実装には。

ちなみに、上位ビットからセットしてくれれば、Perl なら「2進文字列」に unpack して、2文字ずつ頭から抜けばいいだけなので、上の例の処理部を差し替えるなら、

$w1 = unpack("B*", $v3); # 1バイト=8桁の2進数に変換
@w = $w1 =~ /.{2}/g; # 2桁ずつに分解

for ($j = 0; $j < 4; $j++) {

$w[$j] = unpack("C", pack("B8", '000000' . $w[$j])); # 2桁の 2進数を10進変換
print "$i.$j $w[$j]\n";

}

こんな感じで、如何にも Perl っぽくて嬉しい。(いや、ちょっとでも「正規表現」が入ると Perl っぽいやん(笑)2桁ずつに分解してるところだけど(笑))
いやあ、久しぶりにインターネットソケット通信処理を書いたので、つまらんところでハマってしまった(^^;

UDP 通信サーバを Perl で書いたのだが、なぜか外のマシンから接続できない。
自サーバ内でテスト用のクライアントを走らせたらちゃんとメッセージが届くのに・・・

ええ、そうです。あなたの推測どおりでございます(^^;

IO::Socket::INET モジュール使ってるんだけど、その new() コンストラクタ内のオプションで、

my $s = IO::Socket::INET->new(

    LocalAddr => "localhost",
    LocalPort => 50001,
    ReuseAddr => 1,
    Proto     => "udp"

) or die $!;

ってしてただけの話しで(^^;
"localhost"にバインド(アドレス設定)してるんで、同一マシン内からしか接続できないのが当たり前だ。
いや、ま、実際には LocalAddr => $LOCAL_ADDR という具合に定数でセットしてたんで、それに気づくのが遅れてしまった(^^;とほほ・・・
20120213_udp_server.JPG
あと、最初にテストしていたのが、ルータでポートフォワーディングしまくりとか、色々特殊なネットワーク環境下だったので、疑うべきところが色々あって、そこまでなかなか目が向かなかったというのも言い訳の一つに揚げておこう(^^;

LocalAddr に、外部から接続可な IP アドレスを設定すれば問題解決。

さあ、これで客先にこのサーバ持ち込んでテスト出来るぞ。

テスト用サーバが EeePC 4G なんで(今、テストに使える UNIX 環境がこれしか無かったのよ(^^;)、お客さん、びっくりするかもしれんけど(^^;;;
UDP プロトコルでデータ受信するプログラムを xinetd 経由で実行しようと思ってたんだけど、どうもうまくいかんなあ。

以下の環境でテストを実施。

・OS 及び xinetd

CentOS 6.2
xinetd 2.3.14
Perl 5.10.1

・/etc/services にサービス追加

hogehoge        50001/udp

・/etc/xinetd.d/hogehoge を作成

service hogehoge
{
        disable         = no
        user            = root
        wait            = no
        server          = /usr/local/bin/hogehoge.pl
        socket_type     = dgram

}

・テスト用サーバ(/usr/local/bin/hogehoge.pl)

#!/usr/bin/perl

$d = <STDIN>;
exit();

・テスト用クライアント(./udp_client.pl)

#!/usr/bin/perl

use IO::Socket::INET;

$c = IO::Socket::INET->new(
  PeerAddr => "localhost",
  PeerPort => 50001,
  Proto    => "udp"
) or die $!;

$c->send("hello");
$c->close;

これで、クライアントを実行すると、一応 xinetd 経由で hogehoge.pl は起動するのだが、いつまで経っても終了せず。

/var/log/messages に、

Feb  8 00:08:47 server1 xinetd[8188]: 1 descriptors still set
Feb  8 00:08:47 server1 xinetd[8188]: No active service for file descriptor 0

というメッセージが延々出続ける。

file descriptor 0 ってことは、標準入力(STDIN)がアクティブになってないってこと?どういうこと?
xinetd は自分が受信したデータを標準入力でプログラムに渡すのだが、No active service for file descriptor 0 だからそれがうまく渡せないということだよね?

なんでそうなるかがわからん。

以前(もう数年前のことだが)、tcp でデータを受け取るプログラムを xinetd 経由で動かしたことあるけど、こんなエラーが出たことはないでぇ~
む~ん・・・

自前でサーバ書くと、スポーンした子プロセスがゾンビ化したり、親プロセスが原因不明で固まったりって時の処理(監視も含めてね)考えたり、そのプログラム書くのが面倒だなあと xinetd 使おうと思ったんだけど・・・む~ん・・・

「馬鹿め!○○設定をせんかい!」みたいな識者からのお叱りをお待ちしてます。

このアーカイブについて

このページには、2012年2月以降に書かれたブログ記事のうちPerlカテゴリに属しているものが含まれています。

前のアーカイブはPerl: 2011年12月です。

次のアーカイブはPerl: 2012年5月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。


月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら