TCPサーバー・クライアントを作成する
動作確認用にTCPサーバーとクライアントを作ったので、まとめてみます。
参考にした本
サーバー
クライアントから受け取ったメッセージを返信するだけのサーバーです。
require 'socket' port = if ARGV[0] then ARGV[0] else "echo" end gate = TCPServer.open(port) sock = gate.accept gate.close while msg = sock.gets sock.write(msg) end sock.close
クライアント
コンソールからの入力をサーバーに送るだけのクライアントです。
require 'socket' host = if ARGV[0] then ARGV[0] else "localhost" end port = if ARGV[1] then ARGV[1] else "echo" end sock = TCPSocket.open(host, port) while msg = STDIN.gets sock.write(msg) print sock.gets end sock.close
UDPサーバー・クライアントを作成する
これからTCPサーバーとクライアントを作るので、その前に以前作ったUDPサーバー・クライアントをまとめます。
参考にした本
サーバー
クライアントから受け取ったメッセージをプリントするだけのサーバーです。
require "socket" port = if ARGV[0] then ARGV[0] else 10000 end sock = UDPSocket.open sock.bind("", port) loop do msg, (afam, port, host, ip) = sock.recvfrom(1024) print "AFAM=" + afam + " PORT=" + port.to_s + " IP=" + ip print "MSG=" + msg end
クライアント
コンソールからの入力をサーバーに送るだけのクライアントです。
require "socket" host = if ARGV[0] then ARGV[0] else "localhost" end port = if ARGV[1] then ARGV[1] else 10000 end sock = UDPSocket.open while msg = STDIN.gets begin sock.send(msg, 0, host, port) rescue puts "Send Error." end end sock.close
Armadilloでシリアル通信
Armadilloでシリアル通信の動作確認をするところまでをまとめてみます。
環境
参考にしたページ
1.シリアルインターフェースの解放
出荷時のArmadilloのシリアルインターフェース(CON3)は、シリアルコンソールとして使用するよう設定されています。
まずは、これを解放します。
まず、ATDEでromfs以下にあるetc/inittabを編集してイメージファイルを更新します。
atmark@atde3:~/source/20100603/atmark-dist$ vi romfs/etc/inittab ::sysinit:/etc/init.d/rc ::respawn:/sbin/getty -L 115200 ttymxc1 vt102 ::shutdown:/etc/init.d/reboot ::ctrlaltdel:/sbin/reboot
"::respawn:/sbin/getty -L 115200 ttymxc1 vt102"をコメントアウト
atmark@atde3:~/source/20100603/atmark-dist$ vi romfs/etc/inittab ::sysinit:/etc/init.d/rc #::respawn:/sbin/getty -L 115200 ttymxc1 vt102 ::shutdown:/etc/init.d/reboot ::ctrlaltdel:/sbin/reboot
保存したら、make imageを実行します。
atmark@atde3:~/source/20100603/atmark-dist$ make image
できあがったイメージファイルをArmadilloに転送します。
開発中はtftpブートが簡単なのでオススメです。
atmark@atde3:~/source/20100603/atmark-dist$ sudo cp images/*gz /var/lib/tftpboot/
Armadilloを再起動すると、シリアルコンソールにはログイン画面が出てこなくなります。
2.シリアル通信のテスト準備
シリアルポート(外側)とコンソール(内側)の間で文字をやりとりしてみます。
telnetでArmadilloにログインします。
atmark@atde3:~/source/20100603/atmark-dist$ telnet 192.168.1.119 Trying 192.168.1.119... Connected to 192.168.1.119. Escape character is '^]'. atmark-dist v1.26.1 (AtmarkTechno/Armadillo-420) Linux 2.6.26-at9 [armv5tejl arch] armadillo420-0 login: guest [guest@armadillo420-0 (ttyp0) ~]$
rootに変更します。
[guest@armadillo420-0 (ttyp0) ~]$ su - Password: [root@armadillo420-0 (ttyp0) ~]#
通信設定を確認します。
CON3には/dev/ttymxc1が割り当てられています。
[root@armadillo420-0 (ttyp0) ~]# stty -F /dev/ttymxc1 speed 115200 baud; -brkint ixoff -imaxbel
3.シリアルポートから入力された文字を受信してみる
[root@armadillo420-0 (ttyp0) ~]# cat /dev/ttymxc1
これで、シリアルポートからの入力待機中になります。
teraterm側に文字を入力します。
maido ookini
telnet側には以下のように表示されます。
[root@armadillo420-0 (ttyp0) ~]# cat /dev/ttymxc1 maido ookini
4.シリアルポートに文字を送信してみる
[root@armadillo420-0 (ttyp0) ~]# echo "moukari macca?" > /dev/ttymxc1
moukari macca?
ポイント
- inittabを編集してシリアルコンソールを解放する
- 初期状態では/dev/ttymxc1へのアクセス権がないのでrootにsuする
- シリアルポートから受信するには"cat /dev/ttymxc1"
- シリアルポートへ送信するには"echo "hogehoge" > /dev/ttymxc1"
ArmadilloでRubyを動かす
組み込みLinuxで開発を行う事になったんだけど、Cってやった事がないんです。
なので、最近メインで使っているRubyをまずは組み込んでみます。
参考にしたページ
Rubyの含まれるパッケージ名を調べる
atmark@atde3:~$ dpkg -S /usr/bin/ruby1.8 ruby1.8: /usr/bin/ruby1.8
Debianのサイトでパッケージを取得する
http://www.jp.debian.org/distrib/packagesの"パッケージディレクトリを検索"で"ruby1.8"を検索します。
するとDebian -- Errorで取得できる事が分かります。
Armadilloのアーキテクチャはarmelなので、Debian -- Errorをダウンロードします。
atmark@atde3:~$ mkdir ruby atmark@atde3:~$ cd ruby atmark@atde3:~/ruby$ wget http://ftp.jp.debian.org/debian/pool/main/r/ruby1.8/ruby1.8_1.8.7.72-3lenny1_armel.deb --2010-07-12 16:30:40-- http://ftp.jp.debian.org/debian/pool/main/r/ruby1.8/ruby1.8_1.8.7.72-3lenny1_armel.deb ftp.jp.debian.org をDNSに問いあわせています... 150.65.7.130, 202.229.186.27, 203.141.150.38, ... ftp.jp.debian.org|150.65.7.130|:80 に接続しています... 接続しました。 HTTP による接続要求を送信しました、応答を待っています... 200 OK 長さ: 283162 (277K) [application/x-debian-package] `ruby1.8_1.8.7.72-3lenny1_armel.deb' に保存中 100%[======================================>] 283,162 --.-K/s 時間 0.1s 2010-07-12 16:30:41 (1.88 MB/s) - `ruby1.8_1.8.7.72-3lenny1_armel.deb' へ保存完了 [283162/283162]
ruby1.8の依存しているパッケージも取得する
Debian -- Errorにはruby1.8が依存しているパッケージも記載されているので、libc6、libruby1.8もダウンロードしておきます。
atmark@atde3:~/ruby$ wget http://ftp.br.debian.org/debian/pool/main/g/glibc/libc6_2.7-18lenny4_armel.deb atmark@atde3:~/ruby$ wget http://ftp.jp.debian.org/debian/pool/main/r/ruby1.8/libruby1.8_1.8.7.72-3lenny1_armel.deb
以下のコマンドで依存しているライブラリを調べる事も出来ます。
atmark@atde3:~/ruby$ ldd /usr/bin/ruby1.8 linux-gate.so.1 => (0xb7f72000) libruby1.8.so.1.8 => /usr/lib/libruby1.8.so.1.8 (0xb7e83000) libpthread.so.0 => /lib/i686/cmov/libpthread.so.0 (0xb7e6a000) libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7e65000) libcrypt.so.1 => /lib/i686/cmov/libcrypt.so.1 (0xb7e33000) libm.so.6 => /lib/i686/cmov/libm.so.6 (0xb7e0d000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7cb2000) /lib/ld-linux.so.2 (0xb7f73000)
取得したパッケージを展開する
パッケージからバイナリを取り出します。
atmark@atde3:~/ruby$ dpkg -x ruby1.8_1.8.7.72-3lenny1_armel.deb temp-ruby atmark@atde3:~/ruby$ dpkg -x libc6_2.7-18lenny4_armel.deb temp-libc atmark@atde3:~/ruby$ dpkg -x libruby1.8_1.8.7.72-3lenny1_armel.deb temp-libruby
全部同じディレクトリに展開しても良いんですが、どれがどれなのか把握しておいた方が良さそうです。
展開されたバイナリをatmark-distのromfs以下へコピーする
lsすれば分かるとおり、romfs以下にはユーザーランドが格納されています。
該当するディレクトリに展開されたバイナリをコピーして、必要に応じてシンボリックリンクを作成します。
atmark@atde3:~/ruby$ ls ../source/20100603/atmark-dist/romfs/ bin dev etc home lib linuxrc mnt proc root sbin sys tmp usr var a
ln -s ruby1.8 ruby
make imageの実行
追加したバイナリを含んだイメージファイルを作成します。
atmark@atde3:~/source/20100603/atmark-dist$ make image
イメージファイルをArmadilloに転送する
imagesディレクトリ作成されたイメージファイルのうち、圧縮された方(gzの方)をArmadilloに転送します。
atmark@atde3:~/source/20100603/atmark-dist$ ll images/ 合計 31832 -rwxr-xr-x 1 atmark atmark 3610340 2010-07-12 17:00 linux.bin -rw-r--r-- 1 atmark atmark 1762122 2010-07-12 17:00 linux.bin.gz -rw-r--r-- 1 atmark atmark 19852325 2010-07-12 17:00 romfs.img -rw-r--r-- 1 atmark atmark 7316516 2010-07-12 17:00 romfs.img.gz
ポイント
- 取得したバイナリ全てを含めたイメージファイルを作ると、メモリ容量をオーバーする
Armadilloのメモリ容量が小さいため、必要なバイナリ、ライブラリだけに絞ってイメージファイルを作成しないと入りきらないみたいです。
組み込みの面倒なところですね。
自分の場合は、まず必須のruby1.8を転送したところ、libruby1.8.so.1.8が無いというエラーが出たのでlibruby1.8をイメージに含めました。
librubyの中にも必須ではないライブラリがたくさんあるので、必要に応じて削除しなくちゃならないと思います。
libcはエラーが出たら転送したらいいかなと考えています。
dblinkで他のデータベースのテーブルを更新する
PostgreSQLでは、トリガを使うと、テーブルが更新されたら、別のテーブルを自動的に更新する事が出来ます。
では、データベースが異なる場合はどうするか?
トリガの中でdblinkを実行する事で対応可能です。
環境
- PostgreSQL 8.3
- CentOS 5.4
参考にしたページ
インストール
インストール後、データベースへの登録SQLを実行する事で利用可能になります。
sudo yum install postgresql-contrib
postgresにsuしてから登録用SQLを実行します。
$ su - # su postgres bash-3.2$ psql pro9_production -f /usr/share/pgsql/contrib/dblink.sql
dblinkの実行方法
基本は
- コネクションを開いて(dblink_connect | dblink_connect_u)
- SQLを実行して(dblink | dblink_exec)
- コネクションを閉じる(dblink_disconnect)
です。
dblink関数に引き渡すSQLは文字列として渡す必要がある所が、この関数の面倒なところです。
何が面倒かって、シングルコーテーション(')を適切にエスケープしなきゃならない所です。
トリガーでdblinkを実行する例
CREATE OR REPLACE FUNCTION トリガー関数名() RETURNS trigger AS $BODY$DECLARE remote_query text; local_query text; BEGIN /* create connection */ perform dblink_connect('dbname=<データベース名> user=<ユーザー名> password=<パスワード>'); /* create query text */ IF (TG_OP = 'UPDATE') THEN remote_query := 'UPDATE daily_machine_datas SET (' || 'company_id,center_id,model_id,machine_id,rec_date,rec_time,' || 'machine_no,created_at,updated_at) = (' || NEW.company_id || ',' || NEW.center_id || ',' || NEW.model_id || ',' || NEW.machine_id || ',' || '''''' || NEW.rec_date || ''''',' || '''''' || NEW.rec_time || ''''',' || NEW.machine_no || ',' || '''''' || NEW.created_at || ''''',' || '''''' || NEW.updated_at || ''''')' || ' WHERE id = ' || NEW.id; ELSIF (TG_OP = 'INSERT') THEN remote_query := 'INSERT INTO daily_machine_datas(' || 'id,company_id,center_id,model_id,machine_id,rec_date,rec_time,' || 'machine_no,created_at,updated_at) VALUES (' || NEW.id || ',' || NEW.company_id || ',' || NEW.center_id || ',' || NEW.model_id || ',' || NEW.machine_id || ',' || '''''' || NEW.rec_date || ''''',' || '''''' || NEW.rec_time || ''''',' || NEW.machine_no || ',' || '''''' || NEW.created_at || ''''',' || '''''' || NEW.updated_at || ''''')'; END IF; local_query := 'select dblink_exec(''' || remote_query || ''')'; /* execute query */ EXECUTE local_query; /* delete connection */ perform dblink_disconnect(); RETURN NULL; END;$BODY$ LANGUAGE 'plpgsql' VOLATILE COST 100;
ポイント
日付、時刻はシングルコーテーションで囲む必要があり、エスケープする必要がある
dblink関数に渡すSQL文は文字列なので、シングルコーテーションはエスケープする必要があります。
日付、時刻はシングルコーテーションで囲む必要があるため、'が6回も連続する事になります。
リモートのテーブルをSELECTした結果を、ローカルに引っ張ってくる。逆は出来ない
取ってくる事は出来ても、送り出す事が出来ません。
どうしてもリモートにデータを送りたい場合は、回りくどいですが、リモート側にdblinkを導入して、
ローカル側のテーブルを読み取る関数を用意し、それをローカルからdblinkで実行すればOKです。
エラーへの対処
実行例は、開発マシンでは動くのに、実行マシンでは動きませんでした。
ログを見てみると認証エラーが発生していました。
ERROR: password is required DETAIL: Non-superuser cannot connect if the server does not request a password. HINT: Target server's authentication method must be changed.
pg_hba.confでローカル接続をtrustにしているせいで、パスワード無しで接続しようとしてはねられているようです。
今回は、稼働中のシステムの認証方法を変更するのは気乗りがしなかったので、dblink_connectを使わず、dblink_connect_uを使う事で、対処しました。
dblink_connect_uは、初期値ではpotgres以外は実行できないようになっているので、potgresでログインし、実行権限をユーザーに付与する必要があります。
GRANT EXECUTE ON FUNCTION dblink_connect_u(text) TO <ユーザー名>;
コードは以下のように書き直します。
/* create connection */ perform dblink_connect_u('dbname=<接続先データベース名>');
以上で、無事に動くようになりました。
CSVファイルのインポート、エクスポート
PostgreSQLへのテストデータのロードにはcopyコマンドが使えます。
環境
- PostgreSQL 8.4
全カラム出力
copy books to E'c:\\csv\\books.csv' csv header ;
指定カラムのみ出力
copy books(name,price) to E'c:\\csv\\books2.csv' csv header ;
指定カラムのみ入力
copy books(name,price) from E'c:\\csv\\books4.csv' csv header ;
複数レコードの一括更新
複数レコードの一括更新を実装しました。
最初はインプレース編集で実装したんですが、一件ごとの更新に意外と時間がかかるので、一括更新で再実装しました。
環境
- Rails 2.3.5
- Ruby 1.8.7
View
ポイントは"select"の引数"machine_mode[]"です。
<td><%= select "machine_mode[]", "mode_no", [1, 2, 3, 4, 5, 6] %></td>
このようにすると、パラメータは、
"machine_mode"=>{"37"=>{"mode_no"=>"2", "reset_flag"=>"false"}, "38"=>{"mode_no"=>"1", "reset_flag"=>"false"}}
と言う、ハッシュになります。
上記の通り、自動的にidがハッシュのキーになります。
<% form_remote_tag :url => {:controller => "machine_modes", :action => "update" } do -%> <p><%= submit_tag t('button.Update') %><%= hidden_field_tag "model_id", @model_id %></p> <table class="table"> <tr> <th><%= t('activerecord.attributes.machine.machine_no') %></th> <th><%= t('activerecord.attributes.machine_mode.mode_no') %></th> <th><%= t('activerecord.attributes.machine_mode.reset_flag') %></th> <th><%= t('activerecord.attributes.machine_mode.elapsed_days') %></th> </tr> <% for @machine_mode in @machine_modes %> <tr> <td><%= @machine_mode.machine.machine_no %></td> <td><%= select "machine_mode[]", "mode_no", [1, 2, 3, 4, 5, 6] %></td> <td><%= select "machine_mode[]", "reset_flag", [["はい", true],["いいえ",false]] %></td> <td><%= @machine_mode.elapsed_days %></td> </tr> <% end %> </table> <% end -%>
Controller
updateメソッドは、最初の引数にidの配列を、2番目の引数に属性の配列を指定すると、指定したidの数だけ更新処理を行ってくれます。
ids = params[:machine_mode].keys attributes = params[:machine_mode].values MachineMode.update(ids, attributes)