|
Tcl/Tkには、インターネットで使われる通信プロトコルTCP/IPで通信する機構の最も標準的な実装であるBSDソケット(socket)
を使ってコンピュータ間で通信をするコマンドが提供されています。
C言語の元来のソケットはUNIX、Windows PC、Macintosh
それぞれで仕様が微妙に違うので注意が必要なのに対し、
Tcl/Tkではそれらの相違をうまく吸収しているので、
どのプラットフォームでも同じ書き方で同じ結果を得ることができます。
じゃあ、そもそもソケットでどうやって通信がされとるんじゃ?
というところからの話になるとこのページだけではとても紹介しきれないので、
ソケットってナニ?というところは専門の書籍をあたってください。
ただ、C言語の場合のように「ねっとわーくばいとおーだー」の話とか、
「bind」とか「listen」など個々の手順の動作を詳しく知る必要はないので、
簡単な予備知識で十分です。
●サーバーソケットを作る
Tcl/Tkスクリプトは、
他のマシン上のアプリケーションから接続を受けるサーバーソケットを作ることも、
他のマシン上のサーバーアプリケーションに接続してサービスを受けるクライアントソケットを作ることもでき、
ありていに言って、TCPソケットでできることはだいたいできます。
まず、サーバーソケットを作ってみます。
下のサンプルは異常にシンプルですが、
他のマシンからTCPのポート5544で接続を受け、1行のテキストメッセージを受け取り、
その内容をTcl言語で「expr」した結果を返すという、
立派なサービス(?)を行うサーバーアプリケーションです。
(ただし、セキュリティホールにならないよう注意が必要です)
proc accepted {handle cliehost clieport} {
puts "accepted connection from: <$cliehost:$clieport>"
set bun [gets $handle]
flush $handle
if {[catch {set result [expr $bun]} err]} {
puts $handle "ERROR $err\n"
} else {
puts $handle "OK $result\n"
}
close $handle
}
set serversock [socket -server accepted 5544]
vwait zzz
サーバーソケットを作るには、socket -server
コマンドを使います。その次の引数、ここでいうacceptedは、
クライアント機から接続される度に呼ばれるプロシージャの名前
(このページではソケットハンドラと呼ぶことにします)、
最後の引数(5544)は接続を待つ
(「listen」する)「ポート番号」です。
サーバーソケットが接続を待つのは、
イベント待ちの状態でなければいけません。
従って、socket -serverでサーバーソケットを作ったら、
wishのイベント待ちループか、
vwait、tkwaitなどのコマンドでむりやりイベント待ちにする必要があります。
クライアントが接続してきたら、
定義しておいたソケットハンドラが呼び出されます。
ソケットハンドラは上のhandle、cliehost、clieportのような3つの引数をとり、
後ろの2つで世界の中のどのマシンがどのポート番号で接続してきたかがわかります。
一番重要なのは最初の引数handleで、
これをreadやgetsで読めばクライアントが書きこんだ文字列を読むことができ、
逆にこのhandleのputsすればクライアントにメッセージを返すことができます。
このhandleはいわゆる双方向ハンドルなので、
クライアントとサーバーが互いに相手の応答を待ってじっとしてしまう
「デッドロック」にならないよう、どちらかが忘れずにflushする必要があります。
では実際にクライアント機から接続して試してみます。
クライアント機がUNIXでもWindows PCでも、シェルやDOSプロンプトから
「telnet localhost 5544」
と打ってtelnetの画面を出し、「11*20」などの数式を打って
11*20
OK 220
と応答が返ってくればOKです。
●クライアントソケットを作る
今度は、このわずか数十行しかないサーバーアプリケーションにクライアントとして接続するTclスクリプトを書いてみます。
set sock [socket localhost 5544]
puts $sock 3*5
flush $sock
puts [gets $sock]
close $sock
今度は単純にsocketコマンドを使うだけです。
クライアントとして使うときのsocketの引数は、
接続したいサーバーアプリケーションが動いているマシンのホスト名
(またはIPアドレス)とポート番号です。
上の例のように、クライアントがメッセージを何か書きこみ、
これをサーバーが読んで内容に応じて応答を返す、というのは、
SMTPやHTTPなどのTCP/IPベースの有名なプレゼンテーションプロトコルも基本は全部同じです。
そういえば、HTTPで通信するようなアプリケーションをTcl/Tkで書く場合には、
httpという組み込みのパッケージを使うと、
socketでべたに書くよりは楽にそのようなアプリケーションが作れるみたいです。
●ファイルイベントの話
上のサーバーソケットのところで少し出てきましたが、
クライアントとサーバーが互いに相手の応答を待ってじっとしてしまう
「デッドロック」にならないよう、どちらかが忘れずにflushする必要があります。
このようなことにならないように、
fileeventというコマンドを使えば、
「ソケットやファイルIDが読み書き可能になったらこの処理を実行してチョ」
というのを登録しておくことができます。
fileeventコマンドは、ソケットからの相手のデータ送信を待ちつつ、
画面表示など他の処理も同時に行う場合に特に威力を発揮します。
proc accepted {handle cliehost clieport} {
puts "accepted connection from: <$cliehost:$clieport>"
fileevent $handle readable "react $handle"
puts "waiting..."
}
proc react handle {
puts "been reactable!"
set bun [gets $handle]
flush $handle
if {[catch {set result [expr $bun]} err]} {
puts $handle "ERROR $err\n"
} else {
puts $handle "OK $result\n"
}
close $handle
}
socket -server accepted 5544
vwait zzz
# end.
「fileevent ソケットID readable 処理」
「fileevent ソケットID writable 処理」
が基本的な使い方で、「ソケットID」が「読み書き可能」
になったら「処理」を実行するように、という、そのままやん。
上の例を実行するとわかると思いますが、
readableになるタイミングは、相手がそのソケットIDにputsで書き込んで、
flushした直後になります。
これでflushするのを忘れてデッドロックになる心配はバイバイブルー
(本当かなあ)これで今日から安心して眠れます。
(first uploaded 1998/05/16 last updated 2005/06/18, MISUMI URANO)
|