後藤謙太郎 <URL:http://www.notwork.org/~gotoken/mag/cmagazine/>
註: この文書は『C MAGAZINE』2001年2月号 に掲載された記事の元となるものに手を加えたものです.
記事中のプログラムを一つずつファイルにしたものもあります(→list)
間違いを見つけたら,gotoken@notwork.org宛に御連絡くださると喜びます。
Copyright(c) 2001 by GOTO Kentaro. All rights reserved.
Ruby conference より, ソケットを使う (用語, TCPSocketクラス), Net::HTTP, RFCを読もう, サーバも書こう, サーバツールキット, 参考資料
以前ここでもお知らせしたように、昨年11月29日から12月1日にかけて京都でオライリー・ジャパンの主催のPerl/Rubyカンファレンス[PRC]が開かれました。いろいろ勉強になったり、有意義な議論ができたり、なによりたくさんの人にお会いできて楽しい3日間でした。筆者が聴くことが話から簡単に報告したいと思います。
1日目は対談(Larry Wall さん、まつもとゆきひろさん、前田薫さん)から始まりました。まつもとさんと前田さんが適宜訳しながらの臨場感あふれる(?)セッションです。予告ではPerlとRubyの比較ということになっていましたが、 Larry(あえて敬愛の念をこめてこう呼ばせてもらいます)は原石本*1 を読み始めたばかりといったところだったので詳しい比較はとてもできません。そうそう、その本にまつもとさんがサインをしてLarry がおじぎをしながら受けとるという楽しいシーンもありました。 Larry はほんとうに愉快な人です。さて壇上の3人はいずれも臨機応変なので、お題目にとらわれず言語の共通点を挙げてみたり、言語の開発の面白さと大変なところを語り合ったりしていました。それを通じていろいろな哲学を垣間見た気がします。
なるほどと思ったのは、Larryが作ったものは Metaconfig にしても patch にしても、ずいぶんとおしゃべりだ、という前田さんの指摘です。Perl はなんだか自然言語っぽいところもあるし、Larryの自然言語への思いの強さが確認できました。それに比べるとRubyはメソッド名選びに関する厳しさはあるものの、自然言語のような表現方法とは別のところをせめているようです。けれども、どちらの言語の父も表現することが、ほんとに好きなのが伝わってきます。ふたりは万能の言語は存在しないし、Perl もRubyもよく必要になる処理は簡単に書けるようになっていて、しかも実用に耐える程度の高速化がされているという点だということで合意がありました。やはり開発者どうしは言語戦争にならないのでした。まつもとさんはLarryのファンであることを強調していました。そういえば、言語開発のくだりでLarryが、言語開発をする能力も歳とともに衰えるもので私はもう下り坂に入っている、というようなことを言ったのが、とてもさびしく感じられ妙に心に残っています。
さて、お昼休みには木山真人さんの世代別GCの実装の講演がありました。GCはぼくたちをメモリ管理から解放してくれるありがたい存在です。いまのRubyではマークアンドスウィープによるコンサバティブGCという方法が採用されていますが、木山さんはさらなる効率の向上を狙って世代別GCの実装実験をなされています。しかしGCの効率はアプリケーションの性質によって左右されるので、現在のRubyよりもいつも良いわけでなく、一筋縄ではいかないようです。今後が期待される話題です。
午後はWWWサーバサイドプログラミング(前田修吾さん)、数値計算(筆者)、 Windowsへの移植(わたなべひろふみさん)の話でした。WWWサーバサイドプログラミングの概略は本連載の前回で紹介しましたので、そちらを参照して下さい。そうそう、カンファレンスのあと、前田さんによってmod_rubyの公式サイトが誕生しました[MR]。一方、数値計算についてはいずれ詳しく紹介する予定です。わたなべさんの移植に関するお話では、BOW*2 を利用するところからはじまったという移植の歴史的な側面を聴くことができました。思った以上に根性勝負な印象を受けました。Windows版Rubyは一日にしてならずというか、いろいろな苦労とその解決によるノウハウの地道な積み重ねで成り立っていることがわかりました。ありがたいことです。
2日目は、咳さんのdRuby[Sek00]から聴きはじめました(ねぼうしてしまい、よしだむさんのXMLの講演が聴けなかったのは至極残念です)。dRuby(= 分散Ruby)はネットワークでオブジェクトをやりとりする仕組みで、筆者もかねてより興味がありましたが、実は概要を理解したのはこれが初めてです。大雑把にいえば、dRubyはオブジェクトの参照を渡す方法とオブジェクトそのものを渡す2通りの方法を提供するものです。オブジェクトを渡す際にはMarshal モジュールで提供されるオブジェクトのdumpとloadが利用されています。しかしマーシャリングできないオブジェクトやオブジェクトを移動させたくない場合には参照渡しを使います。参照渡しの場合はGCされてしまう可能性があるのですが、これを回避するいくつかの方法も用意されています。ちなみにdRuby は全体が600行足らずで記述されています。
2日目の午後はRubyUnitとXP(助田雅紀さん、石井勝さん)に出ました。同じ時間に別の部屋で行なわれていた「ワンライナーの鉄人」にも興味はあったのですが、個人的にはRubyUnitを聴いて正解だったと思っています(というか、もともと助田さんをカンファレンスに呼んで下さいとお願いしたのは他ならぬ筆者なのでした)。前半は石井さんによるXPの概説です。XPと略されるeXtreme Programmingは最近なにかと話題の開発手法で、小人数で行なわれる極端な方法論です。そこで採り入れられる特徴的な方法の一つはペアプログラミングです。これは常に2人でディスプレイに向かい、1人はコーディング、もう一人はレヴューを行なうというものです。これによって意思疎通、簡潔さの追求、小刻みな変更、そして信頼関係という4つの物差しが満足されることを狙っているそうです。そして、石井さんたちは日本ではあまり例をみないという、このペアプログラミングの実践者なのです。後半の助田さんによるRubyUnitはXPで要求されるテスト、とりわけ Kent Beck さんが提案する単体テストを支援するためのライブラリです[Suk00]。単体テストとは、個々のメソッドやクラスといった部品ごとのテストのことで、製品全体のテストやユーザインターフェイスのテストを含みません。これもまた詳しく紹介したいと思います。
そしてRuby最後のセッションは3日目の「Ruby開発者と話そう」(まつもとゆきひろさん)でした。まつもとさんが、自分は多くの仕事を引き受け過ぎているので、Rubyの開発に専念できるように仕事を分担してくれる人を探しているというお願いをみんなにしたあと、今度は要望を募りました。まつもとさんは盛り上がらなかったらどうしようと心配されていたようですが、つぎつぎと質問などが殺到し、あっという間に時間が過ぎてしまいました。
さて、今回の本題は、インターネットプログラミングです。筆者がRubyに対してありがたいと思う点の一つに、いろいろな技術への入口になってくれるということがあります。いろんなところにつれていってくれる遊び人な友だちみたいな感じとでもいいましょうか、とにかくいろんな技術をこなれた形で使いやすく提供してるという側面があると思うのです。例えば筆者はむかしむかし SunのLWPを教材にして並行プロセスを学びましたが、そこにいたる準備の部分がまどろっこしく感じられて本質的な部分は観念的にしか理解していなかったという悔しい思いがあります。しかしRubyなら「哲学者の食事」*3だってわずか50 行で完全に記述できるので、ロジックから実行までをスクロールしなくてすむ範囲で見ることができ、すごくリアルに感じることができます。またそういう自分が不案内な技術に必要な部品をメソッドのような形で提供してもらえることもまたありがたいことです。そのおかげで手軽に試すことができ, 動かしながら概念を学びとるということをインタープリターならではの軽妙さで味わうことができます。
インターネットプログラミングもその一つといえるでしょう。ソケットを使うプログラミングはやはりCだと敷居が高く感じる人は少なくないと思います。ところがRubyだと毎回やらなければならない決まり切った処理を書かずに済むので本質的な部分に素早く迫ることができるのです[IPR1]。
ソケットについて全く知らないかたのために必要なことだけ説明しておきますね。インターネットにおいては、他のマシンと交信するための手順は約束ごととして定められており、この約束は「プロトコル」と呼ばれます。通常使う範囲でのもっとも基本的なプロトコルは「IP」(Internet Protocol)というものです。これは「ホスト」とも呼ばれるネット上のマシンを一意な番号、すなわち「IP アドレス」で指定することで成り立っています。インターネットでアドレスといえばこのIPアドレスのことを指します。アドレスとは別に各種の便利さを狙って「ドメイン名」と「ホスト名」というものがあります。アドレスは番号ですが、ドメイン名やホスト名は文字列です。ドメイン名からアドレスに変換するサービス(DNS)のおかげで、大抵の場合、ホストは「名前」(= ホスト名+ドメイン名)で指定することができます。
IPを前提として使いやすく整備されたプロトコルに「TCP」があり、IP で実現されるのでTCP/IPとも呼ばれます。現在、皆さんが直接触れるアプリケーションのほとんどすべてはTCP/IPといっても良いでしょう。ネットワークを利用する場合、なにかのサービスを受けることが目的になります。これらのサービスはそれぞれがTCPを前提としたプロトコルとしてまとめられています。 HTTP(Hypertext Transfer Protocol)やFTP(File Transfer Protocol)といったさまざまなプロトコルはTCP/IPを前提としたものです。このHTTP やFTPといったサービスを利用するためには、アドレスのほかにポートというものを指定することがTCP/IPのやりかたです。ポートは番号で指定されます。たとえばHTTP は80、FTPは21を使うのが標準的です。こういった標準的なポートはウェルノウンポート(well-known port)と呼ばれ、とくにUNIXでは特権ユーザでしか利用できません。例えば、自分のマシンのポート80 をHTTPサーバとして利用するためには特権が必要です。詳しくは、先月号の特集記事や、[IPR1]などから読みはじめると良いでしょう。
さてさて、TCPを使うならば、アドレスとポートの組で、他のホストを指定できます。そして交信には読み出しと書き込みの両方ができるIOを利用します。このIOが「TCPソケット」と呼ばれるものです。結局、TCPソケットは、サービスを指定するためにアドレスとポートの組み合わせで開かれます。Rubyでは TCPソケットはTCPSocketという名前のクラスとして提供されています。早速使ってみましょう。
-- List1 socketdemo.rb:
require "socket"
s = TCPSocket.new("www.notwork.org", 80)
s.write "GET /ipr/test.html HTTP/1.0\r\n\r\n"
print s.read
List1は <URL:http://www.notwork.org/ipr/test.html> という資源を取得するプログラムです。わずか5行ですがこれでもHTTPクライアントです。TCP
ソケットを開いて、そこに「GET ...」を書き込んで、読みとっているのがわかりますよね。
List1の実行結果はFig2のようになります。
-- Fig2 List1の実行結果 HTTP/1.1 200 OK Date: Fri, 15 Dec 2000 04:39:23 GMT Server: Apache/1.3.9 (Unix) mod_ruby/0.2.1 Ruby/1.6.1(2000-09-27) Last-Modified: Fri, 14 Apr 2000 06:59:36 GMT ETag: "1b39e-c5-38f6c1d8" Accept-Ranges: bytes Content-Length: 197 Connection: close Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <head> <title>test</title> <link rev=made href="mailto:gotoken@notwork.org"> </head> <body> <h1>test</h1> <p>test,test,test,...</p> </body>
空行の後の「<!DOCTYPE ...」からが指定したURLにあるHTMLファイルの中身ですがその前に10行ほど余分なものがついています。これはHTTPによる応答です。HTTPはヘッダと呼ばれる制御用のメッセージと、ボディと呼ばれるデータを一つのIOで送るので、混ざって送られてきます。この混ざりかたはHTTPで決められているので、それにしたがえばこのなかからデータ(ここでは空行以降)だけを取り出すことができます。でも、そういう面倒な作業をやってくれるライブラリはすでに標準で用意されており、Net::HTTP というものがそれです。
そこでList1を早速作り直してみましょう。しかし、ただ作り直すだけでは強力なNet::HTTPが泣くので、HTTPのリダイレクションとプロクシに対応しました。List3がそれです。リダイレクションというのは、たとえばRubyのホームページ <URL:http://www.ruby-lang.org/> で利用されているもので、引越ししたとか、ブラウザの設定で日本語か英語を切替えるなどの理由で、指定されたURLとは別のURLに飛ばすものです。
このプログラムは、引数に HTTP URL を渡すと標準出力にその結果を吐きます。作りとしてはHTTPURLクラスを作りそのメソッドとしてgetを用意しています(Python 風?)。番号をつけた箇所を説明します。
Net::HTTPは net/http で定義されています。
プロクシは環境変数 http_proxy が設定されていればそれをHTTPURL::PROXYにセットして用います。
@myheaderの `Accept-Language' と `Host' はそれぞれ言語と、サーバのホスト名をあらわします。`Accept-Language'が利用されるかどうかはサーバ次第です。
Net::HTTPオブジェクトを作っています。省略可能な第2,3,4引数でサーバのポートとプロクシを指定しています。job というのはデバッグ用のメッセージを出すために下で定義しています。
HTTPオブジェクトhのメソッドgetを使ってGETしています。その際、省略可能な第2引数に @myheader を渡しています。
HTTP#get はリダイレクトされているときに例外を発生します。
Ruby 1.6から加わったrescueの構文を使って例外オブジェクトをr
に代入しています。
例外 r から、リダイレクト先を得ています。
新しく作ったURLである loc を使って呼び出し直します。
jobの定義です。$DEBUGが設定されているときにメッセージを出力しています。ブロックの評価結果を値として返します。
このプログラムのトップレベルで、HTTPURL#get を呼んでいます。このメソッドの値は HTTP#net の返す値なので、ヘッダとボディの組になっています。ヘッダは$DEBUGが設定されていない場合、使わずに捨てます。
-- List3 htget.rb: URLで指定された資源をSTDOUTへ
#!/usr/bin/env ruby
# htget.rb - get url and print to stdout
# usage: htget.rb url
require "net/http" #1
class HTTPURL
PROXYPAT = %r"http://([^:/]+)(?::(\d+))?"
URLPAT = %r"http://([^:/]+)(?::(\d+))?(/.*)?"
PROXY = if PROXYPAT =~ #2
ENV['http_proxy'].to_s
[$1,($2.to_i)] # [host, port]
else
[nil, nil]
end
def initialize(url)
if URLPAT =~ url
@host = $1
@port = ($2 || 80).to_i
@port_given = $2 && true
@path = $3 || "/"
else
raise ArgumentError, "invalid url `#{url}'"
end
@myheader = { #3
'Accept-Language' => 'ja, en',
'Host' => @host
}
end
attr_reader :host, :port, :path
def get
h = job("connecting: #{host}"){ #4
Net::HTTP.new(@host, @port, *PROXY)
}
begin
job("getting: #{self}"){
h.get(@path, @myheader) #5
}
rescue Net::ProtoRetriableError => r #6
if loc = (r.data['Location'] || #7
r.data['location'])
if PROXY[0]
loc.sub!(ENV['http_proxy'],
"http://#{@host}:#{@port}")
end
job("forwarded: #{loc}"){
HTTPURL.new(loc).get #8
}
end
rescue Net::ProtoFatalError
STDERR.print $!, "\n"
end
end
def to_s
if @port_given
"http:#{@host}:#{@port}#{@path}"
else
"http:#{@host}#{@path}"
end
end
private
def job(*msg) #9
print msg, ">>>start\n" if $DEBUG
res = yield if block_given?
print msg, "<<<done\n" if $DEBUG
res
end
end
if url = ARGV.shift
header, body = HTTPURL.new(url).get #10
if $DEBUG
print ">>>>>>HEADER\n"
header.each{|k,v| print "#{k}: #{v}\n"}
print "<<<<<<HEADER\n"
end
print body
end
プロクシ経由でRubyのホームページにアクセスした結果をFig.2に示します。 -dオプションで $DEBUG を設定してるのでリダイレクションにしたがう様子もわかります。
-- Fig4 List3の実行例
$ env http_proxy=http://wwwcache:3128/ \
ruby -d htget.rb 'http://www.ruby-lang.org/'
connecting: www.ruby-lang.org>>>start
connecting: www.ruby-lang.org<<<done
getting: http:www.ruby-lang.org/>>>start
forwarded: http://www.ruby-lang.org/ja/index.html>>>start
connecting: www.ruby-lang.org>>>start
connecting: www.ruby-lang.org<<<done
getting: http:www.ruby-lang.org/ja/index.html>>>start
getting: http:www.ruby-lang.org/ja/index.html<<<done
forwarded: http://www.ruby-lang.org/ja/index.html<<<done
>>>>>>HEADER
proxy-connection: close
content-type: text/html; charset=iso-2022-jp
last-modified: Mon, 20 Nov 2000 09:44:01 GMT
date: Fri, 24 Nov 2000 07:26:13 GMT
age: 219
accept-ranges: bytes
etag: "253d3-2310-3a18f261"
x-cache: HIT from wwwcache.math.sci.hokudai.ac.jp
content-length: 8976
server: Apache/1.3.9 (Unix) Debian/GNU mod_ruby/0.2.1 ...(省略)
<<<<<<HEADER
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
(以下略)
ネットワークプログラミングは、ソケットの使い方のようなライブラリ側の話とプロトコルに関する2つの技術から成り立っています。ライブラリの使い方はサンプルとドキュメントの両方を見ていくことで理解できますが、プロトコルの方は残念ながらライブラリのドキュメントだけではわかりにくい部分も少なくありません。インターネットで用いられているプロトコルはRFCという形でまとめられており、FTPなどで誰でも入手し読むことができます。
そこでFTPでRFCを取得するプログラムを書いてみましょう。List5は筆者が実際に使っているRFCを取得するためのプログラムです。以前別のところで発表したもの[IPR1]に手を加え、すでに入手しているRFCはそのまま使うようにしてます。ただし、rfc-indexは最後に入手して1カ月以上経っていれば取得し直します。コマンドラインから
$ rfc 2616
とすれば、もし持っていれば RFC2616 をページャを使って表示し、持っていなければ、FTPで取ってきてからページャを起動します。
-- List5 rfc.rb: RFCを表示する。必要に応じてGETする。
#!/usr/local/bin/ruby
require "net/ftp"
require "getopts"
include Net
DEFAULT_URL = ENV['RFC_BASE_URL'] || "ftp://ftp.nic.ad.jp/rfc/"
LOCAL_PATH = ENV['HOME'] + "/lib/doc/rfc/"
PAGER = ENV['PAGER'] || "/usr/local/bin/less -i"
def print_progress nrecv, size
if size != 0
ratio = nrecv * 100 / size
print "\r#{nrecv}/#{size}(#{ratio}%) bytes received. "
STDOUT.flush
end
end
# analyze option
getopts("ipgf", "u:", "o:", "P:")
ARGV.push("-index") if $OPT_i
@use_passive = $OPT_p
@ftp_url = $OPT_u || DEFAULT_URL
@local_path = $OPT_o || LOCAL_PATH
@pager = $OPT_P || PAGER
@force_get = $OPT_f
if ARGV.size == 0
puts "Usage: rfc [-i] [-p] [-u URL] [-o dir] number [...]\n"
puts "-i\tgets rfc-index.txt"
puts "-p\tpassive mode"
puts "-u URL\tbase URL (default: #{DEFAULT_URL})"
puts "-o\toutput directory (default: #{LOCAL_PATH})"
puts "-g\tget only; don't invoke pager"
puts "-f\tforce get any if the rfc exists on local"
puts "-P more\tpager program (default: #{PAGER})"
exit 1
end
def local_file(no)
"#{@local_path}rfc#{no}.txt"
end
def get_rfc(*rfc)
# Parse URL
if %r!ftp://(.*?)(/.*)! =~ @ftp_url
host, remote_path = $1, $2
end
if !host || !remote_path
print "Bad URL is specified.\n"
exit 1
end
# Connect and login
print "connect to #{host}\n"
s = FTP.new(host)
s.passive = @use_passive
print "using passive mode.\n" if s.passive
print s.login
# Real get
rfc.each do |i|
remote_file = "#{remote_path}rfc#{i}.txt"
local_file = local_file(i)
print "Getting #{remote_file} into #{local_file}.\n"
begin
size = s.size(remote_file)
s.gettextfile(remote_file, local_file) do
print_progress File.stat(local_file).size, size.to_i
end
print_progress File.stat(local_file).size, size.to_i
print "Done.\n"
rescue
print "#{$!}\n"
end
end
# Logout and close connection
s.close
end
toget = ARGV
unless @force_get
toget = ARGV.find_all{ |rfc|
! File::exist? local_file(rfc)
}
end
if ARGV.grep("-index") &&
File::exist?(local_file("-index")) &&
File::ctime(local_file("-index")) - Time.now > 3600*24*30
toget &= ["-index"]
end
get_rfc(*toget) unless toget.empty?
system "%s %s" %
[PAGER, ARGV.collect{|i| local_file(i)}.join(" ")]
さて、お次はサーバを書いてみたいと思います。Rubyならサーバを書くのも実に楽ちんです。まずは、サーバの概略を見るために、送られたデータの各行のバイト数を返すようなサーバを書いてみます(List6)。このプログラムを使うには、TTYを二つ用意し、一方でこのプログラムを実行し、もう一方で「telnet localhost 7123」を実行します。サーバを止めるには「こんとろーるC」を押して下さい。telnetを終了するには、「Q」「えんたー」と打って下さい。
-- List6 lengthsrv.rb:
require 'socket'
port = 7123 #1
gs = TCPServer.open(port) #2
while true #3
ns = gs.accept #4
p ns.peeraddr
Thread.start(ns) do |s| #5
while l = s.gets
break if /^q/i =~ l
s.write "S: (%d)%s\n" %
[l.size, l.inspect]
end
s.close
end
end
ポート7123を使うことにします。
TCPServerは、TCP接続を待つためのクラスです。
無限ループになっています。#4で接続したらすぐに次の接続をまつためです。つまりこのプログラムは同時に複数のクライアントの相手ができます。
TCPServer#accept は接続を受け付けて、新しいTCPソケットを返します。
新しく得たソケットを、スレッドに渡しています。
Thread#start の引数はブロック引数に代入されます。これはスレッドの外と内で変数を共有しないために用意されている機能です。こうしておけばnsという変数をスレッドの中に持ち込まなくても済むのでスレッド特有のきわどい状況が発生しません。
このようにRubyにはサーバを書くための仕組みが標準で揃っています。もし、
Cのソケットプログラミングしか知らない方がこれを見たらきっと驚かれるでしょう :-)
Rubyにはサーバに必要なものがあるとはいえ、現実的なサーバの実装はサポートする範囲にもよりますが、それなりに大変です。でも、各プロトコルは決められているので、サーバの部品を用意しておくことは可能でしょう。そこで、筆者らはHTTPサーバを書くためにWEBrickというHTTPサーバツールキットの開発をはじめました[RAA:WEBrick]。執筆時点のバージョンは0.0.4です。
たとえば、最低限の機能を持ったHTTPサーバはList7のように書けます。
-- List7 httpd.rb: HTTPサーバ(WEBrickのサンプルより)
#!/usr/bin/env ruby
require 'webrick/webrick'
require 'getopts'
include WEBrick
getopts nil, "c:"
config_file = $OPT_c
WEBrick::Config.load(config_file)
trap('INT') { exit }
trap('QUIT'){ WEBrick.stop_server }
trap('HUP') { 'signal HUP received.'}
trap('PIPE'){}
STDERR.print "port=%d,
pid =%d\n" % [ Config::Port, $$ ]
WEBrick.start_server(Config::Port){|sock|
HTTPServer.start(sock)
}
必要な部品をいろいろと用意してあるので、目的に応じ特化されたHTTPサーバを作るのが容易になります。このようなサーバツールキットが他にもたくさんあれば世の中楽しくそうですよね。挑戦してみませんか?
後藤謙太郎, 後藤裕蔵, 高橋征義, 渡辺哲也, 『Rubyではじめるインターネットプログラミング』, 第1回, オープンデザイン, No.38, CQ出版 (2000), <URL:http://www.notwork.org/ipr/>
前田修吾, mod_ruby.net, <URL:http://www.modruby.net>
Perl/Rubyカンファレンス2000, 2000年11月29日-12月1日, 国立京都国際会舘, <URL:http://perlruby-con.opensource.gr.jp/>
<URL:http://www.ruby-lang.org/en/raa-list.rhtml?name=WEBrick>
咳, 『dRubyによる分散オブジェクト環境』, <URL:http://www2a.biglobe.ne.jp/~seki/prc2k/s00.html>
助田雅紀, ``RubyUnit'', <URL:http://homepage1.nifty.com/markey/ruby/rubyunit/rubyunit_j.html>
*1 Dave と
Andyの``Programming Ruby[TH01]''のこと。筆者は勝手にこう呼ぼうという運動を個人的に展開中
*2 BSD on Windows
*3 チュート
リアル[RT]に書かれています