[Title will be auto-generated]

Page 1

G1.

情報: 第 1 郚



第 1 日 環境蚭定, C プログラミングの埩習 1. は じ め に G1〜G3 の課題は各 4 回, 合蚈 12 回分で䞀続きの内容ずなっおいる. • UNIX 䞀般, Linux (挔習で甚いる環境) の基瀎 • プログラミング䞀般 (デバッグ方法等), C プログラミングの基瀎 から始たる. 䞭栞郚分では,

• ファむル入出力, 音の入出力, • 音のを察象ずしたデヌタ凊理, デゞタル信号凊理 • むンタヌネット, ネットワヌクプログラミング を孊ぶ. 最埌には面癜い応甚プログラムずしおむンタヌネット電話 (1 察 1 の通話), あるいは電話䌚議システム (倚察倚 の通話) を完成させる事を目指す. いきなりむンタヌネット電話を䜜るずいわれるず, 䞀芋難しそうに思えるかもしれない. しかし, 芁は音を録音する, 再 生する, 音デヌタをネットワヌクで転送する, の 3 ぀ができれば良いのである. 前者に぀いおは, 挔習甚の Linux 環境で は特定のファむル (/dev/dsp) を普通のファむルのように読み曞きするだけで, 思いのほか簡単にできる事を知るだろう. だから, ファむル入出力のプログラミングができおいれば即, 音の録音や再生ができる. 埌者は, デヌタが音声であろう ずなんであろうず, ネットワヌクを介しおデヌタを送るずいう, ネットワヌクプログラミングの基本を孊べばできる. し たがっおファむル入出力, ネットワヌクずいう, 実甚プログラミングの基本を身に぀けおしたえば, もう䞀頑匵りできっ ずできる事で, 実隓期間の最埌の方にそれが「動いた!」ずいう喜びをぜひ倚くの人々に䜓隓しお欲しいのである. 本テキストはそれなりに自己完結的に曞かれおいるので, 説明を順に読んで内容を理解しおほしい. その途䞭, 実際に 䜜業や考察する事を芁求する項目には, 以䞋の印のいずれかが぀けられおおり, その䜍眮づけは以䞋のずおりである. 準備課題 : 簡単な蚭定䜜業の類 (䟋えばネットワヌクぞ接続するための蚭定) や, 埌で課題を実行するための準備ずなる 簡単なプログラミング. 必須項目ず考えるべきだが, もし, チヌムメンバ党員がすでにそれらの項目に慣れきっお おり, 「目を぀ぶっおでもできる」ずいうような堎合はずばしおも構わない. 本課題 : 各回に数問蚭定されおいる, いわば「その日の目暙」ずでもいうべき課題. これらに぀いおは党班がプログラ ムを完成させるこず. 進床把握のため, プログラムは電子的に提出する. ただし, これらひず぀ひず぀にレポヌト を曞いお提出する必芁はない. 遞択課題 : 䜙力のある人には是非やっおもらいたい課題. 内容的には重芁だが, 仮にできなくおもその先で぀たづく事 はないだろうずいう意味で, 遞択課題ずしおいる. 基本的には, 準備課題たたは本課題ず印が぀けられた項目を順にこなしお行くのがよい. 実隓期間の最埌の 2 回を甚いおできたプログラムの発衚䌚を行う. 埌日レポヌトを提出する. 目暙の眮き方:

実隓期間を通しおの最終課題は, 前述のずおりむンタヌネット電話 (1 察 1 の通話), あるいは電話䌚議

システム (倚察倚の通話) を C のプログラムずしお完成させる事である. これを動かす, できればそこで䜕らかのオリゞ ナリティを発揮しお, 発展的課題に挑戊する事が, 孊期を通しおの党䜓目暙である. 各回では, そのために必芁な事を孊 ぶずいう目的意識を持っお臚むずよい. 各回の実隓時間の䜿い方:

最終課題達成に向け, 実隓の各回ではその回甚に蚭定された「本課題」を, 時間内に完成さ

せる事を目指す. そのために本テキストを読んで予習, 心の準備, 必芁ならば過去に習った項目の埩習をしおくる必芁が あるのは蚀うたでもない. 実隓の時間内に, 教員が必芁な項目を講矩圢匏で説明する時間垯もある. その時間垯は講矩時 間ず同様, 話を聞くこずに集䞭する. それ以倖の時間垯は実隓に取り組む. 普通はたず最初に, その日の目暙である本課


題を頭にいれ, その埌, その日の準備課題を順にクリアしおいき, 最終的にその回の本課題をクリアする. 準備課題は, 本 課題をクリアするのに理解すべき必須項目を少しず぀導入しおいるもので, これを順にクリアしおいけば本課題に取り 組み易くなるはずである. しかし, 班員党員がそれを読んで, 「簡単すぎおしかたがない」ず思えるような堎合は飛ばし お, いきなり本課題に取り組んでも構わない. 泚意:

決しお「面倒臭いから」 「早く枈たせたいから」ずいう理由で準備課題を飛ばさないこず. あくたで, すでにその

ような課題は日垞的にやっおおり, それらの項目は目を぀ぶっおでもできる, ず蚀う人たで圢匏的にそれらをこなす必芁 はない, ずいう意味で飛ばしおもよいず蚀っおいる. 実際には本課題をやるには, 準備課題盞圓の事をやらなくおはいけ ないようになっおいる堎合がほずんどであり, 飛ばしたずころで䜜業量が削枛されるわけではない. したがっおいきなり 本課題に取り組むのは, それをいきなり芋せられただけで, 䜕をやったらいいかがすぐに分かっおしたう人向けの遞択肢 である. そうでない人は準備課題を順々にこなしおいくのが, 「急がば回れ」で結果的に早く本課題をこなせるのではな いかず思う. チヌムワヌクに぀いお:

課題は, 普段 4 人䞀組の班を 2 ぀に割っお行う. ぀たり, 1 チヌム 2 人のチヌムを䜜っお行う

(奇数人の班は 2 人たたは 3 人のチヌム). そしお 1 チヌムでひず぀のプログラムを完成させればよい. そのために, チヌ ム内でコミュニケヌションを取りながら協力しお取り組むこず. 決しお, 各自がおんでにテキストを読み, 気が向いたず ころで連携も取らずにコンピュヌタに向かい, 話もせずに黙々ず取り組む, などずいう状態に陥る事のないようにする こず. 基本スタむルずしお, どうすればいいかを二人が理解したら, どちらか䞀人がプログラミングをする, もう䞀方の人はそ れを背埌から芋おツッコミを入れる, あるいは質問をする, ずいうスタむル (ペアプログラミング) が良い. それを, 時々 プログラミングをする係を亀代しながら行う. こうしお, お互いがコミュニケヌションを取りながら, 正しいプログラム, 良いプログラムに関する議論をしながら䜜業を進めおいくこず. お互いがやる事をよく理解し切った埌でなら, ある皋床 の「分担」をしおもよいが, 「負担を半分にする」目論芋で最初から課題を半々に分けるずか, たしおや「今日は A 君, じゃ, 埌はよろしく」などずいう分担の仕方をしないようにする. 特に, チヌム内でプログラミングに察する慣れの差が倧きいずきは, 倚少時間がかかるのは承知で, 特に序盀は䞍慣れ な人が積極的に䜜業を行い, もう䞀方がそれにツッコミを入れるずいうスタむルを取っおほしい. たた, 初玚者は自分の 慣れが少ないずいう事で遠慮したりせず, 怪しい郚分を芋぀けたら, このプログラムはここがおかしいのではないか, こ のプログラムはなぜ正しいのか, などの議論を仕掛ける事を心がけお欲しい (案倖盞手もよく分かっおいなかったりする ものである). 時間が䜙ったら:

各回の進床の目安を䞎えるために本課題が蚭定されおいるが, G1-G3 の内容は䞀぀ながりになっお

おり, あくたで党䜓ずしお䞀぀のプログラムを完成させる事が目暙になっおいる. したがっおある回に時間が䜙ったら先 ぞ進めばよい. 第 8 回に必須課題たでの内容を説明し終える予定であり, その必須課題をこなした埌どのような発展的 課題に取り組むかは, 各チヌムの裁量を重芖する. ある回に時間が䜙ったら, 是非その䜙った時間を利甚しお, 先の回の 予習をする, できるのであれば䜜業をする, 発展的課題に぀いお構想を緎る, 議論をする, などをしおほしい. 必須課題が 早くできおしたえばその分発展的課題に倚くの時間を割く事が出来る.

2. プログラミング以前の準備 2.1 ロ グ ã‚€ ン 挔習環境は, 孊科が貞し出しおいる PC の Linux (Ubuntu) を甚いる. 貞し出しおいる PC は Windows も立ち䞊がる ようになっおいるが, 本挔習の内容は, 音デヌタの入出力, ネットワヌク関係のコマンドなど, 倚くの堎所で Linux を 前提ずしおいる. 慣れおいない人もこの機䌚に孊ぶこず.

準備課題 1.1 Linux を立ち䞊げ, 自分のナヌザ (特に䜜っおいなければ, denjo ナヌザ) ずしおログむンせよ.

2.2 ネットワヌクぞの接続

準備課題 1.2 挔習甚のワむダレスネットワヌク ZENKIJIKKEN ぞ接続せよ. 確認がおら, 本課題の HP がある, 以


䞋の URL ぞアクセスせよ. 以降ここからお知らせを流したり, 資料やプログラムを提䟛する事もある.

http://g1g2g3.logos.ic.i.u-tokyo.ac.jp/ 2.3 シェル いろいろなコマンドを実行するのにもっずも基本的な道具は, いわゆるコマンドラむンで, 通垞, シェルず呌ぶ. シェ ルを起動するには, メニュヌから「端末」アプリケヌションを遞ぶ.

準備課題 1.3 メニュヌから, アプリケヌション → アクセサリ → 端末を遞択しお, 端末を開け.

2.4 Emacs ゚ディタ ファむル (プログラムやデヌタ) を線集するのにぱディタを甚いる. ゚ディタにはいろいろな皮類があるが Emacs を 䜿いこなせるようになるずよい.

準備課題 1.4 メニュヌから, アプリケヌション → アクセサリ → Emacs 22 (GTK) を遞択しお, Emacs ゚ディタ 開け.

3. C プログラミングの環境蚭定 Linux で C プログラミングを行うための基本フォヌムを説明する. 4 孊期に駒堎で䜿った Mac ず共通郚分が倚いので, よく埩習するずよい.

3.1 C コンパむラ Linux では, 暙準的な C コンパむラずしお cc たたは gcc ずいうコマンド (実はどちらも同じ物) を甚いる. もっずも 単玔には, C プログラムがかかれたファむル (䟋: ini.c) を甚意し,

$ gcc ini.c ずする (実際に入力する郚分は gcc ini.c のように倪字䜓で瀺されおいる. $はシェルのプロンプトに向かっお入力する 事を指瀺する印で, これ自身を入力するわけではない). これで a.out ずいう実行可胜ファむルができるが, その名前を指 定したければ, -o オプションを甚いお,

$ gcc -o ini ini.c ずする. できた実行可胜ファむルは,

$ ./ini のように実行する. “./” を忘れお,

$ ini だけだず,

$ ini bash: ini: command not found ずいう゚ラヌになるので泚意.


準備課題 1.5 以䞋のプログラムを Emacs で ini.c ずいう名前で䜜り保存せよ. それをコンパむルし, ini ずいう実行可 胜ファむルを䜜れ.

main(int argc, char ** argv) { char * gn = argv[1]; char * fn = argv[2]; char g = gn[0]; char f = fn[0]; printf(”initial of ’%s %s’ is %c%c.\n”, gn, fn, g, f); } ここでたくさんの譊告が出るが䞀旊気にせずに先ぞ進む. コンパむルできたら, 以䞋のようにコマンドを実行しお確認せよ.

$ ./ini Daisuke Matsuzaka initial of ’Daisuke Matsuzaka’ is DM. 匕数が足りないず以䞋のような出力 (Segmentation Fault) になる.

$ ./ini Daisuke Segmentation fault これも䞀旊気にせずに先ぞ進む.

3.2 ファむル䜜成ず呚蟺の基瀎 念のため, 䞊蚘を実行するための Emacs の操䜜ず基瀎抂念の埩習 (4 孊期に Mac でやったものずほずんど同じ).

(1) Emacs でファむルを䜜る: C-x C-f

(Ctrl キヌず x を同時に打ち, そのたた Ctrl キヌを抌しながら f を打぀)

ずするず

Find file: ~/ ずいうのが䞋に珟れるので, ~/ 以降にファむル名 (ここでは ini.c) を入力しお Enter.

Find file: ~/ini.c ~は自分の, 「ホヌムディレクトリ」を衚しおおり, 䞊蚘の操䜜でホヌムディレクトリ盎䞋にファむルが出来る. (2) Emacs でファむルを保存: C-x C-s (3) ファむル操䜜の GUI: Mac のファむンダや Windows の゚クスプロヌラに盞圓するものは, メニュヌ → 堎所 → ホヌムフォルダ で開く. ini.c を䜜ったらそれが芋えるはず.

(4) シェルの基本コマンド: シェルを起動した盎埌, $ ls


ずするずホヌムディレクトリのファむル䞀芧が衚瀺される. ini.c を含め, 䞊蚘の GUI ず同じファむルが芋えるは ずである. ディレクトリを理解する

今埌, すべおのファむルをホヌムディレクトリの䞋に䜜っおいくずいうわけにはいかないの

で, 適宜ディレクトリを䜜る, 䜜ったディレクトリ内に Emacs でファむルを䜜る, 䜜ったディレクトリ内のファむルをコ ンパむル, 実行する, ずいう事が出来るようになっおおいおほしい. そのために必芁な, 埩習しおおくべきコマンドず抂 念を列挙しおおく.

(1) パス名: ファむルやディレクトリの名前の事. • /から始たるパス名は, 絶察パス名ず呌ばれる. 䟋えば/home/tau/ini.c は, – ルヌトディレクトリ (システムに唯䞀存圚する, 頂点ずなるディレクトリ) の䞭の, – home ずいうディレクトリの䞭の, – tau ずいうディレクトリの䞭の, – ini.c ずいうファむル (ひょっずしたらディレクトリかもしれない. 名前だけからは刀断できない) を意味しおいる. たずえ話ずしおは, 2 号通実隓宀を, 「倧宇宙銀河系倪陜系地球日本囜東京郜文京区本郷

7-3-1 工孊郚 2 号通 4F 新通実隓宀」ずいうようなもの. • /から始たらないパス名は, 盞察パス名ず呌ばれる. ini.c のように単独でファむル名を指定しおいるのも, 盞察パス名の䞀皮である. 盞察パス名は以䞋で説明する「カレントディレクトリ」を起点ずした䜍眮を指 定しおいる. 䟋えばカレントディレクトリが/home/tau であれば, ini.c は, /home/tau/ini.c を意味し,

enshu/mk data.c は, /home/tau/enshu/mk data.c を意味しおいる. いちいち「倧宇宙銀河系倪陜系地 球日本囜東京郜文京区本郷 7-3-1 工孊郚 2 号通 4F 新通実隓宀」ずいう代わりに, 日本囜内では「東京郜文 京区本郷 7-3-1 工孊郚 2 号通 4F 新通実隓宀」ず蚀えばよいし, 普段工孊郚 2 号通にいる人は「新通実隓 宀」でよいのず同じ.

(2) カレントディレクトリ: 起動されおいるプロセスごず保持されおいる. シェルの堎合, pwd で衚瀺できる. $ pwd /home/tau シェルをメニュヌから立ち䞊げるず, 初期状態ではカレントディレクトリは自分のホヌムディレクトリずなっおお り, それは実隓環境では, /home/h ナヌザ名 i ずなっおいる.

(3) ディレクトリを䜜る: ディレクトリを䜜るには, $ mkdir h ディレクトリ名 i 䟋:

$ mkdir g1g2g3 は (g1g2g3 が盞察パスなので), h カレントディレクトリ i/g1g2g3 ずいうディレクトリを䜜る.

(4) Emacs でファむル䜜成: Emacs をはじめずしお色々な゜フトでホヌムディレクトリを~/ ず衚蚘する習慣があり, ファむルを䜜成した際の,

Find file: ~/ini.c は, ホヌムディレクトリ䞋に ini.c, ぀たり (ホヌムディレクトリが/home/tau であれば), /home/tau/ini.c を䜜 るずいう事になる. もちろんここで,

Find file: ~/enshu/mk data.c ずやれば, /home/tau/enshu/mk data.c ができる.

(5) カレントディレクトリの倉曎: シェルでカレントディレクトリを倉曎するには,


$ cd h ディレクトリ名 i ずする. たた,

$ cd は,

$ cd h ホヌムディレクトリ i の略蚘. 䟋:

$ cd g1g2g3 なお, カレントディレクトリを D にする事をしばしば, 「D に移動する」ずか, 「D に行く」などず蚀う.

(6) ディレクトリの内容を衚瀺: $ ls h ディレクトリ名 i で h ディレクトリ名 i にあるファむルやディレクトリ䞀芧が衚瀺される.

$ ls は,

$ ls h カレントディレクトリ i の略蚘.

(7) コピヌや移動: cp や mv などでファむルをディレクトリ間でコピヌしたり移動したりしお, 䜜ったファむルの「敎 理」ができるように. 慣れないうちは GUI でやっおもよい. ディレクトリ構造ず, 「カレントディレクトリ」の抂念を頭に入れお, ファむルがどこぞ行ったのかきちんず把握出来る ようにしおおくこず. 以䞋のような手順がよく珟れる.

$ cd

# ホヌムディレクトリぞ移動

$ mkdir enshu # enshu ディレクトリを䜜る ここで Emacs で ~/enshu/xyz.c を䜜成

$ cd enshu

# enshu ディレクトリぞ移動

$ ls xyz.c

# xyz.c ができおいる事を確認

$ gcc xyz.c

# コンパむル

$ ./a.out

# 実行

3.3 Emacs 内でのコンパむル プログラムの線集 → コンパむル → ゚ラヌを修正するためにたた線集 → たたコンパむル → · · · ずいうサむクルを 効率的に行うために, Emacs の䞭で gcc を実行する方法をぜひ身に぀けおほしい. それには端末で gcc を実行する代わりに, Emacs で

M-x compile (発音: 「メタ゚ックス コンパむル」) ずいうコマンドを実行する (このコマンドの実際の入力方法が分からなければ䞋 蚘参照). するず, Emacs 䞋郚の现いずころ (ミニバッファずいう) に, コマンドを入力するための以䞋のようなプロンプ トが珟れる (図 G1.1.1).


なお, プロンプトが珟れおいる状態でやっぱりやめたくなったら, C-g で脱出する (コマンド実行前の状態に戻る).

Emacs では䞀連のコマンド操䜜をやっおいる最䞭に蚳がからなくなったら, 倧抂の堎合 C-g で脱出できる.

図 G1.1.1

M-x compile 入力埌の Emacs 画面

Compile Command: make -k

“make -k” の郚分を消しお実行したいコマンド (端末に入力する物ず同じ) を入力する. Compile Command: gcc ini.c Emacs の䞭で別のバッファが開き, 端末で実行した時ず同じような出力が珟れる (図 G1.1.2). 同じ出力を埗るのにいちいち M-x compile ずやるのは, 䞀芋するず端末で入力するよりもかえっお面倒なようだが, あ る皋床プログラムの行数が増えお来お, コンパむル゚ラヌや譊告が出た際に真䟡が発揮される. コンパむル゚ラヌや譊告 が起きたら,

C-x ‘ (たず Ctrl キヌず x を同時に打ち, その埌‘ (= Shift + @) を単独で打぀) を打぀事で゚ラヌや譊告が起きた堎所に自動的にカヌ゜ルが飛んでくれる. C-x ‘を繰り返し入力するず次々ぞ別の゚ ラヌや譊告の堎所ぞ飛んでくれる. ‘はバッククオヌト (逆匕甚蚘号). 普通の匕甚蚘号 (’) ではないので泚意. 普段あた り䜿わない䞊に, キヌボヌド䞊芋぀けにくい䜍眮にある.

M-x コマンドに぀いお

Emacs の操䜜方法の説明の䞭で, “M-x h コマンド名 i” ずいう衚蚘が䜿われる. M-x ずは具

䜓的には, M-(メタキヌず呌ばれる) ず, x を同時に抌す事を意味する. ではそのメタキヌずはどのキヌの事か (‘M’ の事 ではないので泚意)? ややこしい事に, キヌボヌドのタむプによっお違う事がある䞊, 䜕通りも入力の仕方がある. 奜み に応じお䜿い分けお欲しい.


図 G1.1.2

M-x compile で gcc を実行埌の Emacs 画面

• Alt キヌ (通垞スペヌスキヌの巊偎). x ずの距離が近いので速いが, 䜿えない事もある. • ESC キヌ (通垞巊䞊). ほが確実に䜿えるが x ずの距離が遠い. この堎合実は ESC キヌず x を同時に打぀必芁は ない (ESC キヌを抌した埌, x を抌せば良い).

• C-[キヌ (Ctrl キヌず [キヌを同時に抌す). どこでも䜿えお, しかも Ctrl は巊手, [は右手なので, 慣れるず速い. この堎合も C-[キヌず x を同時に打぀必芁はない.

準備課題 1.6 䞊の ini.c を, M-x compile を甚いおコンパむルせよ. C-x ‘で譊告や゚ラヌの堎所に自動的にカヌ゜ル を飛ばしおみよ. M-x compile でコンパむルする際, -Wall ずいうオプションを぀けおみよ (぀たり, gcc -Wall -o ini

ini.c). 倚数の譊告 (タむプミスをしおいれば゚ラヌが出るかもしれない) が出力される. それぞれの譊告や゚ラヌの堎 所にカヌ゜ルを飛ばし, その意味を考え, 譊告が出ないように修正しおみよ.

なお, M-x compile を実行するず, 画面がひずりでに二぀に分割される. 元の状態に戻したければ, C-x 1 を入力する ず元に戻る. Emacs で, 画面の分割や画面に衚瀺する内容・ファむルを切り替える方法に぀いおは, 4. 節にたずめた. 機䌚のある時に䞀床, Emacs のマニュアルを読んで, 画面分割, それを元に戻す, 画面間を行き来する, 画面に衚瀺す るファむルを倉える, などの操䜜が出きるようになっおおくず良い (倚くはメニュヌからでも行えるので, なれないうち はそれを䜿うのも良い).

4. Emacs におけるりィンドり, バッファの扱い Emacs では, 同時に耇数のファむルを開いおそれらを行ったり来たりしながら線集する事が出来る. 画面を分割しお 半分にファむル A, もう半分にファむル B, ずいうような事が普通にできる. 玔粋に耇数のファむルを線集するだけなら,


Emacs を耇数起動しおもよいのかもしれないが, この機胜は先の M-x compile や, 以䞋で玹介する M-x gdb (Emacs 内 でデバッガを実行する機胜) を䜿ったずきに, ひずりでに発動される (勝手に画面が分割される). したがっお画面を分割 した状態から元にもどしたり, 分割しお出来た小画面の間を行き来したり, それぞれの小画面に衚瀺するものを切り替え たり, などが自圚にできないず戞惑う事になる.

図 G1.1.3

Emacs のフレヌム, りィンドり, バッファ

ここではそれを混乱なく行えるための最䜎限の抂念を曞いおおく. 説明を誀解なく行うためにたずは蚀葉に぀いお

(図 G1.1.3 参照) フレヌム : Emacs を立ち䞊げるず出おくる四角党䜓を, Emacs ではフレヌムず呌ぶ. 倚くのワヌプロ゜フトでりィンド りず呌んでいるものにあたる. りィンドり : Emacs では, ファむルの内容や M-x compile におけるコンパむラの出力など, 䞀かたたりの内容を画面に 衚瀺しおいる領域をりィンドりずいう. 䞀般にはフレヌムの䞭に 1 ぀たたは耇数のりィンドりが存圚しおいる事に なる. バッファ : ファむルの内容や, M-x compile の結果などを保持しおいる「もの」を, Emacs ではバッファず呌ぶ. 先の りィンドりず䌌おいるが, バッファは必ずしも画面に衚瀺されおいる (りィンドりが割り圓おられおいる) ずは限 らない事に泚意が必芁である. ぀たり, Emacs であるファむルを開けば, そのファむルの内容を保持するバッファは垞に (ファむルを閉じるたで) 存圚 しおいるが, 垞にりィンドり (画面) に衚瀺されおいるずは限らない. 慣れないうちは, 自分の線集しおいるファむルのバッファがりィンドりから消えおしたうず, それを再びりィンドりに 衚瀺する方法が分からず, 結果ずしおそのファむルを線集できなくなったり, 線集する床に Emacs を起動し盎したりす る矜目になり効率が悪い. 慣れれば簡単であるし, 慣れないうちはメニュヌからほずんどの操䜜を行う事も出来るので, 以䞋をマスタヌしおほしい (è¡š G1.1.1 参照).

(1) 䜕はなくずも C-g: 先にも説明したが, Emacs では䞀連のコマンド操䜜をやっおいる最䞭に蚳がからなくなった


ら, C-g で脱出できる (コマンド実行前の状態に戻る). 以䞋をやっおいる最䞭も同様.

(2) りィンドりを分割する: 分割したいりィンドりの䞊にカヌ゜ルがある状態で, C-x 2 (瞊分割は C-x 3). たたは

Emacs メニュヌ → File → Split Window これを繰り返すずいくらでも画面を分割する事が出来る. 耇数のファむルを芋比べながら線集したい堎合に䜿う. たた, M-x compile やのちに説明する M-x gdb を実行 するずひずりでにこれが実行される.

(3) 分割されたりィンドり間を行き来する: マりスで目的のりィンドりの䞊をクリックするか, たたは C-x o ずする.

(4) 分割されたりィンドりのうちの䞀぀を画面から消す: 目的のりィンドりの䞊にカヌ゜ルがある状態で, C-x 0 ずする. りィンドりを消すのであっお, ファむルを閉じる (バッファを消す) のではない事に泚意. あくたで「画 面に䜕を衚瀺するか」を遞んでいるだけである. 画面から消えたバッファは, 「りィンドりに衚瀺する内容 (バッ ファ) を切り替える」にある方法で再び衚瀺できる.

(5) 分割されたりィンドりのうち, 䞀぀を陀いお党郚画面から消す: 目的のりィンドりの䞊にカヌ゜ルがある状態で, C-x 1 ずする. 画面をすっきりさせたいずきに䜿う.

(6) ファむルを開く: 別のファむルを開く (線集する) 床に新しい Emacs を立ち䞊げる必芁はない. C-x C-f たたは,

Emacs メニュヌ → File → Visit New File で珟圚のりィンドりに新しいファむルが衚瀺される. もずもず衚瀺されおいたファむル (バッファ) は, 閉じられ たのではなく, あくたでりィンドりに衚瀺されなくなっただけの話なので泚意. 再び衚瀺したければ, 以䞋「りィ ンドりに衚瀺する内容 (バッファ) を切り替える」を参照. たた, 二぀ずも衚瀺したければ, 適宜りィンドりを分割 しお耇数のりィンドりを衚瀺しおから, 目的のりィンドり䞊で, 「りィンドりに衚瀺する内容 (バッファ) を切り 替える」をやる.

(7) りィンドりに衚瀺する内容 (バッファ) を切り替える: Emacs メニュヌ → Buffers で衚瀺から目的のファむルを遞択 慣れお来おいちいちマりスを䜿うのが面倒になっおきたら,

C-x b ずやるず,

Switch to buffer (default . . . ):


è¡š G1.1.1 操䜜内容 操䜜の䞭断 りィンドり分割 りィンドり間移動 りィンドりを画面から消す 䞀぀のりィンドりだけを画面に残す ファむルを開く ファむルを閉じる (バッファを消す) りィンドりの衚瀺内容 (バッファ) 切り替え バッファの䞀芧

Emacs りィンドり・バッファ操䜜

キヌボヌド

C-g C-x 2 (瞊分割: C-x 3) C-x o C-x 0 C-x 1 C-x C-f C-x k C-x C-b C-x C-b

マりス

Emacs メニュヌ → File → Split Window 移動先りィンドりで巊クリック

Emacs メニュヌ → File → Visit New File Emacs メニュヌ → Buffers Emacs メニュヌ → Buffers

ずいうのが画面䞋郚に出るので, そこでファむル名を入力する. 䞀芧を衚瀺したければそこで htabi. たた, 途侭 たで入力しお htabi を抌せば補完しお (残りを補っお) くれる. 䞀芧を衚瀺するずそこで自分の芋芚えのあるファ むル名に加えお, M-x compile の結果できたバッファや, Emacs が勝手に䜜ったバッファも衚瀺されるので䞀床 泚意しおみよ.

(8) 存圚しおいるバッファの䞀芧: Emacs メニュヌ → Buffers でメニュヌにバッファ䞀芧が衚瀺される. たたは

C-x C-b でもよい.

(9) バッファを消す (ファむルを閉じる): 目的のバッファをりィンドりに衚瀺しお, そのりィンドり䞊にカヌ゜ルが ある状態で,

C-x k ずするず

Kill buffer (default h そのファむル名 i): ず衚瀺されるのでそこで Enter. 消したいファむルの名前が分かっおいるのであれば, いきなり C-x k で, 䞊蚘 プロンプトに察しおファむル名を入力しおもよい (ここでも htabi を抌すず䞀芧が出おきたり, 途䞭たで入力しお

htabi を抌すず補完をしおくれたりする). Emacs では 10 や 20 のバッファを保持しおおくのはよくある事で, あたりこためにファむルを閉じる必芁はな い (むしろこために保存 C-x C-s しおおくこず).

5. 情報源: マニュアルに぀いお 本資料でも必須の項目に぀いおは, 操䜜方法を含めお説明しおいるが, 網矅的に説明する事はできない. コマンドの䜿 い方, ゜フトの操䜜方法, C 蚀語の関数など, 詳现な情報を自分で調べられるようになっおほしい. ここでも怜玢゚ンゞンは偉倧なツヌルだが, Linux (UNIX) では以䞋のような決たった操䜜で, 信頌できる詳现な情報 が埗られる. 積極的に䜿いこなすこず.

5.1 man コマンド $ man h コマンド名 i $ man h 関数名 i などで, コマンドや C 蚀語の関数の詳现なマニュアルを衚瀺する事ができる. 䟋:


$ man gcc $ man open $ man -s 2 open man コマンドは q で終了する (less コマンドを起動しおいるので操䜜はそれず同じ). -s 2 ずいうオプションは, マニュアルの第 2 章から怜玢せよずいう意味で, man だけでは目圓おでないペヌゞが衚瀺 されおしたう (䟋えば, C 蚀語で䜿う open ずいう関数に぀いお知りたいのだが, man open は open ずいうコマンドが衚 瀺されおしたう) 時に甚いる. 1 章がコマンド, 2 章がオペレヌティングシステムが盎接提䟛しおいる関数 (システムコヌ ル), 3 章がその他の関数 (ラむブラリ関数) である. 実際問題ずしおは, 関数に぀いお知りたいのにコマンドが出おきお したう, ずいう堎合に, -s 2 や-s 3 を詊しおみる, ずいう䜿い方をする事が倚い. 同じ内容を Emacs 内に衚瀺させる事もできる.

M-x man Manual entry: gcc のように. これは特に, 関数の䜿い方を調べお, 必芁な#include や, 匕数の䞊びなどをコピヌするのに䟿利である (Emacs のコピヌ&ペヌストをマスタヌしよう). この堎合も目圓おでないコマンドが出おしたったら,

M-x man Manual entry: -s 2 open などずする.

5.2 info コマンド 倧きなコマンドの本栌的なマニュアルは, info コマンドで提䟛されおいる事が倚い.

$ info gcc $ info emacs など. q で終了, スペヌスでペヌゞをめくる. それ以倖の操䜜は Emacs ず䌌おいる. ある項目を衚瀺したい時は, m を 打っおから項目名を入力するか, 行きたい項目の䞊にカヌ゜ルを持っお行っお m を打぀.

$ info だけで起動するずすべおの info ペヌゞの䞀芧が珟れる. これも Emacs 内で起動でき,

M-x info ずする. ぜひ䞀床, 自宅などで時間を取っお, 「マニュアル閲芧の緎習」をしおみるずよい.

• man で man コマンドの䜿い方を眺めおみる • man で top コマンドの䜿い方を眺めおみる • info で info の操䜜方法を習埗する. たずはスペヌスキヌで通しお読む. q で終了. • info で, Emacs の䜿い方を眺めおみる. 䟋えば画面の分割や切り替えの方法に習熟すべく, マニュアルの該圓セクションを探しお読んでみる.

6. デ バッガ デバッガはプログラムを実行しながらその内郚を調べる事ができるツヌルである. 具䜓的には以䞋のような事ができる.


(1) プログラムを少しず぀ (䟋えば 1 行ず぀) 実行させる (2) プログラムを特定の堎所たで実行させる (3) プログラムの実行が停止した堎所で, 倉数や匏の倀を衚瀺させる (4) プログラムの実行が停止した堎所で, 関数呌び出しの履歎 (バックトレヌス) を衚瀺させる 特に, プログラムが segmentation fault などを起こしお異垞終了した際に, デバッガを甚いるず, 発生した䜍眮や, そこ での倉数や匏の倀を衚瀺させる事ができる. segmentation fault での終了はさもなければ手がかりを埗る事が難しいの で, デバッガがもっずも重宝される堎面である.

6.1 デバッガの起動ず終了 ここでは最䜎限のデバッガの甚い方を説明する. 倚くの郚分は 4 孊期の挔習で習っおいるず思う. ステップ 1: コンパむル : プログラムをコンパむルする際に, -g オプションを぀けおコンパむルする.

M-x compile Compile command: gcc -g -o ini ini.c もちろんその他のオプションを぀けおもよい. 実際, -Wall を垞に぀けおおくのはよい習慣である. ステップ 2: デバッガを起動 : デバッガを起動するコマンドは, “M-x gdb” ずいうコマンドである.

M-x gdb Run gdb (like this): gdb --annoteate=3 ini 最埌の, “ini” の郚分が実際にデバッグしたい実行可胜ファむル名で, 必芁に応じお修正する. 䞊蚘を行うず, Emacs の画面が二぀に割れ, gdb のプロンプト (以䞋の (gdb)) が珟れる.

Current directory is /home/tau/ GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type ”show copying” and ”show warranty” for details. This GDB was configured as ”i486-linux-gnu”... (gdb) この状態ではただプログラム (ini) は起動しおいない. 以降 gdb のプロンプトに様々なコマンドを入力する事で, プログラムを終了たで実行したり, 少しだけ実行したり, 停止した堎所での状態を調べたりする事ができる. ステップ 3: プログラムの起動 : ずもあれプログラムを起動しおみる. run コマンドがプログラムを最初から実行する ためのコマンドである.

(gdb) run Daisuke Matsuzaka で, ini に, 匕数 Daisuke Matsuzaka を䞎えお実行する (぀たりこれで, 端末から./ini Daisuke Matsuzaka ず したのず同じ. コマンド名 ini 自䜓はここで改めお䞎える必芁はない).

(gdb) run Daisuke Matsuzaka Starting program: /home/tau/ Daisuke Matsuzaka Initial of ’Daisuke Matsuzaka’ is DM. Program exited with code 046. (gdb) なお, run の代わりに r だけでもよい.


䞀般に gdb は曖昧さがなければいくらでもコマンドの名前を省略できる (䟋えば次に述べる quit の代わりに q だ けでもよい, など). ステップ 4: デバッガを終了する : 終了するには,

(gdb) quit ずする. 正しいプログラムをデバッガで実行しおもあたり面癜みはない. 今床は ini を足りない匕数で実行し, 先ほど発生しお いた segmentation fault の発生䜍眮を突き止めよう. もう䞀床 gdb で ini を実行するが, 今床は匕数ひず぀で実行する. ステップ 1,2 は先ず同じで, ステップ 3 で,

(gdb) run Daisuke ずする. するず以䞋のようなメッセヌゞが衚瀺されるずずもに, その゚ラヌが発生しおいる゜ヌスコヌド䞊の䜍眮が衚瀺 されるであろう.

(gdb) run Daisuke Starting program: /home/tau/ini Daisuke Program received signal SIGSEGV, Segmentation fault. 0x080483a7 in main (argc=2, argv=0xbf8815d4) at ini.c:5 画面を芋るず,

char f = fn[0]; ずいう行でプログラムが停止しおいる ⇒ その行の実行䞭に segmentation fault が発生しおいる, ずいう事が瀺されお いる (図 G1.1.4).

Segmentation fault は䞀般に, 䞍正なメモリアドレスを参照した際—C 蚀語の蚀葉で蚀えば, 間違ったアドレスを保 持しおいるポむンタ p を, p[i], *p, p->f などの匏で参照した際— に発生する゚ラヌであり, 䞊蚘を芋るず (おそらく)

fn ずいうポむンタが䞍正なアドレスを保持しおおり, それを通じお fn[0] ずいう参照を行っおいるからであろうず想像 が぀く. それを確かめるには, fn ずいう倉数に䜕ずいうアドレスが入っおいるかを芋るずよい. それには print コマンドを甚 いる. print コマンドは gdb 内で匏の倀を衚瀺するコマンドである.

(gdb) print fn $4 = 0x0 (gdb) print fn[0] Cannot access memory at address 0x0 (gdb) 入っおいるアドレスは 0x0 (぀たり 0 番地. 16 進数衚蚘) であり, これは䞍正なポむンタの代衚倀のような物である. も ちろんアドレスを芋おどのアドレスなら䞍正かを䞀般的に刀断するのは難しいが, 0 を代衚ずしお, 非垞に小さい数字は ほが間違いなく䞍正である. 0x0 が䞍正である事は, その次の print fn[0] を実行した際の゚ラヌメッセヌゞ (Cannot

access memory at address 0x0) で確信できる. 今回の堎合, ここに 0x0 が入っおいた理由は, コマンドラむン匕数を䞀぀しか䞎えおおらず, argv[0], argv[1] には文字 列が入っおいるが, argv[2] には 0x0 が入っおいたためである.


図 G1.1.4

GDB 内で segmentation fault が起きた堎所を衚瀺

6.2 関数呌び出し履歎の衚瀺 (bt, up, down) 前節ではプログラムが停止した際, 特に segmentation fault で停止した際に゜ヌスコヌドの䜍眮を瀺しおくれる事を 芋たが, 最終的に segmentation fault を起こした䜍眮だけでは圹に立たず, どのような関数呌び出しを経おそこにたど り着いおいるのかを知りたい事がある. このための機胜がバックトレヌスを衚瀺する (bt) および, フレヌム間の行き来 をする (up/down) コマンドである. 説明のために, 先ほどのプログラムを少し修正しお以䞋のようにする. 単に, fn[0] のように文字列の先頭を取埗しおい る郚分を, get first char ずいう関数を䜜っお取埗するようにした.

char get first char(char * s) { return s[0]; } main(int argc, char ** argv) { char * gn = argv[1]; char * fn = argv[2]; char g = get first char(gn); char f = get first char(fn); printf(”initial of ’%s %s’ is %c%c.\n”, gn, fn, g, f); } 同じように-g を぀けおコンパむルし, デバッガで, 足りない匕数で実行するず, 圓然の事ながら, 3 行目の return s[0]; の 行で segmentation fault が起きる.

(gdb) r Daisuke Starting program: /home/tau/ini2 Daisuke


Program received signal SIGSEGV, Segmentation fault. 0x0804837a in get first char (s=0x0) at ini2.c:3 ポむンタ s の倀や, s[0] を衚瀺させる事で, 同じ事が起きおいるず玍埗できる.

(gdb) print s $1 = 0x0 (gdb) print s[0] Cannot access memory at address 0x0 ここで, bt (backtrace) コマンドを実行するず, どのような関数呌び出しの結果, そこぞたどり着いおいるのかが衚瀺 される.

(gdb) bt #0 0x0804837a in get first char (s=0x0) at ini2.c:3 #1 0x080483c2 in main (argc=2, argv=0xbfb59894) at ini2.c:10 この堎合, main が get first char を呌び出しおいるずいう至極もっずもな結果が衚瀺される. さお先ほどず違い, s=0x0 は, 関数の匕数ずしお枡された物なので, 間違いの「倧元の原因」は, その関数を呌び出し た地点に遡っお初めお分かる. そのために甚いるのが up コマンドである. up コマンドを実行するず, Emacs 䞊で矢印 が, main 関数内の, get first char が呌び出された地点に移る. 今床はそこで, fn や, その他, main 関数内で定矩されお いる倉数を甚いた匏を評䟡できるようになる.

(gdb) up #1 0x080483c2 in main (argc=2, argv=0xbfb59894) at ini2.c:10 (gdb) p fn $2 = 0x0 容易に想像できる通り, この状態で down を実行するず泚目地点が元に戻る.

6.3 ブレヌクポむントずステップ実行 (break, next, step, cont, fini) デバッガを甚いおプログラムを所定の䜍眮たで実行させたり, 䞀行ず぀実行させる機胜である. それらすべおの基本は, ブレヌクポむントずいう機胜で, プログラムの途䞭に印 (ブレヌクポむント) を぀けた䞊でプログラムを実行する. プロ グラムの実行がそこに達したら停止する. 停止したらあずは segmentation fault で停止したずきず同じで, print や bt,

up, down を䜿っおプログラムの状態を調べる事ができる. ステップ 1,2 : プログラムをコンパむルし, M-x gdb でデバッガを起動するずころたではこれたでず同じ. ステップ 3 ブレヌクポむント蚭定 : run コマンドでプログラムを走らせる前に, 止めたいずころにブレヌクポむントを 蚭定する. ここではプログラムの先頭で止たるように, main 関数に蚭定する.

(gdb) break main Breakpoint 1 at 0x8048393: file ini2.c, line 7. break は b でもよい. ステップ 4 プログラムの起動 : 埌は通垞どおりプログラムを走らせる.

(gdb) r Starting program: /home/tau/ini2 Breakpoint 1, main (argc=1, argv=0xbf8f3634) at ini2.c:7 するずプログラムが main 関数の先頭で停止する. Emacs 䞊で゜ヌスファむル䞊の停止䜍眮に印が衚瀺される.


この状態から以䞋のような手段でプログラムを「少しず぀」実行できる.

break ず continue : もう少し先に別のブレヌクポむントを蚭定しおそこたで進める. この堎合, 進めるために run コ マンドではなく, continue を䜿う (run は垞にプログラム最初からの実行).

step : 1 文実行する. その文に関数呌び出しが含たれおいたらその関数の䞭に突入する (぀たり呌び出された関数の先 頭で停止する).

next : 1 文実行する. step ず異なり, あくたで今いる文の実行が終わるずころたで進む. これらを組み合わせおプログラム実行䞭のどの時点で, どのような状態になっおいるかを調べお行く事ができる. なお, ブレヌクポむントは以䞋の堎所に蚭定する事ができる. 関数の先頭 :

(gdb) break h 関数名 i 䟋:

(gdb) b main ゜ヌスコヌド䞊の指定䜍眮 :

(gdb) break h ゜ヌスファむル名:行番号 i 䟋:

(gdb) b ini.c:5 Emacs のキヌ操䜜 : 前項ず同じ事を, Emacs で゜ヌスコヌド䞊のブレヌクポむントを蚭定したい行にカヌ゜ルを移動 しお, C-x SPACE を打぀事でできる.

6.4 デバッガ: たずめ コマンドずしおは以䞋蟺りを抑えおおくずよい. display, finish は本資料では説明しおいない. gdb の䜿い方に぀いお も是非, info などで䞀床時間を取っおみおみるずよい.

• q(uit) • r(un) • p(rint), disp(lay) • bt, up, down • b(reak), n(ext), s(tep), c(ont), fini(sh)

7. C プログラムのデバッグ 今回の課題は C プログラムの間違いを修正する事である. C 蚀語に関する埩習, デバッガの䜿い方の埩習ずレベルアッ プ, より高次元な, 「デバッグの仕方 (6= デバッガの䜿い方)」を身に぀ける緎習ず思っお, クむズ気分で取り組んでほしい.

本課題 1.7 課題 HP にアクセスするず, たくさんの「間違った」プログラムの入ったファむルがおいおある. それら に含たれる間違いを修正し, 正しく動かせ. コンパむルする際は最終的には, -Wall オプションを぀けお, 譊告が出なく なるようにせよ. 手順:

ファむル problems.tar.gz をダりンロヌドしたら, それを以䞋で展開する.

$ tar xvf problems.tar.gz


するず, problems の䞋に倚数のサブディレクトリ 00, 01, . . . , ができる. 䞀぀のディレクトリが䞀問に盞圓する. 各ディ レクトリのファむルをコンパむルしお実行し, 正しい動䜜をさせる事が目暙である. コンパむル時に゚ラヌになったらそ れを読んで修正する. 無事コンパむルできたら実行する. 実行の仕方 (䞎えるべきコマンドラむン匕数など) ず, そのプ ログラムが意図しおいる蚈算内容は, プログラム先頭にコメントずしお曞いおあるので, それを読むこず. 匕数を䞎える 堎合, いろいろな匕数で正しく動く事を目暙にする. 䟋 1:

$ cd 00 $ gcc p00.c $ ./a.out

# ここで゚ラヌが出たら修正 # ここで゚ラヌが出たり動䜜がおかしければ修正

もちろんコンパむルには M-x compile を䜿う事を掚奚する. 䟋 2:

$ cd 03 $ gcc p03.c $ ./a.out 3 3^2 = 8

# 匕数に぀いおは p03.c のコメントを読む # 間違い! 修正

いろいろなレベルの間違いが含たれおいる. それらに察し, その意味するずころを正しく把握しお, 修正するのが課題で ある. 正しいプログラムにするために必芁な修正の量は, 文字数・行数ずしおはどれもわずかである. たた, 個々のプロ グラムは䞀から曞いおもすぐに曞けおしたうような単玔な物もある. だからずいっお䞎えられたプログラムを無芖しお, ずもかく動くプログラムを曞いお, 「はいできたした」ずいうのはこの挔習の意図するずころではない. あくたで, 䞎え られたプログラムの, 「どこが, なぜ間違っおいるかを求める」のが目暙である. 「自分は医者, プログラムは患者」ず蚀 う぀もりで, 症状から間違いを「蚺断」「説明」しおほしい. ヒントずしお, どのような段階でどのような皮類の゚ラヌ が埅ち受けおいるのか, そのいく぀かを列挙しおおく (以䞋は今埌も実際に遭遇する, 兞型的な゚ラヌである). コンパむル・リンク時の゚ラヌ : gcc が゚ラヌを出力する.

• 文法゚ラヌ • 型゚ラヌ • 関数宣蚀の欠劂に関する譊告 • リンク゚ラヌ. プログラム䞭で䜿甚しおいる関数がどこにも定矩されおいない, ずいう゚ラヌ. これを盎す には gcc を起動するコマンドラむンに, あるオプションを远加する必芁がある. 実行時の゚ラヌ : 実行可胜ファむルができるが, 実行するず゚ラヌメッセヌゞを衚瀺しお途䞭で終了する.

• segmentation fault • assertion ゚ラヌ (“Assertion ‘x == y’ failed.” のようなメッセヌゞ) • その他の゚ラヌ (“fopen: No such file or directory” のようなメッセヌゞ) 動䜜・出力の間違い : 実行可胜ファむルができ, 実行しおもあからさたな゚ラヌはでないが, 答えなり, 動䜜なりがおか しい. もちろん最埌のタむプの゚ラヌが, 実際゚ラヌであるずわかるためには, そもそもそのプログラムが䜕をする「はずの」 プログラムなのかが, どこかで芏定されおいなくおはならないが, 出力から垞識的に刀断しおほしい (䟋えばプログラム の出力が, “10 + 20 = 50” だったら, それはおかしいずいう事). プログラム自身は高床でもなければ, 圹に立぀物でもない. あくたで, C 蚀語の芏則をよく知る事, プログラムの実行 芏則を知る事, 問題究明のための考え方を習埗する事, そのための道具 (デバッガなど) を習埗する事が目的である. 具䜓 的には次のような抂念や道具を「緎習する」事を心がけるこず.

• コンパむル時の゚ラヌは, きちんず゚ラヌメッセヌゞを読んで, 䜕を蚀われおいるのか, なぜそれが゚ラヌになる のか, C 蚀語の芏則を理解するこず (圓おずっぜうでプログラムを修正しない).


• M-x compile ず, C-x ‘によっおコンパむル゚ラヌの䜍眮ぞ自動的に飛ぶ, を䜿いこなすこず • segmentation fault など異垞な終了の仕方をしたら, ゜ヌスを圓おもなく眺めるのではなく, たずは M-x gdb で, それが発生しおいる堎所を突き止めるこず. 圓おずっぜうで盎す, の前になぜそれが起きるのかを理解するこず.

segmentation fault は觊っおはいけないメモリ番地を觊った時におこる. それが起きる理由のほずんどは, 配列の 添字にその芁玠数以䞊の倀を指定した堎合, ポむンタ倉数に間違った倀を入れ (あるいは倀を入れ忘れ) おそれを 通じおメモリをアクセスした堎合, である (*p, p[i], p→f など).

• assertion ゚ラヌは発生䜍眮 (゜ヌスファむル名ず行番号) がひずりでに衚瀺されるが, やはり M-x gdb で突き止 めるのも簡単である.

• その他の実行時゚ラヌに぀いおも, プログラムが終了する際に呌ばれる exit, abort などの関数にブレヌクポむン トを仕掛け (break exit) お実行する事で捕捉できる. up コマンドを䜿っお exit/abort を呌び出した地点に戻る 事で, 問題の発生堎所を突き止められる.

• 䞀般に, プログラムの入力を倉えられる堎合, その入力を色々かえお挙動を探る. その際, 「゚ラヌが出るなるべ く小さな入力を突き止める」ずいう考え方が重芁である. その方が圧倒的にデバッグは楜になるし, それができた 時点で原因が掚枬できおしたう事も倚い.

• プログラムの挙動を調べるのにはプログラムがどこを実行しおおり, その時に倉数の倀が䜕であるかを調べるの が基本である. printf をはさんで芁所で倉数や匏の倀を衚瀺する, いわゆる “printf デバッグ” は有効である.

8. Linux を䟿利に䜿うためのおたけ 8.1 パネルにアプリケヌションを登録する メニュヌからアプリケヌションを毎回遞択する代わりに, パネルに登録しおおく事ができる.

(1) 目的のアプリケヌションをメニュヌに衚瀺させお, そのアプリケヌションの䞊で右クリック (2) 「このランチャをパネルに远加」 こうしお, 端末ず Emacs は毎回メニュヌを䜕局もたどらずに起動できるようにしおおくず良い.

8.2 パネルに CPU・ネットワヌク䜿甚量を衚瀺する パネルに垞時, CPU やネットワヌクの䜿甚量を衚瀺しおおくず, 自分のプログラムが走り出した時の倉化が芋られお 面癜い䞊, 間違いを探す助けにもなる (堎合もある).

(1) パネルの空癜郚分を右クリック → パネルぞ远加 → システムモニタ を遞択 (2) プロセッサ消費量の衚瀺がパネルに珟れる. それを右クリック → 蚭定 (3) 「監芖するリ゜ヌス」で, プロセッサ, メモリ, ネットワヌクを遞択. 他にも衚瀺したければ自由に遞択. 8.3 ゜フトりェアパッケヌゞをむンストヌルする 挔習で䜿う倚くの゜フトりェアはすでにむンストヌルされおいるが, 新しいパッケヌゞを入れるのも簡単なので, やり 方を芚えおおくず良い. 方法 1 GUI :

(1) メニュヌから, アプリケヌション → 远加ず削陀 を遞択 (2) 奜きなアプリケヌションを遞んで「倉曎の適甚」. これは, 「どんなアプリ (ゲヌム?) があるのだろう」ずいうようなずきに有甚である. 方法 2 apt-get コマンド : むンストヌルしたい゜フトの名前 (パッケヌゞ名) が分かっおいる堎合, root ナヌザになっ た埌, コマンドラむンで, apt-get コマンドでむンストヌルできる. 䟋えば “iperf” ずいうパッケヌゞを入れるず蚀 う事になったら,

$ sudo apt-get install iperf でよい. どのようなパッケヌゞがむンストヌルできるかの怜玢は,

$ apt-cache search h キヌワヌド i


でできる. ゲヌムを探したいのなら,

$ apt-cache search game たた, 「䜿いたいコマンド名」がそのたたパッケヌゞ名になっおいる事が倚い. したがっおあるコマンドを䜿っお, 「そ んなコマンドはない」ずいわれたら,

$ sudo apt-get install h ないずいわれたコマンド名 i ずすれば, 10 秒埌には䜿える状態になっおいる事が倚い. それ以前に, Ubuntu の堎合, 有名なコマンドに぀いおは, 「そ んなコマンドはない」ず蚀われたずきにパッケヌゞ名を教えおくれる事もある.

$ sl プログラム ’sl’ はただむンストヌルされおいたせん。 次のように入力する事でむンストヌルできたす:

sudo apt-get install sl bash: sl: command not found GUI を甚いた方法で, 有甚そう, 面癜そうなアプリケヌションを探しおむンストヌルしおみよ. apt-cache search でも探 しおみよ.


第2日

ファむルの読み曞きず, 音デヌタの入出力

今回はファむルの読み曞きを䞭心に行う.

2 幎 4 孊期に C 蚀語の暙準ラむブラリ (fopen, fprintf など) を甚いたファむル入出力をやったず思うが, ここでは UNIX のシステムコヌルを甚いたファむル入出力に぀いお玹介する. 関数名ずしおは, open, read, write, close の 4 ぀ を甚いる. 䞡者の違い, なぜこの実隓でこちらを甚いるのかに぀いおも軜く觊れる. その過皋で, ファむルの䞭身を「バ むトの列」ずしお正しく理解できるように, 別の蚀葉で蚀えば「バむナリファむルの読み曞きが混乱なくできるように」 なっおほしい. 次に, ある特別なファむル (/dev/dsp) を読み曞きするだけで, 音の入出力が行える事を説明する. したがっおプログ ラミングずしおは, ファむルの入出力ができれば録音・再生が行える 事になる. 音を衚珟するデヌタ圢匏も非垞に扱い やすい物で, 各時刻における振幅をそのたた䞊べただけの物 (Linear PCM) である. 最埌に, 埌々波圢デヌタを解析したり倉換したりするための助けずしお, グラフを可芖化するツヌル gnuplot に぀いお 孊び, 録音・再生される音を可芖化する.

1. ファむル入出力 1.1 ファむルぞの出力ずファむルの䜜成 たずはファむルにデヌタを曞き蟌む緎習をしおみよう. 基本フォヌムは以䞋のようになる. ステップ 1: ファむルを開く :

int fd = open(filename, O WRONLY | O CREAT | O TRUNC, 0644); フラグがややこしいが, さしあたっおファむルを䜜るずきはこうする, ず芚えおおけば良い.

• O WRONLY で, filename を曞き蟌みモヌドで開く事, • O CREAT で, filename がなければ䜜る事, • O TRUNC で, filename が存圚しおいおも䞀床空にする (䞀から䜜り盎す) 事, • 最埌の 0644 は, もしこれで filename が䜜られた堎合, 読み曞き暩限を誰に䞎えるかずいう事を指定しおい るが, ずりあえずこの実隓では 0644 以倖は䜿わない. このパタヌンはよく䜿うので, 䞊ず同じ事をする creat ずいうシステムコヌルもある.

それを䜿っお䞊蚘を

creat(filename, 0644) ず曞いおも良い. ステップ 2: 曞きたいデヌタを配列に準備する : 実際に曞きたいデヌタを䜜るずころだから, もちろんプログラムによっ お千差䞇別である. 以䞋はあくたで䞀䟋.

unsigned char data[N]; data[0] = . . . ; data[1] = . . . ; ... ステップ 3: デヌタを曞く :

write(fd, data, N); これで, data[0], data[1], . . . , data[N−1] の N バむトが filename に曞き蟌たれる. 䞀般に, write(fd, a, n) は, ア ドレス a から始たる最倧 n バむトを, fd (が衚しおいるファむル) に曞き蟌む, ずいう事である. ただし, 芁求した N バむトすべおが曞き蟌たれるずは限らず, それ未満のデヌタ (1 バむト以䞊) だけが曞き蟌たれ る事もあるので泚意が必芁である. 実際に曞き蟌たれたバむト数が返り倀になる. 倱敗した堎合は䜕が返るか? き


ちんずマニュアルで調べよ (第 1 ç« , 5. 節参照). もちろん, write は奜きなだけ繰り返し呌び出しおよい. その堎 合, write を呌び出した順に, ファむル内にデヌタが順に栌玍される. ステップ 4: ファむルを閉じる :

close(fd); これ以䞊 write でデヌタを曞き蟌む事はできなくなる. たず open ずいうシステムコヌルを呌ぶず, そのファむルに曞いおよい蚌しずしお, æ•Žæ•° (ファむルディスクリプタず 呌ばれる) が返される. あずは個々の読み曞きや, 䜿い終えた埌ファむルを閉じる際に, ここで返された倀を枡す.

open ず write の関係は, ディズニヌランドのなんずかパスポヌトず, 乗り物の関係に䌌おいる. 䞀床刞売り堎でパス ポヌトを買い (open), それを手にしたら個々の乗り物に乗る (write する) ずきはそのパスポヌトを芋せれば (fd を匕数 に枡せば) よい. ファむル入出力に限らず, このようなパタヌン—最初の呌び出しで「䜕か」が返されお, その返された 「䜕か」をその埌呌び出す関数に枡す—はいろいろな堎面で珟れる.

準備課題 2.1 ファむルの第 0 バむト目に 0, 第 1 バむト目に 1, . . . , 第 255 バむト目に 255, が入っおいるような, 256 バむトのファむル my data を䜜る C プログラム mk data.c を䜜れ. mk data.c は, open, write, close を甚いおファ むル my data を䜜成する.

そのプログラムを無事コンパむルするには, いく぀かのヘッダファむル (.h で終わるファむル) を#include する指瀺 を曞かなくおはならない (#include <????.h>). どのファむルを#include すればよいのかを, man コマンドで調べ お, 正しくコンパむルせよ (第 1 ç« , 5. 節参照). 以降も, C 蚀語で提䟛されおいる関数を䜿うには, あるヘッダファむル を#include しなければならない, ずいう堎合がしばしば珟れるが, 䞀々断らない.

準備課題 2.2 mk data.c が完成し, 無事 my data ができたら, ls コマンド (-l オプション) を甚いお, 実際にファむル が 256 バむトである事を確認せよ. たた, そのファむルを cat コマンドや Emacs で開いお, どんな颚に芋えるか, 芋お みよ.

準備課題 2.3

• 第 0 バむト目に 228 • 第 1 バむト目に 186 • 第 2 バむト目に 186 • 第 3 バむト目に 229 • 第 4 バむト目に 191 • 第 5 バむト目に 151 ずいう, 6 バむトからなるファむル hitoshi を䜜るプログラム hitoshi.c を䜜り, 同じ事をしおみよ.

泚意:

䟋えば最初の問題に察しお以䞋のような結果を期埅しおいるのではない.

012345678910111213...253254255


同様に 2 個目の問題に察しお, 以䞋のような結果を期埅しおいるのではない.

228186186229191151 蚀い換えればここで蚀う「第 0 バむト目に 0」ずいうのは, あくたで「敎数の 0」, あえお 2 進数で曞けば 00000000 ず いう 8 ビットの事を蚀っおいるのであっお, 文字の’0’ の事を蚀っおいるのではない. この話がよく分からないず感じたら以䞋の段萜を読んで, ファむルの䞭身に䜕が栌玍されおいるのかに぀いお理解を しおおいおほしい.

すべおのファむルは, バむト列 (ビット列) に過ぎない

(1) ファむルは蚀うたでもなくデヌタを栌玍した物である. (2) そのデヌタずは, バむトがずらずらず䞊んだ物である. (3) 1 バむトは 8 bit の事であったから, 1 バむトは 256 通りの倀を区別できる. (4) それを 0 から 255 たでの数字だず思えば, 芁するにあらゆるファむルの䞭身は, 「0 . . . 255 たでのどれかの数字」がずらずらず䞊んでいる 物なのである. もう少し具䜓的に蚀えば, オペレヌティングシステムがプログラムに察しお提䟛しおいるファむルの入出力機胜が,

• このファむルから X バむト読たせお䞋さい ⇒ 0, . . . , 255 たでの数字が X 個䞊んだ物が返っおくる • 0, . . . , 255 たでの数字が X 個䞊んだ物を枡しお, このファむルに X バむト曞かせおください ⇒ それをファむル に曞いおくれる ずいうだけの物であっお, すべおはこれを基本ずしおいる, ずいう事である. 蚀われるたでもない圓たり前の話ず映るかもしれないが, これたでファむルの読み曞きはテキスト (文字) しかやった 事がない, ずいう人は混乱の元ずなるかもしれないのでよく泚意しおほしい. ずもかくわかっおおくべき事は, ファむルに栌玍されおいる物は (どんなファむルであっおも), 「0 . . . 255 たでのど れかの数字」がずらずらず䞊んだ物であり, ファむルの䞭にそれ以倖の物—「文字」や絵—などずいう物はない. この 様に蚀うず, いやちょっず埅お, 先孊期習った fprintf 関数で,

fprintf(fp, ”hello\n”); のように曞けばファむルに, hello ずいう文字列が曞かれ, それぱディタでそのファむルを開けば確認できるではない か, これは「文字」を曞いおいるではないのか? ずいう疑問を持぀人もいるず思う. しかし実は, 䞊蚘ず「たるっきり同 じファむル」は, 以䞋のようにしおも䜜る事ができるのである.

unsigned char data[6]; data[0] = 104; data[1] = 101; data[2] = 108; data[3] = 108; data[4] = 111; date[5] = 10; write(fd, data, 6); この様にしおできた二぀のファむルはたるっきり同じである. この意味においお, すべおのファむルはバむト列に過ぎな い, それ以倖の物は入っおいない, ずいう事なのである.


バむト列に過ぎないデヌタが, ゚ディタで文字ずしお衚瀺されたり, 画像ずしお衚瀺されたり, 音楜ずしお流れたりす るのは, すべお, プログラムが「ある玄束 (笊号化方匏. ファむルフォヌマット)」にしたがっおバむト列を解釈しおいる おかげ, ずいう事である. 笊号化方匏のうち最も単玔で, お銎染みの物は, 英数字を笊号化した, ASCII 笊号ずいう物で, 䟋えば a ずいう文字に

97, b に 98, c に 99, . . . , を割り圓おおいる. この他「改行」に 10 を割り圓おおいる. だから䞊述の, hello\n は, 104, 101, 108, 108, 111, 10 ずいう䞊びになる. 最初の問題で Emacs でファむルを開いたずきに, 䞀郚に abcdefg. . . のよう な芋慣れた文字が出珟した事ず思うが, それはこのような事情による. 他にも, 挢字には挢字のための, 音には音のため の, 静止画には静止画のための, 動画には動画のための笊号化があるのである. くどくどず述べおきたが, 芁するにファむルから入力しおきたデヌタが 10, 20, 30, 40 ずいうバむト列である事ず, 文 字列”10 20 30 40”であるずいう事は倧違いで, それをうっかり勘違いしおいるず, 今埌音声などのデヌタを扱うずきに 意味䞍明な事になるから泚意せよずいう事である. ちなみに埌者の文字列 (”10 20 30 40”) はバむト列ずしおは, 49, 48,

32, 50, 48, 32, 51, 48, 32, 52, 48 である. どんなデヌタもバむト列だず今䞀床匷調するために断っおおく. 1.2 od コマンド ファむルにどのような「バむト列」が入っおいるのかをありのたた芋せおくれるコマンドがある.

$ od -t u1 ファむル名

のように䜿う.

準備課題 2.4 od コマンドを甚いお, my data を衚瀺しお, 予期した通りの結果になっおいる事を確認せよ. hitoshi に぀いおも同じ事をしおみよ.

od は, -t コマンド (t は type の意味) で, バむト列の「解釈の方法」を様々に倉えお, 同じファむルを衚瀺する事がで きる. 䞊蚘で甚いた-t u1 は, ファむルをバむトの䞊びずみなし, か぀個々の 1 バむトを unsigned ずみなしお衚瀺せよ ずいう意味である. ぀たり, 個々のバむトを 0, . . . , 255 の敎数ずみなせ, ずいう意味である. これが, -t d1 なら, 個々 のバむトを笊号付き敎数ずみなす— −128, . . . , 127 の敎数ずみなす—ずいう意味になる. -t u2 なら 2 バむトず぀を たずめお 16 ビットの敎数 (0, . . . , 65535) ずみなす. もう分かるず思うがこれらはすべお, 「同じバむト列を」芋おいる に過ぎない. そのバむト列を「どう解釈するか」その方法に色々あるずいう事の䟋に過ぎない.

od コマンドは, ファむルにデヌタを曞いた぀もりなのに結果が思うようにならない, ずか, ファむルからデヌタを入力 しおいる぀もりなのに思うようにならない, などのずき, 蚺断ツヌルずしお有甚である.

1.3 コマンドラむン匕数 ほずんどの実甚的なプログラムは, ちょうど od コマンドの䟋で芋たように, 扱うファむル名や, 動䜜を倉えるための オプションをコマンドラむン匕数ずしお受け取る. 我々が最初の 2 問で䜜ったように, どんな堎合でも my data ずいう 名前のファむルを䜜る, などずいう頑固な事にはなっおいないのが普通である. ここではそのようなプログラムを C で䜜るずきのやり方を説明する. 難しい事はなく, コマンドラむンで䞎えた匕数 を, 自分のプログラムの䞭で「受け取る」方法を理解すれば良いだけである.

(1) コマンドラむンで䞎えた文字列は, main 関数の匕数ずしお枡される. (2) main 関数には二぀の匕数が枡されおおり, それを受けずるには以䞋のようにする. main(int argc, char ** argv) 倉数名はどうでもよく, 型 (int ず char **) ずその順番が重芁である.

(3) 第䞀匕数 (argc) には, コマンドラむンで䞎えた文字列の数 (コマンド名を含む) が入り, argv にはそれらの文字列 の配列が入る.

(4) 䟋えば,


$ ./mk data foo ずいうコマンドラむンに察しおは,

• argv[0] = ”./mk data” • argv[1] = ”foo” が栌玍され, したがっお, argc = 2 ずなる (芁するに, argv の芁玠数を䞎えおいるのが argc).

準備課題 2.5 ./mk data コマンドをもう少しマシな物にするために, デヌタを曞き出すべきファむル名をコマンドラ むンから受け取るようにせよ.

1.4 ゚ラヌ怜査 ファむルを䜜成する際の基本フォヌムが

int fd = open(filename, O WRONLY | O CREAT | O TRUNC, 0644); であるず述べたが, 実はこれでは足りない. 実際にファむルを開けたかどうかを怜査しなくおはならない. 䟋えば開こう ずしたファむルが, 曞き蟌み犁止 (䞍可) なディレクトリにあるかもしれず, そのような堎合はファむルを開く事はでき ない. 埌にファむルを読み蟌む緎習をするが, その際にタむポなどで, 存圚しないファむルを読もうずすれば, もちろん ファむルを開く事はできない. 埌の週でネットワヌクプログラミングを行うが, ネットワヌクではその他にも様々な゚ ラヌの芁因 (蚭定ミス, 通信盞手がいない, ネットワヌク自䜓が䞍調, など) がある. ラむブラリ関数の䜿い方を間違っお いる, ずいう事だっおある. 教蚓は, ラむブラリ関数などを呌び出したら, それが成功したか吊かを必ず怜査しなくおはならない, ずいう事である. その調べ方は関数によっお違うが, たいがいの堎合, 返り倀で成功・倱敗が区別できるようになっおいる. 䟋えば open ずいう関数は, 開くのに倱敗するず, −1 を返すず決められおいる. したがっお簡単な基本フォヌムは以䞋のようになる.

int fd = open(filename, O WRONLY | O CREAT | O TRUNC, 0644); if (fd == −1) { /* ゚ラヌ時の凊理 */ } 「゚ラヌ時の凊理」ず蚀っおも具䜓的には䜕をすれば良いのだろうか? 倚くのラむブラリ関数は, ゚ラヌを返した盎埌に,

perror ずいう関数を呌び出すず, その原因を衚瀺しおくれる. したがっお完党な基本フォヌムは以䞋のようになる. int fd = open(filename, O WRONLY | O CREAT | O TRUNC, 0644); if (fd == −1) { perror(”open”); exit(1); } exit(1) は, プログラムを終了する関数である. したがっおこのプログラムは, ファむルを開くのに倱敗するず, その理由 を短く述べお終了するようになる. ここで perror や exit などの新しい関数が出おきた. man を䜿っお#include すべきヘッダファむルを入れる事を忘れ ずに.

準備課題 2.6 䞊蚘のような゚ラヌ凊理を斜したプログラム mk data2.c を䜜り, open の匕数に次の入力を䞎えお起動 しおみよ. どんな゚ラヌメッセヌゞが出力されるか.

• /foo • bar/baz (ここで, bar は存圚しないディレクトリずする)

もし, ラむブラリ関数を呌び出す床にこれをやるのが面倒ずいう人は,


void die(char * s) { perror(s); exit(1); } ずいう関数を䜜っおおき, 倱敗したらこれを呌び出すようにすれば倚少楜になる.

なぜこれが重芁か?

この「基本フォヌム」を芋お, 「なんだ, 結局倱敗したらただ終了するだけなのか」ず, ガクッず

くるずずもに, 「倱敗は倱敗, どうせ倱敗するんだったら䜕が起きおも同じ. ゚ラヌメッセヌゞなんか衚瀺したっお, 慰 めにしかならない」ず思ったそこのキミ, その考えは即刻改めなくおはならない. ゚ラヌ怜査の䞀番の目的は, ゚ラヌが起きたらそのたた先ぞ進たない ずいう事である. それは, ゚ラヌが起きたたた凊理を先ぞ進めるず, プログラムはもっずわけの分からない挙動を瀺すか らである. 仮に䞊のプログラムで, 䜕かの理由で open が倱敗したずしおも, プログラムの凊理自䜓は先ぞ進んでしたう. そしお, write システムコヌルたで到達する. もちろんファむルは開かれおいないのだから実際にデヌタがかけるはずは ない. そこで write システムコヌルが倱敗した事も怜査をせずに先ぞ進めば, 凊理が倱敗しおいるのに気づかないたたど んどん先ぞ進み, プログラムが䞀芋正垞に終了するように芋えるずころたで進む事もありうる. にもかかわらずファむル ができおいないずか, ファむルが最初から存圚しおいた堎合, その䞭身が曞き換わっおいない, などの意味䞍明な状態に 陥る. たずえ話ずしおは, 瀟保庁が幎金蚘録挏れ問題を長幎攟眮したために, 今や䜕がどうなっおいるのかさっぱり分からな い状態になっおいる, ずいうのず同じである. 䜕事も最初に間違いが起きたずきに気づいお行動を起こすべきなのである.

準備課題 2.7 write の挙動ず, 返り倀を man コマンドで調べおみお「曞き蟌みが成功した」事を確認するにはどのよ うな怜査を入れればいいか, 考え, 以降実践せよ.

゚ラヌ怜査を省略しおしたいたくなる気持ちは以䞋のような物だろう.

• 自分はただプログラミングが䞍慣れで, 数行曞けばすぐに間違える状態である. そんな時にプログラムの行数を増 やしたくない.

• ゚ラヌが起きたずきにする事はどうせ぀たらない事なので, あたり考えたくない. 繰り返しになるが, ここで実践せよず蚀っおいるのは倧げさな, ゚ラヌからの回埩 (ナヌザにファむル名を聞き盎すなど) などではなく,

if (. . . 倱敗 . . . ) { perror(”なにか䞀蚀”); exit(1); } の「1 行」を加えるずいう単玔か぀決たりきった事なので, 䞊蚘の心配は圓たらない. 倧しお考える必芁もなく, プログ ラムの行数も 1 行増えるだけ. にもかかわらず, それがプログラムを完成させるたでの時間を短瞮するのである. 「急が ば回れ」の粟神なのだ, ず思っお, 必ず, ゚ラヌを怜査する—ずいうよりも, 「成功を確認せずに先ぞ進たない」—事を 癖にしおほしい. だからやるべきは, 「゚ラヌ怜査」ずいうよりも「成功確認」ず蚀うべきである. 石橋を叩いお枡る, ずいうこずわざもある. 実を蚀うずファむル入出力くらいならこれを守らずずも䜕ずかなるのかもしれないが, 通信がからんでくるず, 蚭定間 違いや, 通信媒䜓, 通信盞手の䞍調による, 「䞍可避な゚ラヌ」ずいう物がたくさん出おくる. その時に「誰が悪いのか」 をすぐに突き止める手段ずしお, 垞に, 「自分は悪くない」ずいう事を確認しながら先ぞ進む事が必須になっおくる.


1.5 読 み 蟌 み 次にファむルからデヌタを読み蟌む緎習をしおみよう. 基本フォヌムは以䞋のようになる. ファむルを開く :

fd = open(filename, O RDONLY); O RDONLY で, ファむルを読み蟌みのために開く事を指定しおいる. もちろん盎埌の゚ラヌ怜査はするこず. 今 埌の説明では䞀々断らない. デヌタを読み蟌むための領域を配列ずしお準備する : 䟋えば,

unsigned char data[N]; デヌタを読む :

n = read(fd, data, N); これで, data[0], data[1], . . . , data[N−1] に, 読み蟌たれたデヌタが栌玍される.

read(fd, a, n) は, fd (が衚しおいるファむル) から最倧 n バむト読み出し, それをアドレス a から始たる堎所に曞 き蟌む. ただし write ず同様, N バむトぎったり読み出されるずは限らず, それ未満のデヌタだけが読たれる事もあるので 泚意が必芁である. 実際に読たれたバむト数が返り倀になる. 倱敗した堎合は䜕が返るか? もちろんマニュアルで 調べよ. もちろん, read は奜きなだけ繰り返し呌び出しおよい. その堎合, read を呌び出した順に, ファむル内の デヌタが順に読み出される. ファむルを閉じる :

close(fd); これ以䞊 read でデヌタを読み蟌む事はできなくなる.

泚: ファむルの「終わり」たで読むには?

n = read(fd, data, N); は, 最倧 N バむトのデヌタをファむルから読み蟌もうずする. この時, 実際に読たれたデヌタのバむト数 (1 以䞊) が, 返 り倀ずしお返される. ゚ラヌの堎合は −1 が返される. ファむルの終わりたで読みきっお, もうデヌタがないずいうずき は 0 が返される. これが, 「゚ラヌもなく, ファむルが最埌たで読めた」ずいう事の蚌ずなる. 結局, 「ファむルの終わ りたで読む」基本フォヌムは以䞋の通り.

while (1) { n = read(fd, data, N ); if (n == −1) { perror(”read”); exit(1); } if (n == 0) break;

# ファむルの終わり

. . . data に䜕か凊理をする . . . } もちろん䜕バむト䞀床に読むか (䞊蚘の N ) は自由である. ファむルの終わりを通垞その名の通り, END OF FILE ず呌び, EOF ず曞く. ファむルを読もうずしお, EOF が怜出 された (䟋えば, read が 0 を返した) 堎合, 比喩的な衚珟ずしお, 「EOF を読んだ」などずいう事がある. もちろん実際 には, デヌタを読んだわけではない. 「EOF が返された」などずいう事もある. 実際には 0 が返っおいるのに. これら は比喩的な衚珟です.


本課題 2.8 コマンドラむンで指定されたファむルを open で開き, 䜕バむト目が䜕ずいうバむトだったかを, 2 カラム で (第䞀カラムがバむト数 (最初のバむトを 0 バむト目ずする), 第二カラムがその倀) 衚瀺するプログラム read data.c を曞け. 䟋えばこのプログラムを䜜っお, 先ほど䜜った my data を読たせれば圓然の事ながら,

00 11 22 ... 254 254 255 255 のように衚瀺されるべきである. それを確認せよ.

これはたさしく, od コマンド (を, 単機胜にしお出力圢匏を倉えた物) を䜜っおいる事になる. 泚: 読み曞き同時 open

ファむルを曞き蟌みず読み蟌み䞡方で開く必芁がある堎合は, O RDONLY, O WRONLY の

代わりに, O RDWR を甚いる.

int fd = open(filename, O RDWR | O CREAT | O TRUNC, 0644);

トピック: fopen/fprintf ず, open/read/write の関係

2 幎 4 孊期に, ファむル入出力を行うための関数ずしお,

fopen, fprintf, fscanf などの関数を習った事だろう. 䟋えば以䞋のように䜿う. FILE * fp = fopen(filename, ”wb”); if (fp == NULL) { perror(”fopen”); exit(1); } fprintf(fp, ”hello %s san!\n”, argv[1]); この機胜は C 蚀語の「ストリヌム入出力ラむブラリ」ず呌ばれおいる. いわばファむル入出力に最䜎でも二通りの流儀 (open/read/write を甚いる流儀ず, fopen/fprintf などを甚いる流儀) がある事になる. 䞡者の違いずしおたず最初に気づく, 関数名以倖の違いは, open が敎数を返すのに察しお, fopen は

FILE *ずいう構造䜓ぞのポむンタずいう物を返す事である. したがっお fopen で開いた結果の fp を, write/read など のシステムコヌルに (そのたた) 枡す事はできないし, 逆も同様である. 以前に open の返り倀がディズニヌランドのパスポヌトだず述べたが, このたずえ話を (無理やり) 続けるず, fopen の 返り倀は, ディズニヌランドたでの電車の切笊, ディズニヌランドパスポヌト, アンバサダヌホテル宿泊刞, ホテルず䌚 堎の埀埩バスなどがすべおセットになった, クヌポン刞のような物である. 実は open が「最も基本」的な機胜であり,

fopen も実際に open を䜿っお最終的にはファむルを開いおいる. そしお実際, FILE ずいうデヌタ䞭にはこっそりずファ むルディスクリプタ (open の返り倀) が含たれおいる (クヌポン刞の䞭の䞀枚に, ディズニヌランドのパスポヌトが含 たれおいるのず同じ). 同様に, fprintf などの機胜も, 適宜 write システムコヌルを甚いる事で実珟されおいる (乗り物 に乗るずきはクヌポン刞ではなく, 結局ディズニヌランドのパスポヌトを芋せる). fopen/fprintf などのラむブラリは,

open/read/write の呚りに, ファむル入出力をより䟿利にするためのおたけを色々くっ぀けた物ず思えば良い. • 䟋えば read/write システムコヌルは, 配列ずその長さ (バむト数) を指定した読み曞きしかできないプリミティブ な物だが, fprintf は䞊蚘のように文字列の䞭に, 別の文字列 (%s) や敎数の倀を文字列化した物 (%d) を埋め蟌む 機胜を持っおいる.

• 読み蟌みに関しおは, fgets(data, N, fp) のような, ファむルから 1 行読み蟌む (改行文字が珟れるたで読み蟌む) などの, よく䜿う物が甚意されおいる.

• read/write により近い物ずしお, fread/fwrite ずいう関数がある. 匕数などもよく䌌おいる.


たた, fopen ずその呚蟺の関数たちは, C 蚀語の暙準ラむブラリずしお芏定されおいる関数たちで, UNIX であろうず

Windows であろうず䜿える. 䞀方 open/read/write は UNIX のシステムコヌルであっお, 基本的には UNIX 固有の物 である, ずいう違いもある. こう聞くずじゃあなぜわざわざこの実隓では, プリミティブなシステムコヌルの方をわざわざ䜿わせるのか? ず疑問 に思うかもしれない. その理由はいく぀かある.

• fopen/fprintf/fwrite/fread なども結局はシステムコヌルを䜿っお実珟されおいる. そのため埌者の方が動䜜が単 刀盎入でわかりやすいずいう事がある. 䟋えば前者にはバッファリングずいう機胜があり, いく぀かの曞き蟌み芁 求をメモリ䞊に貯めおおき, 䞀回の write システムコヌルで曞きにいく, ずいうような事をする. したがっお曞き 蟌みを行った瞬間に出力が行われるずは限らず, 「音が思ったタむミングで鳎らない」などの問題を究明する時 の未知数が䞀぀増えおしたう.

• 今回の実隓で音の入出力をするに圓たっおは, fprintf が提䟛するような文字列を䟿利に入出力する機胜は䞍芁で, システムコヌルを甚いたからずいっお話が難しくなる事はあたりない.

2. 暙準入出力ずリダむレクト ファむルを読み曞きしたければ, open システムコヌルでファむルを開き, 返されたファむルディスクリプタを read/write システムコヌルに枡す, ずいうのが基本だが, UNIX では, open しなくおも「最初から開かれおいる」ファむルディス クリプタが存圚する. それが「暙準入出力」ずいう物で, 以䞋の 3 ぀である.

0 : 暙準入力ずよばれ, read システムコヌルを甚いお読み蟌む事ができる. これを読み蟌むず, 通垞はキヌボヌドからの 入力を読み蟌む事になる. 䟋えば以䞋はキヌボヌドから 10 バむト読み蟌む.

read(0, data, 10); 1 : 暙準出力ずよばれ, write システムコヌルを甚いお曞き蟌む事ができる. これに曞き蟌むず, 通垞は端末ぞ出力する 事になる. 䟋えば以䞋は端末にデヌタを 10 バむト曞き蟌む.

write(1, data, 10); 2 : 暙準゚ラヌ出力ずよばれ, write システムコヌルを甚いお曞き蟌む事ができる. これに曞き蟌むず, 通垞は端末ぞ出 力する事になる. 䟋えば以䞋は端末にデヌタを 10 バむト曞き蟌む.

write(2, data, 10); なぜ暙準出力ず, 暙準゚ラヌ出力の二぀が甚意されおいるのかは埌述. それぞれ, 同じ機胜の, ストリヌム入出力ラむブラリ版ずでも蚀うべき物が存圚しおおり, それぞれ, stdin, stdout, stderr ず衚蚘する (#include <stdio.h> する必芁がある). だから,

read(0, data, 10); は

fread(data, 1, 10, stdin); ず, ほが同じ意味.

write(1, data, 10); write(2, data, 10); はそれぞれ,


fwrite(data, 1, 10, stdout); fwrite(data, 1, 10, stderr); ずほが同じ意味になる. そしおよく䜿う fprintf も,

fprintf(stdout, ”hello %s san\n”, name); fprintf(stderr, ”hello %s san\n”, name); ず曞けばそれぞれ暙準出力, 暙準゚ラヌ出力に曞き蟌む事になる. そしお前者が普段よく甚いおいる printf に他ならな い. ぀たり䞊蚘の 1 行目は,

printf(”hello %s san\n”, name); ず同じ事である. そしお UNIX においおは, コマンドを起動する際にファむルのリダむレクト (redirect) ずいう衚蚘を甚いるず, それ らの入出力先を倉える (リダむレクトする) 事ができる.

./a.out > filename ずするず, 暙準出力を filename にした䞊でプログラムが起動される. したがっおプログラムが open や fopen を䞀切呌 び出さなくおも,

write(1, data, 10); fprintf(stdout, ”hello %s san\n”, name); printf(”hello %s san\n”, name); などは, filename ぞの曞き蟌みず同じ効果を持぀. 芁するに単玔なファむル入出力しか行わない (扱うファむルが 1 ぀だ けで, 起動時に決たるような物) プログラムであれば, open などを甚いお明瀺的にファむルを開く必芁がなくなるので ある.

> は, 暙準出力のみ, filename に送り蟌む. 暙準゚ラヌ出力を別のファむルに送り蟌みたければ, 2> ずいう衚蚘を甚 いる. ぀たり,

./a.out > filename 2> filename2 ずする. ただし倚くのプログラムはプログラムの通垞の出力を暙準出力に曞き, ゚ラヌメッセヌゞなどは暙準゚ラヌ出 力に曞く, ずいう慣習を守る. そのような慣習に沿ったプログラムは, 暙準出力だけを必芁に応じおリダむレクトし, ã‚š ラヌは垞に端末に衚瀺する (぀たりリダむレクトしない) ずいう䜿い方をするのが䟿利である.

3. デバむスファむルの入出力を甚いた音声入出力 Linux 環境 (本実隓甚に特別にあ぀らえた物ではなく, 暙準的な Linux 環境) では, 音デヌタを入出力するための, 驚 くほど単玔なむンタフェヌスが甚意されおいる. それは, “/dev/dsp” ずいう名前のファむルを通垞のファむルず同じむ ンタフェヌスで読み曞きすれば, それが音の入出力になっおいる, ずいう物である. 背景説明を埌回しにしおたずは以䞋 をやっおみよ. たずは音デヌタの入力.

準備課題 2.9 以䞋のいろいろなコマンドで, /dev/dsp を読んでみよ. ファむルが終了する事はないので, 適圓なずこ ろで, Ctrl-C で匷制終了させる.

$ od -t u1 /dev/dsp


$ ./read data /dev/dsp ./read data は, 問題 2.8 で䜜ったプログラムである.

おそらく, 127, 128, 129 付近の倀がずらずらず䞊ぶず思われる. コンピュヌタ内蔵のマむクが有効になっおいれば, ここで内蔵マむクに向かっお叫べば, 衚瀺されるデヌタに倉化が珟 れるだろう. 内蔵マむクではなく, マむク端子が有効になっおいれば, マむク端子に倖付けマむクを接続しおそのマむク に向かっお叫べば, 衚瀺されるデヌタに倉化が珟れるだろう. どちらかの方法で出力が倉化する事を確認しおみよ. もし かするずボリュヌムやミュヌトの蚭定等でどちらをやっおも倉化しない可胜性もあるが, それらの蚭定方法は埌で説明 する事にしお今は先ぞ進む. ずりあえずうたく行ったなら, 以䞋のようにしお/dev/dsp の䞭身を「そのたた」ファむルに保存すれば, 「録音噐」 が完成する.

$ cat /dev/dsp > data

# 数秒埅っお, 適圓なずころで Ctrl-C で終了させる

cat は, 「ファむルの䞭身を芋る物」ず思っおいるずこれ自䜓䞍思議に芋えるかもしれないが, cat ずは芁するに, 「ファ むルの䞭身をそのたた暙準出力に出すプログラム」である. 䞭では以䞋のような事が起こっおいるず想像すれば良い (説 明を簡略化するため, ゚ラヌ怜査などは省略しおいる).

int main(int argc, char * argv) { int fd = open(argv[1], O RDONLY); unsigned char data[1000]; while (1) { int n = read(fd, data, 1000); if (n == −1) { perror(”read”); exit(1); } if (n == 0) break; write(1, data, n); } return 0; } だから䞊のコマンドラむンは, /dev/dsp から読んできた物をそのたた, data に保存する, ずいう動䜜になる. 次は音デヌタの出力. 想像できるように, /dev/dsp ずいう名前のファむルに曞けば良い.

準備課題 2.10 以䞋のコマンドで, /dev/dsp に曞いおみよ.

$ cat data > /dev/dsp

ここで, data は先ほど䜜った物である. 先の実隓でもし, マむクが有効に機胜しおいそうであれば, これで先ほど自分 が叫んだ音が, 再生されるはずである. ただしこの堎合もボリュヌム蚭定などが関係しおくるので, きちんず蚭定方法を 理解するたではうたく行かなくおも深远いしなくおよい.

/dev/dsp が䜕者かを理解する

ずもかく/dev/dsp ずいうファむルを読み曞きするだけで, 音が入出力できおしたっ

た. これをずもかく玍埗しおしたえば本実隓を進めるには充分なのだが, 䞀方でこんな物を芋せられるず「ファむルずい うのはディスクに保存されたデヌタの事だず思っおいたのに, 読み蟌むずその時の音が出おくるなんおむミフ」ずいう


人もいるず思う (念のため, /dev/dsp を読んだずきに出おくるのは, 過去に/dev/dsp に曞かれた倀ずは党く関係がない. あくたでその時コンピュヌタに入力されおいる音である). コンピュヌタに音を入出力するには, ハヌドりェアレベルではコンピュヌタに装着されおいるサりンドカヌドず CPU の間で, デヌタのやりずりをする事になる. その詳现はサりンドカヌドごずに異なるが, オペレヌティングシステムずし おはそれらに察しお, あたりサりンドカヌドの機皮に䟝存しない, か぀簡易なプログラミングむンタフェヌス (API) を 提䟛したい.

UNIX においお, サりンドカヌドを扱う API ずしお策定されおいる物の䞀぀が, Open Sound System (OSS) ず呌ば れる API で, /dev/dsp ずいうファむル名を通しお, 音の入出力を行えるようにする, ずいう「決たり」もそこで仕様ず しお策定されおいる. そもそもデバむスにはサりンドカヌド, ハヌドディスクコントロヌラ, シリアル入出力, USB デバむスなど, いろいろな皮 類がある. UNIX では, それら様々なデバむスの皮類毎に, それず通信するための新しい関数矀 (䟋えば, open sound card,

read sound card, etc.) を発明せずに, (1) すべおのデバむスに「共通の」むンタフェヌス (open, read, write, close, etc.) を蚭ける. (2) open, read, write, close, etc. の操䜜にどう「反応するか」はデバむスごずに異なっおよい (それを決めおいるの は, デバむスドラむバず呌ばれるプログラム). ずいう慣習を䜜っおいる. そしお, それらを「普通の」ファむルの読み曞きのむンタフェヌスずも共通化しおしたった. 最埌の, 「デバむス」を読み曞きするむンタフェヌスず「普通のファむル」を読み曞きするむンタフェヌスを同じにす る必然性はないず感じるかもしれない. しかし, 共通化する事で普通のファむルもデバむスも党く同じように読み曞きで きるようになる. 埌に出おくる, ゜ケット (ネットワヌクずのやりずりをする API) も, UNIX においおは, 実はファむル ず共通の API で読み曞きする事もできる. よく考えおみるず, そもそも「普通のファむルの挙動 (曞いたデヌタが読たれる)」ずいう物自䜓, ハヌドディスクや

USB メモリなどの「2 次蚘憶装眮」ず呌ばれる—それ自䜓ずおも耇雑な—「デバむス」ずのやりずりを䞊手にやる事に よっおどうにか実珟されおいる非垞に高玚な機胜である. 普段ファむルをあたりに䜕気なく䜿っおいるので忘れおした いがちだが, コンピュヌタを䜜る䞊で, 普通のファむルの挙動が, その他のデバむスずのやりずりより簡単に, 自然に実 装できるずいうわけではないのである. だから, /dev/dsp が「普通のファむルず挙動が違う」ず蚀っお悩むのはあべこ べで, 「いろいろなデバむスが, 同じむンタフェヌスを持぀が, だがそれぞれ異なる挙動を瀺す」ずいう䞖界がたず先に ある (それが自然).

4. 音入出力の蚭定 音の録音や再生がうたく行っおいないず思ったら, サりンドカヌドの蚭定に問題がある可胜性がある. 録音, 内蔵マむ ク, 倖付けマむクが入力ずしお有効になっおいない, 録音レベルが䜎すぎる, ミュヌトが蚭定されおいる, などの問題が ある. 再生でも, 内蔵スピヌカ, 倖付けスピヌカが有効になっおいない, 再生レベル (ボリュヌム) が䜎すぎる, ミュヌト が蚭定されおいる, などの問題がありうる. 入力源 (マむク端子か内蔵マむクか), 出力先を遞んだり, 再生録音レベルを調節するためのアプリケヌションずしお, 「音量調節ツヌル」がある. パネルにスピヌカの絵をしたアむコンがあればそれである. なければ, 第 1 ç« , 8.1 節で説明 した芁領で远加せよ. その䞊で,

(1) パネル䞊のスピヌカの絵をしたアむコンを右クリック → 音量調節ツヌルを開く (2) 線集 → 蚭定や, ファむル → デバむスの倉曎, を開いお色々探る (図 G1.2.1). 具䜓的にどれを遞んだら良いのかは機皮によっおも異なるようで, あたり䞀般的な事は蚀えない. 実隓で貞し出しおいる 機皮でどれを遞んだら良いかは実隓 HP に蚘述する.

5. /dev/dsp のデヌタ圢匏 実際に/dev/dsp を読んだずきに読たれるデヌタや, /dev/dsp に音を鳎らしたいず思ったずきに曞き蟌むべきデヌタ の圢匏は非垞に単玔な物で, 音の波圢 (y = f (t)) を, 䞀定間隔で暙本化 (sampling) した倀がずらずらず䞊んでいるだけ の物—Linear Pulse Code Modulation (Linear PCM)—である (図 G1.2.2). 個々の暙本が䜕 bit の倀ずしお衚されるか (暙本化分解胜, sampling resolution), 1 秒間に幟぀の暙本をずるか (暙本


図 G1.2.1

図 G1.2.2

音量調節ツヌル

/dev/dsp のデヌタ圢匏 (Linear PCM)

化呚波数, sampling rate), 幟぀のチャネルをずるか (モノラルかステレオか), は蚭定によっお倉える事ができ, 可胜な 組み合わせや既定の蚭定もサりンドカヌドによっお異なる. 実隓環境での既定倀は以䞋のようになっおいる.

• 暙本化分解胜: 8bit (256 皮類の倀) • 暙本化呚波数: 8kHz (8000 暙本/秒) • チャネル数: 1 (モノラル) したがっお読み出されるデヌタのレヌトは 1 バむト/暙本 ×8000 暙本/秒 ×1 = 8000 バむト/秒ずなる.

1 ぀の暙本は 1 バむトで 256 皮類の倀をずり埗るが, これを笊号なし敎数, ぀たり 0 から 255 たでの倀ずしお読み, 128 を匕くずそれが各時点での振幅ずなる. だから, 入力がほずんどないずきは, 128 前埌の倀が読み出される事になる.

準備課題 2.11 cat ずリダむレクトを䜿っお/dev/dsp を 5 秒ほど読み出し, ファむルに保存せよ. そのファむルのサむ ズを芋お, 䞊蚘のデヌタレヌトず倧䜓あっおいる事を確認せよ. 逆に適圓な倧きさのファむルを cat コマンドで/dev/dsp に出力しおみお, 出力し終えるのにかかる時間がデヌタレヌトず倧䜓あっおいる事を確認せよ.


6. gnuplot /dev/dsp から読み出されるデヌタを波圢ずしお可芖化しおみよう. gnuplot は数匏で衚されたグラフや実隓デヌタ を, 可芖化 (グラフ衚瀺) する汎甚的なツヌルである. 奥が深いツヌルだがここではこの実隓で必芁な最䜎限の機胜を説 明する.

gnuplot に衚瀺させたいデヌタは, x 軞の倀を第䞀カラム目に, y 軞の倀を第二カラム目に曞いた行を, ずらずらず䞊 べただけの物である. 䟋えば,

00 11 24 39 4 16 5 25 6 36 7 49 8 64 のように. 倀はもちろん小数点付きの数字であっおも構わない. そのようなデヌタがファむル (a.txt ずする) ずしお準備できたら, 以䞋のようにする.

$ gnuplot (バナヌが衚瀺される) gnuplot> plot ”a.txt” gnuplot> これで窓が珟れ, グラフが衚瀺される (図 G1.2.3). 特に蚭定をしないず, 各デヌタが点ずしお衚瀺される. これを線で 結んで衚瀺するなど色々な蚭定が可胜だがこの実隓では䞍芁なので省略する.

gnuplot をたず立ち䞊げおから, plot コマンドを入力する代わりに, 以䞋のような内容のファむル (a.gpl ずする) plot ”a.txt” pause −1 を甚意しお,

$ gnuplot a.gpl ずしおもよい (pause −1 は, 改行が入力されるたで窓を衚瀺し続けるずいう指瀺で, これを曞かないず, 窓が䞀瞬衚瀺さ れおすぐに終了しおしたう).

/dev/dsp から読み出されたデヌタを衚瀺するにはどうしたら良いか? 課題 2.9 をクリアしおいれば䞀瞬である.

本課題 2.12 課題 2.9 においお read data を䜿っお埗た/dev/dsp の出力を, gnuplot で衚瀺しおみよ. なんずなくそ れらしい波圢が衚瀺されれば成功である. 次に, read data を少し倉曎しお, 暪軞が時間 (秒単䜍), 瞊軞は振幅 0 の時 が正しく y = 0 ずしお衚瀺されるようにしお (読み出されたデヌタから 128 を匕いお) みよ.

/dev/dsp のデヌタ圢匏が Linear PCM である事を実感するもう䞀぀の方法は, 人工的な波を出力しお, 音を生成しお みる事である.


図 G1.2.3

gnuplot

本課題 2.13 いろいろな呚波数の正匊波:

y = A sin (2πf t) を/dev/dsp に出力しおどのような音が聞こえるかを詊しおみよ. A (振幅), f (呚波数) をコマンドラむンから受け 取っお倉曎できるようにせよ. 文字列を浮動小数点に倉換するには, atof ずいう関数を䜿う (#include を忘れずに). あたり高い呚波数を出しお耳を痛めないように. f = 440Hz 付近, 小さめの A からスタヌトしおみよ.



第3日 党時間実習ずする. 遅れおいる人はこの時間で远い぀く. 䜙裕のある人は, 次回を予習する, 発展課題の構想を緎っお議 論する, などの時間ずしお䜿う.



第 4 日 デゞタル信号凊理 1. ね ら い 1.1 暙本化定理ず AD 倉換 時間的にも振幅的にも連続倀を有するアナログ信号は時間軞䞊での離散化暙本化振幅軞䞊での離散化量子 化を通しおデゞタル信号ぞず倉換される。有名なシャノンの暙本化定理は情報の欠萜無く AD 倉換Analog-Digital 倉換を行なうための「暙本化」に関する必芁十分条件を教えおくれる。ここでは故意に䞍適切に暙本化された゚ むリアスが混入した信号を生成し暙本化定理を実隓的に怜蚌する。

1.2 暙本化信号に察するフヌリ゚倉換プログラミングずそれを甚いた信号解析 時系列信号に察する最も広く知られた信号解析手法䞎えられた時系列信号を基本波の重み付け和ずしお衚珟する。即 ち基本波ぞず分解する手法であるフヌリ゚倉換を埩習し暙本化信号に察するフヌリ゚倉換である DFTDiscrete

Fourier Transformを C 蚀語で実装する。䜜成したプログラムを䜿甚し前節で甚いた゚むリアスが混入したサりン ドデヌタに察しおスペクトル解析を詊みる。

1.3 畳み蟌み挔算のプログラミングず FIR フィルタ 〜簡易サりンド゚フェクタの詊䜜〜 フィルタリングの基瀎ずなるむンパルス応答畳み蟌み挔算に぀いお埩習しこの挔算を C 蚀語で実装する。たた むンパルス応答が有限長のデゞタルフィルタを FIRFinite Impulse Responseフィルタず蚀うがこのフィルタを 䜿っお音響ホヌルの反響音゚コヌ効果をシミュレヌトする。自身の声を音響ホヌルでの歌声に倉換する。

2. 解

説

2.1 アナログ→デゞタル→アナログ倉換 地衚を䌝わる地震波目に飛び蟌む可芖光線耳に飛び蟌む音響信号錻を刺激する化孊物質など様々な情報は時系 列信号ずしお存圚するこずが倚い。通垞自然界に存圚する時系列信号は任意時刻においお倀を持ちたた芳枬信号の 振幅は連続倀を持぀。即ちアナログ信号である。アナログ信号に察しお暙本化量子化を経るこずでデゞタル信号は生 成される。信号のデゞタル化によりアナログの状態では困難であった凊理が可胜ずなった。デゞタル信号凊理である。

2 幎埌期の「信号解析基瀎」を孊んだ孊生は暙本化信号を元のアナログ信号に戻すための暙本化に関する必芁十分 条件を孊んだはずだ。アナログ信号が存圚する呚波数垯域が f ≀ Fa であった堎合暙本化呚波数 Fs を 2Fa 以䞊に蚭 定するこずが必芁である。䟋えば 5kHz たでの垯域に存圚するアナログ信号を暙本化するには10kHz 以䞊の暙本化呚 波数を遞べばよい。こうすれば暙本化信号から元のアナログ信号を完党に埩元できる1 。

2.2 䞍適切な暙本化ず゚むリアス 2Fa 以䞋の暙本化呚波数を甚いた堎合どのような珟象が起こるのだろうか䟋えばオヌケストラの挔奏をその たた 8kHz で暙本化するず䜕が起こるのだろうか数匏的な詳现は「信号解析基瀎」のノヌト及び参考図曞に譲るが 暙本化ずいう操䜜は本来の信号スペクトル X(ω) に察しおそれが呚波数軞䞊で繰り返し珟れるような人工的な成分 を生み出す図 G1.4.1 参照。暙本化呚波数の代わりに暙本化角呚波数 ωs が䜿われおいる2 。結局暙本化呚波数が 䞍適切に䜎い堎合図 G1.4.2 にあるように本来のスペクトルず暙本化によっお人工的に䜜られたスペクトルずが 重なっおしたう。これが゚むリアスである。オヌケストラの挔奏をそのたた 8kHz で暙本化するず゚むリアスが容 易に起きる。実隓ではこの゚むリアスをダりンサンプリングを通しお人工的に生成する。

2.3 アップサンプリングずダりンサンプリング 䞎えられたデゞタル・サりンドデヌタを本来のサンプリング呚波数ずは異なる呚波数で再生するこずを考えおみる。 䟋えば48kHz で AD したデヌタを8kHz で DA するダりンサンプリングにはどうすれば良いのか8kHz で AD 1 音楜 CD は 44.1kHz の暙本化16bit の量子化によるデゞタル信号敎数倀列である。これは「地球䞊の党おの音は 22.05kHz 以䞋の

垯域にしか存圚しおいない」こずを䞻匵しおいるものでは圓然ない。この仕様はそもそも「人間の耳が聞き取れる垯域可聎領域が 凡そ 20kHz たで」ずいう生理孊的事実を考慮しお決められた仕様である。䜆し䞭にはそれ以䞊を聞き分ける方々もいる。 2 デルタ関数 ÎŽ(t) を䜿った暙本化信号の衚珟及びそのフヌリ゚倉換に぀いおは数匏の展開を確認しおおくずよいだろう。


図 G1.4.1

サンプリングされた波圢の呚波数スペクトル䞊アナログ信号 x(t)䞋暙本化信号 xs (t)

図 G1.4.2

折り返し歪み゚むリアスの発生

したデヌタを 48kHz で DA するアップサンプリングにはどうしたらよいのかずいった問題である。 図 G1.4.3 に瀺すように48kHz で AD したデヌタを間匕けば6 サンプル毎に䞀぀取り䞊げる暙本化呚期的に は 8kHz になる。䞀方8kHz で AD したデヌタに察しお各サンプルの間に 5 ぀れロを挿入すれば暙本化呚期的には

48kHz ずなりか぀8kHz でのデゞタルむメヌゞず同䞀になる。これらをそのたた 8kHzあるいは 48kHzデヌ タずしお DA するずどのような音が聞こえるだろうか期埅した音ずは異なるはずである。デゞタル化暙本化の お䜜法を知らないずこのような誀った信号を生成するこずがあるので泚意が必芁である。

2.4 離散フヌリ゚倉換Discrete Fourier Transform, DFT 可芖光線音響信号などの時系列信号は䞀皮類の情報だけを運ぶ蚳では無い。䟋えば音声には䜕を喋ったのか誰 が喋ったのかどのように喋ったのかなど様々な情報が含たれおいる。぀たり耇数の独立した情報が䞀぀の時系列信 号に混入しおいるこずになる。どの情報が時系列信号のどの偎面に察応しおいるのか笊号化されおいるのかを 知ろうずする堎合その信号を「分析」し「分解」する必芁が生じる。このような堎合に垞套手段ずしお甚いられおい るのがフヌリ゚倉換に基づく呚波数解析である。以䞋フヌリ゚倉換を埩習する。 任意の呚期的なアナログ時系列信号 x(t)䜆し呚期を T ずするに察するフヌリ゚玚数展開は以䞋のようになる。

x(t) =

∞ a0 ∑ 2π + ) (an cos nω0 t + bn sin nω0 t) (ω0 = 2 T n=1

これをオむラヌの公匏ejΞ = cos Ξ + j sin Ξ を䜿っお曞き改めるず以䞋のように耇玠フヌリ゚玚数展開ずなる。

x(t) =

∞ ∑

cn ejnω0 t

n=−∞

(ω0 =

2π ), T

cn =

1 T

∫ T2

x(t)e−jnω0 t dt

− T2

ここで1) フヌリ゚玚数展開では x(t) は実関数であるが耇玠フヌリ゚玚数展開では x(t) は耇玠関数ずしおの扱いを

時間間匕き

図 G1.4.3

れロ詰め

時間間匕きのみによる䞍適切なダりンサンプリングずれロ詰めのみによる䞍適切なアップサンプリング


受ける点及び2) 埌者では呚波数が負の領域にたで拡匵されおいる点に泚意しお欲しい。たた耇玠フヌリ゚玚数展 開は基本波を ejnω0 t ずしお任意の x(t) を重み付き基本波の足し合わせに分解する操䜜である。 さお非呚期信号に察しおは䞊蚘の呚期信号に察する展開系を T → ∞ ぞず拡匵するこずで応甚するこずが可胜で ある。任意の耇玠アナログ信号 x(t) に察するフヌリ゚逆倉換フヌリ゚倉換は䞋蚘ずなる。

x(t) =

1 2π

∫ ∞

X(ω)ejωt dω,

∫ ∞ X(ω) =

−∞

jωt

ここでも同様角呚波数 ω の基本波 e

x(t)e−jωt dt

−∞

に察する重みが X(ω) である。和蚘号

∑

∫

が積分蚘号 になり正芏化

係数が付䞎されただけである。以䞊はアナログ信号 x(t) を基本波ずその重みに分解する数孊的ツヌルであるがこれ のデゞタル暙本化信号版が離散フヌリ゚倉換DFTである。暙本化呚期 Ts サンプル数 N の暙本化信号 xs (n) 即ち xs (n) = x(nTs )䜆し n = 0, ..., N − 1に察しお逆 DFTDFT は䞋蚘のように定矩される。 N −1

xs (n) =

2π 1 ∑ Xs (k)ej N kn , N

Xs (k) =

N −1 ∑

xs (n)e−j N kn 2π

(k = 0, ..., N − 1)

n=0

k=0

2pi

ここでも同様基本波を ej N kn ずしおn が時間k が呚波数に察応重みを付けお足し合わせおいるだけである。 なお分析実時間長は N Ts であるためk は NkTs [Hz] の呚波数に察応する。逆 DFT 及び DFT をオむラヌの公匏を 䜿っお䞉角関数衚蚘に戻すず䞋蚘のようになる。xs (n) の実郚虚郚を xrs (n)xis (n) ずしお N −1

xrs (n) =

1 ∑ r Xs (k) cos(2πnk/N ) − Xsi (k) sin(2πnk/N ) N k=0

xis (n) =

1 N

N −1 ∑

Xsr (k) sin(2πnk/N ) + Xsi (k) cos(2πnk/N )

k=0

でありDFT は

Xsr (k) =

N −1 ∑

xrs (n) cos(2πnk/N ) + xis (n) sin(2πnk/N )

n=0

Xsi (k) =

N −1 ∑

−xrs (n) sin(2πnk/N ) + xis (n) cos(2πnk/N )

n=0

ずなる。ここたで来るずC 蚀語でプログラミングできるだろう。任意の耇玠系列信号 xrs [], xis [] を入力ずしそれを耇

DFT ずなる。 玠系列信号 Xsr [], Xsi [] に倉換する関数を曞けばそれが C 蚀語で実装した √ ある区間の信号を DFT し振幅スペクトルlog |Xs (k)| = log

スペクトル∠Xs (k) = arctan

(

Xsi (k) Xsr (k)

)

2

Xsr 2 (k) + Xsi (k)k は呚波数に盞圓や䜍盞

を取り出すこずを呚波数解析ず蚀う。蚀い換えれば各呚波数の振幅の倀や

䜍盞の倀を䜿っお様々な情報が衚珟される振幅や䜍盞に情報が埋め蟌たれるこずが倚いずいうこずである3 。再 掲するが k = 0, ..., N − 1 は呚波数で蚀えば 0, ..., NN−1 T1s (= NN−1 Fs ) に盞圓する。実隓で確認しお欲しいがDFT を 行なうず振幅スペクトルや䜍盞スペクトルはナむキスト呚波数 Fs /2 を軞にした察称圢になる図 G1.4.1 参照。

2.5 畳み蟌み挔算ず FIRFinite Impulse Responseフィルタ 屋倖である歌を誰かに歌っおもらう。ホヌルで同じ歌を同じように歌っおもらう。党く同じように歌っおもらっおも 声の響き方聞こえ方は違っおくる。歌い手の口を出た時は同じように空気が震動しおいたはずなのに耳に届いおい る空気の震動パタヌンは屋倖ずホヌルでは倧きく異なっおくる。目の前にいる人の声を聞く。今床は携垯を通しお聞 く。圓然盞手は同じように喋っおいおも聞こえお来る声空気の震動パタヌンは盎接聞くのず携垯を通した堎合ず では異なっおくる。このような信号の倉容はその信号にフィルタがかかるこずで起こる珟象である。ここではデゞ タル信号凊理におけるフィルタリングの基瀎であるFIR フィルタに぀いお解説する4 。 単䜍むンパルス信号を

{ ÎŽ(n) =

1

(n = 0)

0

(n 6= 0)

3 ちなみに人間の耳は近䌌的に蚀っお音響信号に察する倩然の「察数振幅スペクトル抜出噚」ずしお機胜しおいる。 4 以䞋では添字の s は省いおいる。


実線盎接音 波線間接音

"impulse_response.dat"

B A 0

図 G1.4.4

500

1000

1500 2000 2500 sample index

3000

3500

4000

反射波による゚コヌ間接音の発生ず音響ホヌルのむンパルス応答

ずするず任意のデゞタル暙本化信号 x(n)(n = 0, 1, 2, ...) は䞋蚘のように衚珟される。

x(n) =

∞ ∑

x(k)ή(n − k) (n = 0, 1, 2, ...)

k=0

぀たり単䜍むンパルスを基本波ずしおその重み付け和ずしお x(n) を衚珟しおいるだけである。ここで単䜍むンパ ルス ÎŽ(n) をフィルタ H に通した時にその出力応答が h(n)(n = 0, 1, 2, ...) ずなったずする。むンパルスに察する 出力応答ずいう意味でこの h(n) を単䜍むンパルス応答ず呌ぶ。フィルタ入力を i(n)出力を o(n) ず曞けば

i(n) = ÎŽ(n) → フィルタ H → o(n) = h(n) (n = 0, 1, 2, ...) ずなる。さお x(n) をこのフィルタに通した堎合の応答はどのようになるだろうか時刻 k だけ移動した単䜍むンパル ス ÎŽ(n − k) に察する応答は圓然h(n − k) ずなる。よっお

i(n) = x(n) =

∞ ∑

x(k)ÎŽ(n − k) → フィルタ H → o(n) =

k=0

∞ ∑

x(k)h(n − k) =

k=0

∞ ∑

i(k)h(n − k)

(n = 0, 1, 2, ...)

k=0

ずなるのは自明だろう。これがデゞタル信号における「畳み蟌み」挔算である。畳み蟌み挔算は察称性があるため

o(n) =

∞ ∑ k=0

i(k)h(n − k) =

∞ ∑

h(k)i(n − k)

(n = 0, 1, 2, ...)

k=0

が成立する。結局フィルタの単䜍むンパルス応答h(n)が既知ずなれば劂䜕なる信号を入力したずしおもその 出力はプログラミングできるこずになる。さおh(n) が有限項で打ち切りずなったずする。即ち

h(n) = 0

(n > n0 )

である。この堎合のフィルタをむンパルス応答が有限ずいう意味でFinite Impulse Response (FIR) フィルタず呌ぶ。

2.6 反射波による゚コヌ効果の FIR フィルタを甚いたモデル化ず実装 音響ホヌルのステヌゞにおいお単䜍むンパルスを発生させるこずを考えおみる。䟋えば手を口の前でパンずたた いおみる図 G1.4.4 巊䞭の A。するずどうなるか。図に瀺すように盎接耳図䞭の Bに届く経路もあるし盎 接音実線様々な壁に反射しお時間差を䌎っお耳に届く経路もある間接音砎線。圓然盎接音は枛衰が小さい し間接音は枛衰が倧きい。たた間接音の経路は無数にある。その結果パンずいうむンパルス生成に察しお耳に 届いた信号が図 G1.4.4 右ずなったずする。このむンパルス応答 h(n) が䞎えられればそのホヌルにおいお自分の 口で発生させた音がどのように聞こえるかは簡単にプログラミングで確認できるこずになる。䟋えばカヌネギヌホヌ ルで歌うず自分の声はどのように聞こえるかはh(n) さえあればプログラミングできるずいうこずになる。 この反射波による゚コヌ効果に察する簡単なモデルを考える。今むンパルス応答が図 G1.4.5 のようなものであっ たずする。この堎合盎接音によるむンパルスず数個の間接音によるむンパルスのみを考える。盎接音から次の反射 音たでの遅延を初期遅延dp その埌の遅延を高次遅延ds ず呌ぶこずにする。たた盎接音の振幅を a(0)反射 波による間接音の振幅を a(k)(k > 0) ずする。ここで以䞋の関係匏を仮定する。

  (k = 0)   1 a(k) = ap (k = 1)    a a(k − 1) (k > 1) s


振 幅

盎接音

間接音

a(0)

a(1) a(2)

dp

0 図 G1.4.5

ds

è¡š 倧小の音響ホヌル甚のパラメヌタ倀

a(3) a(4) a(5)

ds

ds

ds

パラメヌタ

小ホヌル

倧ホヌル

ap as dp ds K

0.25 0.4 50 [ms] 25 [ms] 10

0.25 0.4 100 [ms] 50 [ms] 10

時間

盎接音及び間接音で構成されるむンパルス応答のモ

デル

n1

の時間遅れ

の時間遅れ

z −n1

a1

z −n2

a2

−n3

a3

:

:

+

z −nK

aK

Y (z)

X(z)

+

X(z) z

図 G1.4.6

z −1 z −1

nK z −1

Y (z)

z −1 a1

z −1

z −1 a3

a2

aK

図 G1.4.4 に察する 2 通りのフィルタ構成

    0 d(k) =

(k = 0)

dp (k = 1)    d + d(k − 1) (k > 1) s

ap は初期反射音の枛衰率as は高次反射音の枛衰率である。このようなモデルを仮定すれば入力 i(n) に察する出力 o(n) は次のように衚珟できる。FIR フィルタの䞀皮である。 o(n) =

K ∑

a(k)i(n − d(k))

k=0

このようなモデルを仮定すれば幟぀かのパラメヌタ倀を操䜜するこずで様々な゚コヌ効果を暡擬するこずができ る。䟋えば衚に瀺す倀を䜿うこずで倧小の音響ホヌルを暡擬するこずができる。 なお図 G1.4.4 に瀺した反射波による音響効果は基本的にはある単䞀の音源からの音波が異なる時間遅れでか ぀異なる係数倍されお芳枬者に届くこずが原理ずなっおいる。これをそのたたデゞタルフィルタ的に解釈すれば5  図 G1.4.6 巊ずなる。これを図 G1.4.6 右のように考えれば兞型的な FIR フィルタであるこずが分かるだろう。

3. 実隓課題ず怜蚎事項 本課題に぀いおも web ペヌゞが甚意されおいる。各皮デヌタやサンプルプログラムがダりンロヌドできる。たた理 解の助けずなるようなリンクを玹介しおいるので必芁に応じお参照するず良い。なお目安ずしお2 幎埌期に「信号 解析基瀎」の単䜍を取埗した者は党課題に臚んで欲しい。それ以倖の者は課題 4.4 たでは遂行しお欲しい。

本課題 4.1 同䞀音源に察しお異なる暙本化呚波数で暙本化したサりンドデヌタの聎取 暙本化呚波数 Fs が䞎えられこれを甚いお任意の任意の音源を AD 倉換する堎合゚むリアスが生じないよう に事前に Fs /2[Hz] 以䞊の情報を削陀するLow Pass Filtering, LPF必芁がある。数皮類の音源に察しお適切 な LPF を斜した䞊で48kHz, 16kHz, 8kHz の暙本化16bit の量子化を斜しお埗られたデゞタル・サりンドデヌタ を実隓 web に掲茉しおいる。これを聎取しお暙本化呚波数の違いによる聞こえ方の違いに぀いお音源間で比范せ 5 乗算加算時間遅れの 3 ぀の挔算で信号の凊理を考えればずいうこずである


よ。デゞタル・サりンドデヌタ再生プログラムは web にある。 怜蚎事項

音源によっお「暙本化呚波数の違いによる聞こえの差」に差があるのは䜕故かを考えよ。

本課題 4.2 䞍適切に暙本化された䞍適切にアップダりンサンプリングされたサりンドデヌタの聎取 図 G1.4.3 に瀺すように48kHz で正しく暙本化されたデヌタに察しお 6 サンプル毎に䞀぀の倀を抜出しお 䜜成した疑䌌 8kHz 暙本化デヌタを掲茉しおいる䞍適切なダりンサンプリング。48kHz デヌタが 4kHz 以䞊の垯 域に情報を持぀堎合圓然゚むリアスが生じる。適切に 8kHz で暙本化されたデヌタは課題 1 で聎取したが䞍適 切な 8kHz デヌタに぀いおも聎取し゚むリアスの聞こえ方目立ち方に぀いお音源間で比范せよ。 たた8kHz で正しく暙本化されたデヌタに察しおれロを 5 ぀ず぀挿入するこずで䜜成した疑䌌 48kHz 暙本化デヌタも掲茉しおいる䞍適切なアップサンプリング。これに぀いおも聎取し比范せよ。正しくアップサン プリングしたデヌタ48kHz 暙本化デヌタであるが情報は 4kHz 以䞋にのみ存圚するも掲茉しおいる。 怜蚎事項

課題 4.1 を螏たえ゚むリアスの目立ち方に音源間で差がある理由を考えよ。たた䞍適切なアップ

サンプリングによっお䜜られた 48kHz デヌタはどのようなスペクトル圢状を持぀こずになるのかを考えよ。

本課題 4.3 C 蚀語における DFT の実装 任意の耇玠系列信号 xrs [], xis [] を入力ずしそれを耇玠系列信号 Xsr [], Xsi [] に倉換する関数をプログラミングせよ。 たたその関数を甚いお任意の耇玠系列信号 xrs [], xis [] に察しおその察数振幅スペクトルを蚈算するコマンドを䜜成 せよ。コマンドの出力をファむル化しGNUPLOT を䜿っおグラフ化せよ。なお以降の実隓では区間長 N が異 ] [ なるスペクトルを比范するため察数振幅スペクトルずしおは log10

1 i2 r2 N {Xs (k) + Xs (k)}

を䜿え。

穎埋め゜ヌスファむルは実隓 web に掲茉しおいる。

本課題 4.4 DFT によるスペクトル解析6 課題 4.1課題 4.2 で聎取したサりンドデヌタを分析し察数振幅スペクトル特性を求めよ。8kHz, 16kHz, 48kHz のデヌタに察しお区間長は N = 256, 512, 1536 ずせよ。実時間長で蚀えばどれも 32[msec] である。なお各音 源に察しおどの時点からの N サンプルを分析察象するのかに぀いおは実隓 web を参照せよ。 怜蚎事項

課題 4.1, 課題 4.2 で行なった予想ず DFT による分析結果を比范し自らの予想を怜蚌せよ7 。゚む

リアスが目立぀音源䞍適切なアップサンプリングによる圱響は分析結果のどこに珟れおいるのか考察せよ。

遞択課題 4.5 実際のむンパルス応答を䜿った音響効果シミュレヌション 数皮類の異なるむンパルス応答を実隓 web に掲茉しおいるデヌタフォヌマットなどは web 参照。これを甚い お各皮音源を挔出しおみよ。なお本課題のために実際に無響宀反響音が党く無い郚屋で収録されたサりンド デヌタも掲茉しおいる。これに぀いおも実隓しおみよ。 穎埋め゜ヌスファむルは実隓 web に掲茉しおいる。 6 必ず課題 4.1課題 4.2 の予枬を行なっおから実行するこず!!! 7 課題 4.1, 課題 4.2 の予想が䞍適切であっおも課題 4.4 での怜蚌の結果正しい考察に到達しおいれば枛点はしない。自らの思考の

誀りを実隓を通しお芋぀けそれを正す胜力も今埌芁求される胜力の䞀぀である。


怜蚎事項 ゚コヌ効果は FIR フィルタであるこずを説明した。䜜成した各皮゚コヌフィルタヌの察数振幅スペク トルを求めGNUPLOT で図瀺せよ。

遞択課題 4.6 FIR を䜿った簡易サりンド゚フェクタの詊䜜 図 G1.4.5 に埓っお簡易サりンド゚フェクタを詊䜜しおみよ。なお穎埋め゜ヌスファむルは無い。

4. 実隓方法ず泚意事項 本テキストず実隓 web をよく参照しながら実隓を進めるように。各皮コマンドの䜿い方デヌタファむルのフォヌ マットなどの情報も実隓 web に掲茉しおいる。なお配垃 PC の内蔵スピヌカでは音の違いがよく分からない可胜性 があるので倖付けスピヌカを繋げるず良いだろう。䜆しむやみにボリュヌムを䞊げお回りの孊生に迷惑をかけない ように。たたヘッドセットマむクは乱暎に扱うず砎損する折れるこずがあるので泚意するように。

参考図曞 • 「プログラミング蚀語 C」B. W. カヌニハンD. M. リッチヌ著石田晎久蚳共立出版 (1989) • 「信号解析入門」越川垞治著近代科孊瀟 (1992) • 「ディゞタル・サりンド凊理入門」青朚盎史CQ 出版瀟 (2006) • 「C 蚀語ではじめる音のプログラミング」青朚盎史オヌム瀟 (2008)


G2.

情報: 第 2 郚


第 5 日 むンタヌネット基瀎 1. は じ め に 今回からいよいよむンタヌネット電話 (or 䌚議システム) を䜜るにあたっおの, もう䞀぀の重芁な芁玠である, ネット ワヌクに関する課題を始める. 今回は, ネットワヌクの「プログラミング」に぀いお孊ぶ前の準備ずしお, むンタヌネットに぀いお理解しおおくべき 抂念を孊び, 既存のコマンドを通しおネットワヌクを䜿っおみる事で, それを実感する. 今回孊ぶ内容は, ネットワヌク を甚いる゜フトりェアを蚭定したり, ネットワヌクが぀ながらない時のトラブルシュヌトのために必芁な知識や抂念で もある.

準備課題 5.1 挔習甚 wireless network ZENKIJIKKEN に接続し, むンタヌネットのペヌゞが閲芧できる事を確かめ よ. 䟋えば挔習ホヌムペヌゞ http://g1g2g3.logos.ic.i.u-tokyo.ac.jp/にアクセスしおみよ.

2. むンタヌネットの実䜓 2.1 IP コンピュヌタを日垞利甚するに圓たっおは, むンタヌネットぞ接続されおいる ≈ Web ペヌゞが閲芧できおメヌルができる ずいう事かもしれない. 本実隓を通しお「むンタヌネットぞ接続されおいる」ずいう状態をもう少し粟密に, 技術的に理 解できるようになっお欲しい. 「むンタヌネット」ずいう物の技術的な実䜓は, Internet Protocol (IP) ずいう通信プロトコルを基瀎ずしおいる. IP は, IP アドレスずいう名前を宛先ずしお, その宛先さえ指定すれば䞖界䞭のどこぞでもパケットを届けおくれるずいう 仕組みである. そのために IP では様々な宛先ぞの経路 (ルヌト) を教え合う仕組み, IP を䜎䜍局のネットワヌク (Local

Area Network. 有線 LAN や無線 LAN) ず結び぀ける仕組みを芏定しおいる. IP アドレスの䟋は, 133.11.238.2 のような, 0-255 たでの数字が 4 ぀䞊んだ物である. ビット数で蚀えば, 8 ビット ×4 = 32 ビット である. これは, IP ver. 4 (IPv4) ず呌ばれるプロトコルで甚いる IP アドレスで, IP ver. 6 (IPv6) の堎合 は, FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 のように, 16 進数 4 桁 (16 ビット)×8 = 128 ビット のアドレス が甚いられる. 本実隓で甚いる IP アドレスは IP ver. 4 の物である. 圓然の事ながらあるホストで, IP を甚いた通信が できるための条件その 1 は, IP アドレスが割り圓おられる事である.

UNIX では, ホストに蚭定されおいる IP アドレスは, ifconfig コマンドを䜿っお調べる事ができる (Windows では, ipconfig).

準備課題 5.2 ifconfig コマンドを甚いお自分のアドレスを調べおみよ.

$ ifconfig

なお, 有線, 無線含めお倚数のネットワヌクむンタフェヌスに関する出力がなされるが, 実隓宀では無線 LAN を甚い おいるだろうから, このうち芋るべきは無線 LAN の物である. それは wlan0 ずいう名前のむンタフェヌスになっおい るこずだろう.


$ ifconfig wlan0 ずすれば wlan0 の蚭定内容だけを芋る事ができる. 「むンタヌネットに接続されおいる」ずいう状態は, 最も原始的な意味では「IP パケットを送受信できる」ずいう事で ある. UNIX で, あるアドレスに IP パケットを送信し, その返事を受け取るずいうコマンドが, ping である. Windows でも ping コマンドを甚いる.

準備課題 5.3 ifconfig コマンドを甚いおお互いのマシンの IP アドレスを調べおそれを教え合い, ping コマンドを甚 いおお互いの疎通確認をしおみよ. 珟圚どのマシンも名乗っおいない IP アドレスぞ向けお ping コマンドでパケット を投げおも, 圓然返事は垰っおこない. 適圓なアドレスを指定しお ping コマンドを実行しおみお, その時の挙動も確 認しおみよ.

$ ping h 友達のマシンの IP アドレス i $ ping www.yahoo.co.jp $ ping h でたらめな IP アドレス i

もちろん, 自分に IP アドレスが割り圓おられおいないずきに ping コマンドを発行しおも, 成功しない. これもあえお 詊しお確認しおおくず良い. ゚ラヌのずきにどんな挙動になるかを知っおおく事は, 「急がば回れ」で, 埌々自分のプログラムの間違いを蚺断する 際に重芁である.

IP で䞖界䞭のマシンずパケットのやりずりが出来るために, 䞭心的な圹割を果たしおいるのが, パケットの宛先 IP ア ドレスに応じお, そのパケットを適切な方向ぞ送り蟌む (転送する) ルヌタず呌ばれる機材である. ルヌタでない機材 (ほ ずんどのホスト) は,

• 自分の「近隣」の宛先ぞは, LAN を甚いおパケットを盎接届け, • それ以倖のパケットはすべお決められたルヌタ (default gateway) に投げ぀ける, ずいう単玔な事しかしない. どの IP アドレスを「近隣」ずみなすかは, 「サブネット」ずいう範囲で蚭定されおおり, 実 䜓ずしおはある IP アドレスの区間 (圓然その区間には自分の IP アドレスが含たれる) である. 衚蚘ずしおは,

133.11.238.0/25 のように,

address/n ずいう衚蚘を甚いる. address は IP アドレス, n は (原理的には)0. . . 31 たでの敎数である. これは「address ず䞊䜍 n ビットが䞀臎するすべおのアドレスの集合」を意味しおいる. 䟋えば 133.11.238.0/25 は, 133.11.238.0, 133.11.238.1,

. . . , 133.11.238.127 を意味しおいる. 䟋えば 133.11.238.10 ずいう IP アドレスで, サブネットずしお 133.11.238.0/25 を蚭定しおいるホストがある IP ア ドレスに IP パケットを送信するずき,

• そのアドレスが, 䞊蚘の範囲にあれば, そこぞは定䜍局のネットワヌク (LAN) の機胜を甚いお (どうにかこうに か) パケットを届ける

• そうでなければ, default gateway に, (やはり定䜍局のネットワヌクの機胜を甚いお), パケットを届けお, あずは 適切な転送先に送っおもらう


ずいう堎合分けをする. この堎合分けを, 宛先に応じおもっず现かく蚭定しおいるのがルヌタである. 適圓に曞いた「ど うにかこうにか」の郚分はそのうち授業で孊ぶ事だろう. 芁するにサブネット内は「どうにかこうにか」LAN の機胜を 甚いお通信し, サブネットを越えるためにルヌティングを行っおサブネット間を枡り歩くのが IP 通信で, だから Internet

(Inter Network) ず呌ばれる. ここたでをたずめるず, あるホストで IP 通信ができるための条件その 2 は, そのホストが属するサブネットの情報が 蚭定されおいる事, である. そしお条件その 3 は, default gateway が正しく蚭定されおいる事, である. ただ, これ は自分ず同䞀のサブネット内の盞手ずしか通信しないのであればなくおもよい事になる. サブネットの情報も ifconfig コマンドで調べる事ができる. 実際に衚瀺されるのは subnet mask (サブネットマスク) ず呌ばれる情報である. あ るサブネット address/n に察するサブネットマスクずは, 32 bit 䞭䞊䜍の n ビットがすべお 1, 䞋䜍 (32 − n) ビットが

0 であるようなビット列を IP アドレス颚に衚蚘した物である. 䟋えば 133.11.238.0/25 ずいうサブネットのサブネット マスクは, 䞊䜍 25 ビットが 1 であるようなビット列を IP アドレス颚に衚蚘した, 255.255.255.128 である. 芁するに,

133.11.238.0/25 ずいうサブネットは, a & 255.255.255.128 = 133.11.238.0

ずなるような IP アドレス a の集合, ずいうこずである.

Default gateway は, route コマンドを甚いお調べるこずができる. このコマンドは䞀般に, どの IP アドレス宛のパ ケットは, 次にどこぞ届けるかず蚀う情報 (ルヌティングテヌブル; 経路衚) を衚瀺するもので, 通垞のホストであれば, 同䞀サブネット内ずそれ以倖の欄が衚瀺される. そしお埌者に察しお default gateway ぞパケットを投げる, ずいう情報 が衚瀺される. $ route -n カヌネル IP 経路テヌブル 受信先サむト

ゲヌトりェむ

ネットマスク

フラグ

Metric

Ref

䜿甚数

むンタフェヌス

133.11.238.0

0.0.0.0

255.255.255.128

U

2

0

0

wlan0

0.0.0.0

133.11.238.1

0.0.0.0

UG

0

0

0

wlan0

2.2 グロヌバル IP アドレスずプラむベヌト IP アドレス ひずたびホスト (PC など) に IP アドレス, default gateway, サブネットマスクが蚭定され, default gateway たで は LAN で通信できる, ずいう状態が確保されおしたえば, あずは䞖界䞭のどんなマシンに察しおも IP パケットを届け る事が出来る. もちろんそれには default gateway が正しく蚭定されおいる (宛先 IP アドレスに応じお適切な経路 = 次のルヌタを遞んでくれる), ずいうのが前提であるが, その仕組みに぀いお話すず長くなる (そのうち授業で出おくる) し, 幞い個々のホストがその蚭定に関䞎するわけではないのでここでは深入りしない. 逆に䞖界䞭のホストから default

gateway たで, あるサブネット行きの IP パケットが届くずいう状態になっおいればあずはそのサブネット䞭の個々のホ ストに察する IP パケットをその gateway が LAN を䜿っお届けおくれる. こうしお個々のホストは, 䞖界䞭のホストず 通信が出来るこずになる. ただしいく぀か䟋倖があり, 実隓宀環境もその「䟋倖」に盞圓しおいるのでそれを䞀応説明しおおく. フィルタリング : セキュリティの方針により, ルヌタが䞀郚の IP パケットを転送しない, ずいうこずがある. 䟋えば,

• 特定の IP アドレスに向けたパケットしか転送しない • 特定の IP アドレスに向けたパケットは, 特定の IP アドレスから送られおきたパケットしか転送しない など. 埌述する「ポヌト」を甚いおさらに现かく蚭定されるこずもある. プラむベヌト IP アドレス : 党䞖界的な慣習ずしお, ルヌタがそれらに向けた転送をしないこずが決められおいるサブ ネットが存圚する. それらのサブネットに属するアドレスをプラむベヌト IP アドレスず呌ぶ. それ以倖の IP ア ドレスはグロヌバル IP アドレスず呌ぶ. プラむベヌト IP アドレスずは具䜓的には以䞋である.


192.168.0.0/16

192.168.0.0

...

192.168.255.255

172.16.0.0/12

172.16.0.0

...

172.31.255.255

10.0.0.0/8

10.0.0.0

...

10.255.255.255

これらの IP アドレスに察しお, 異なるサブネットからルヌタを経由しお IP パケットが転送される, ずいうこずは 慣習䞊行われない. 逆に蚀うず, 本来䞖界䞭で䞀意に割り圓おられるはずの IP アドレスも, プラむベヌト IP アドレスに関しおは䟋倖 で, LAN が異なれば同じプラむベヌト IP アドレスを耇数のマシンが名乗っおも混乱は生じない. プラむベヌト

IP アドレスの名前の由来でもある. そこでプラむベヌト IP アドレス (サブネット) は, LAN を手軜に構築する手 段ずしお倚甚されおいる. 実隓宀の無線 LAN に぀ないだ堎合も, プラむベヌト IP アドレスが割り圓おられる. 家 庭でプロバむダず契玄しお, ブロヌドバンドルヌタなどに PC を぀ないだ際に割り圓おられるアドレスも倧抂プラ むベヌト IP アドレスである.

プラむベヌト IP アドレスしか持たない PC でも, 䞖界䞭のマシンぞ向けおパケットを送るこずは可胜である. 䞀方, 侖界 䞭のマシンからそのプラむベヌト IP アドレスぞ向けおパケットを送っおも, それがそのマシンに届くこずはない. では なぜ, そのようなマシンでも普通にホヌムペヌゞが芋られるのか? ホヌムペヌゞを芋たいずいうリク゚ストが Web サヌ バに届くずころたではよいが, 返事 (ホヌムペヌゞの内容) をどうやっお受けずるのか? そこには分かりにくいトリックが働いおいる. 自分から䞖界䞭のマシンぞ向けお IP パケットを送る際に経由するルヌ タ (通称, NAT ルヌタず呌ばれる. 家庭で䜿うブロヌドバンドルヌタも倧抂がこれである) が, IP パケットの送信元 IP アドレスをこっそり, PC の IP アドレスからルヌタの物に倉曎しおいる. リク゚ストを受け取った Web サヌバはそれ がルヌタからの物であるず思ったたた, ルヌタに返事を返す. そしおルヌタに届いた IP パケットをルヌタが PC に転送 する.

2.3 UDP ず TCP IP での通信ができるようになれば, 原理的には, 䞖界䞭のどのホストずでもパケットのやりずりができる. しかしなが ら実際のアプリケヌションを䜜るに圓たっおは IP だけではただ䞍十分な点が倚い. そのために, UDP ず TCP ずいう プロトコルが IP 䞊に構築されおいる.

IP が䞍十分な点の第䞀は, IP アドレスは個々のホストに぀き䞀぀ (耇数蚭定する事もできるが, その堎合でもせいぜ い数個) しか持たせられないずいうこずである. そのため, 耇数のアプリケヌションが䞀぀のホストで同時に起動されお 通信しようず思うず, それらの仕分けをする必芁が生ずる. ぀たりそれら耇数のアプリケヌションに異なる論理的な「宛 先」を割り圓おおやらないずいけない. 䟋え話ずしおは, 「東京郜文京区本郷 7-3-1」ずいうアドレスに, ビルの名前や孊 科の名前を぀けお, それを実際に受け取る人を指定できる必芁が有る. このためにポヌト番号ずいう数字を甚い, IP ア ドレスずポヌトの組を通信の宛先名, ずできるようにしたのが UDP (User Datagram Protocol) である. ポヌト 番号は 16 ビット, ぀たり䞀぀の IP アドレスで, 65,536 個の UDP 通信の宛先を論理的に持぀事ができる.

IP が䞍十分な点の第二は, 通信の到達保蚌 (信頌性) がないずいう点である. ぀たり, 送信したパケットが必ず宛先に 到着するずいう保蚌はないし, 到着したか吊かを送信者に知らせる仕組みもない. これは様々な堎面でプロトコルやネッ トワヌク機噚の蚭蚈を単玔にする. 䟋えばネットワヌク機噚は高負荷時に䜕の制埡や通知もせずに, パケットを砎棄する 事ができる. そもそも䞖界䞭ず通信する事を目暙に蚭蚈されたプロトコルだから, すべおのルヌタが健康に動䜜しおいる などずいう前提でプロトコルを蚭蚈する事はできないので, これはもっずもな事である. 䞀方で, すべおのアプリケヌションを「送信したパケットが黙っお砎棄されるかもしれない」ずいう前提で蚘述しな くおはいけないのでは, プログラムは恐ろしく耇雑になっおしたう. そこで, IP の䞊に信頌性のある通信を提䟛しおい るのが, TCP (Transfer Control Protocol) である. むンタヌネット䞊のアプリケヌション—Web, メヌル, ファむ ル転送など—は, 倚くが TCP を甚いおおり, むンタヌネット技術のコア䞭のコアず蚀っおよいプロトコルである. その ためむンタヌネットの事を代名詞的に, TCP/IP ず呌ぶ堎面も倚い. ただし, 「無理な物は無理」—䟋えば通信盞手自 身が途䞭でいなくなっおしたったり, LAN ぞの接続が長時間切れたら゚ラヌになる—ずいう事は圓然である. 䞀時的な ルヌタの高負荷や, 短時間の切断に察する耐性を提䟛するのが TCP である.


2.4 DNS 最埌に, 宛先の指定に IP アドレスずポヌト番号を甚いるず述べたが, IP アドレスは人間が甚いる名前ずしおは䞍䟿で ある. そこで通垞, ホスト名は, IP アドレスではなく, DNS 名ずいうシンボリックな (アルファベットを甚いた) 名前 で指定する. DNS 名の䟋は, www.yahoo.co.jp や www.cnn.com のような, web ペヌゞを閲芧しおいるずきにも, 甚い おいる物である. DNS 名は実際の IP 通信に先立っお, IP アドレスに倉換される必芁があり, この倉換を行う仕組みが

DNS (Domain Name System) である. DNS は技術的には DNS 名からそれに関連する情報 (IP アドレスなど) を 匕き出すための巚倧なデヌタベヌスである.

DNS の倉換凊理は, 倚数の蚈算機 (DNS サヌバ) に分散しお実珟されおいる. したがっおこの問い合わせ自䜓に IP (UDP) が䜿われおいる. 個々の PC は, 手近な DNS サヌバひず぀を指定しお, すべおの問い合わせをそこ (Primary DNS サヌバ) ぞ投げ぀ける. DNS サヌバは, DNS サヌバ間で協調し, 自分で凊理できない問い合わせを適切に転送し お, どんな問い合わせに察しおも結論を出す. そこであるホストで, シンボリックなホスト名を甚いた通信 (≈ 䞀般ナヌ ザが快適に, 「いわゆるむンタヌネット」) ができるための条件その 4 は, primary DNS サヌバが蚭定されおいる事で ある. ただ, 玔粋な技術的な蚀葉䜿いにしたがえば, これは「IP 通信ができる」ための芁件ではない.

UNIX 䞊で, DNS を甚いお DNS 名を IP アドレスに倉換するコマンドはいく぀かある. nslookup, host, dig などが あるが, 本実隓では host コマンドを䜿う. Windows には, nslookup コマンドがある.

準備課題 5.4 host コマンドで www.yahoo.co.jp や, 自分のよく䜿うホスト (Web サヌバなど) の IP アドレスを調 べよ.

$ host www.yahoo.co.jp それで IP アドレスが分かったら, http://www.yahoo.co.jp/ の代わりにそのアドレスを甚いお, ブラりザでペヌゞを アクセスしおみよ. そのたた yahoo を芋続けおはいけない.

たた, シンボリックなホスト名を IP アドレスに倉換する仕組みは, DNS 以倖にも, 蚭定ファむル (/etc/hosts) ぞ盎接 蚘述する, NIS, LDAP などの仕組みがあり, 実際の運甚ではそれらを組み合わせる事もあるので, 少々ややこしい. こ の実隓では, DNS 以倖は無芖しおよい.

2.5 ここたでのたずめ. むンタヌネットの芁件 個々のホストに以䞋の情報を䞎える (蚭定を斜す) ず, むンタヌネットで通信をするための芁玠技術が䜿えるようになる.

IP アドレス: サブネット: どの宛先アドレスの範囲に, LAN を甚いお盎接 (gateway を経由せずに) パケットを送信するか

default gateway: サブネット倖の IP アドレスぞ向けたパケットを送り぀ける宛先. そこから先の転送を行っおくれ る (はず).

DNS サヌバ: シンボリックなホスト名 (DNS 名) を IP アドレスぞ倉換しおくれるサヌバ

さお, 䞊蚘の情報は手動で蚭定する事もできるが, ラップトップなど, 倚くのパヌ゜ナルナヌザ向けのマシンでは DHCP

(Dynamic Host Configuration Protocol) ずいうプロトコルで自動的に蚭定される. 今時の PC は, 䜕も蚭定をし なければ DHCP を甚いおこれらの情報を自動蚭定するようになっおいるため, PC を物理的にネットワヌクに接続すれ ば気づかないうちにむンタヌネットぞ぀ながっおいる事も倚い. その裏ではこれらの情報が自動的に泚入されおいるの である. ifconfig で衚瀺されるのもこうしお泚入された情報に他ならない. コンピュヌタの䞖界には (䞖の䞭すべおで?) 倧した意味のない 3 文字略語があふれおいるが, è¡š G2.2.1 に茉せた物 は間違いなく重芁な抂念である.


è¡š G2.2.1 略語

IP UDP TCP DNS DHCP

むンタヌネット関係の最重芁 2/3/4 文字略語

抂芁 IP アドレスを宛先ずしお, 䞖界䞭のネットワヌクに IP パケットを届ける パケットは途䞭で砎棄されるかもしれない 通信の宛お先に IP アドレスずポヌト番号を甚いる. 䞀ホストで耇数の通信が可胜になる パケットの到達性ず到達順序を保蚌する IP アドレスの代わりにホスト名 (www.yahoo.jp など) を甚いた通信 を可胜にする. 正䜓は, ホスト名 →IP アドレスの倉換 ホストをむンタヌネットに接続するための情報を配信する

実装の (䞻な) 所圚 ルヌタ ホスト ホスト DNS サヌバ

DHCP サヌバ

3. nc コマンドで TCP/IP 通信を行う 前節で述べた通り, むンタヌネットを甚いたアプリケヌションは, TCP もしくは UDP ずいうプロトコルを甚いお, 「IP アドレスずポヌト番号の組」を宛先ずしお指定しながら通信を行う. もちろんそれは普段, むンタヌネットを甚いおいるあらゆるアプリケヌションの䞭で起きおいる事である. 䟋えば

Internet Explorer や Firefox などのりェブブラりザ, Windows メヌルや電信八号などのメヌル゜フトなど, あらゆる゜ フトが内郚で TCP や UDP を甚いた通信を行っおいる. 本実隓で䜜るアプリケヌションも䟋倖ではない. そのために

UNIX でも Windows でも, 「゜ケット」ず呌ばれるプロセス間通信のためのむンタフェヌス (Application Programming Interface; API) が甚いられる. その説明は次回に回す事ずし, 今回は TCP や UDP を甚いた通信を行うプリミティブ なコマンドを通しお, その抂念を実感する事を目指す. もちろんそれらのコマンドも゜ケットむンタフェヌスを甚いお通 信を行っおいる.

netcat ずいうプログラム (nc コマンド) は, (1) どこかの IP アドレス + ポヌトずの接続を確立する (2) その接続盞手から受け取ったデヌタを暙準出力に垂れ流す (3) 暙準入力から受け取ったデヌタをその接続盞手に垂れ流す ずいう機胜だけを持぀単玔なプログラムである. 「接続を確立する」ずいうのは, 話し盞手を決めお, その人が実際にい る事を確認する, ずいう事で, 電話をかけお呌び出し音が鳎るたでのプロセスず思えば良い. 電話で話をするには, 「ど ちらかが電話をかけお, どちらかが受け取る」ずいう颚になっおいないずいけない. ゜ケット通信の堎合も同様で, 電話 をかける偎をクラむアント, 受け取る方を「サヌバ」ず蚀う慣習になっおいる. サヌバ: 接続を確立する盞手を指定せずに, 「誰か」が接続をしおくるのを埅぀. ポヌト番号を自ら指定する事もできる し, オペレヌティングシステムに割り圓おおもらう事もできるが, どちらにしおも䞀぀のポヌト䞊で, 「接続埅ち」 状態になる. クラむアント: サヌバが接続埅ちをしおいる IP アドレスずポヌト番号を指定しお, 接続をする. この䞡者が揃うず, äž¡ 者の間で実際のデヌタの通信を始める事ができる.

nc コマンドをサヌバずしお甚いるには, -l オプションを指定し, 「埅ち受け」に入るポヌト番号を指定する. ポヌト番 号は, 0〜65,535 のどれかを指定するが, 1,023 以䞋は管理者暩限がないず甚いる事ができない䞊, 䞀般に䞋の方の番号は 予玄されおいたり, 既存のアプリケヌションが甚いおいる可胜性が有るため, ここでは甚いない. したがっお, 倧きい数

(䟋えば 10,000 以䞊) を指定する物ず芚えおおく. なお, nc コマンドは, 特に指定しなければ (UDP ではなく)TCP を甚 いお通信する. 䟋:

$ nc -l 50000 nc コマンドをクラむアントずしお甚いるには, 接続したいサヌバの IP アドレスずポヌト番号を指定する. 䟋: $ nc hIP アドレス i 50000 もちろん IP アドレスの郚分には, ifconfig コマンドで調べた, サヌバプロセスが走っおいるホストの IP アドレスを甚い


る. これは自分のマシンの物でも, 友達のマシンの物でも, (IP アドレスさえ分かっおいれば) 海の向こうのマシンの物 でも良い. たた, デバッグの際は同䞀のマシン内で䞡方のプロセスを立ち䞊げるのも䟿利である. そのような堎合, 127.0.0.1 ずい うアドレスは「自分」を意味する物ずしお垞に䜿えるので䟿利である. それら二぀のプロセス (A, B ずする) が揃っお起動するず, A の暙準入力ぞ入力したデヌタは B ぞ送られ, それが B の暙準出力ぞ出力される. その逆 (B の暙準入力 → A の暙準出力) も同様である.

準備課題 5.5 nc コマンドを甚いお 2 ぀のマシンの間を接続し, 簡単な文字列を送りあっおみよ.

準備課題 5.6 わざず間違えたポヌト番号 (サヌバが接続埅ちでないようなポヌト番号) ぞ接続し, その際の挙動を芋お おくこず.

準備課題 5.7 同じポヌト番号で二぀のサヌバを立ち䞊げたらどうなるか? これも埌のために䞀床は芋おおくこず.

準備課題 5.8 man コマンドで nc の䜿い方を調べ (man nc) よ. サヌバやクラむアントが, 接続に成功した際にメッ セヌゞを衚瀺しおくれるようにするためのオプションが有る. それを調べお䜿っおみよ. たた, 既定では TCP を甚い るが, UDP を甚いるためのオプションも調べお芋よ.

nc コマンドの基本は, 暙準入力 → ネットワヌク, ネットワヌク → 暙準出力, ずいうデヌタの転送を行う事であったか ら, 圓然, 暙準入出力のリダむレクトを䞊手に甚いれば, ファむルの内容を送受信する事も可胜である.

本課題 5.9 nc コマンドを甚いお, 適圓なファむルの内容を別のマシンぞコピヌしおみよ. UDP でも転送しおみよ.

転送する「ファむル」ずしお, /dev/dsp を甚いれば, 䞀方のマシンに入力した音をもう䞀方のマシンぞ転送する事が できる. 転送した偎でそれをリダむレクトしお/dev/dsp に曞けば, 片方のマシンに吹き蟌んだ音をその堎で別のマシン 䞊で再生できおしたう.

本課題 5.10 nc コマンドを甚いお, 片方のマシンに入力した音声を別のマシンぞ転送し, そのマシンでその音を出力 しおみよ. 安定しお送れるか? TCP, UDP それぞれ甚いお違いがあるかどうか芳察しおみよ.

これで「䞀方通行」の音声転送が出きおしたった. 埌はこれを逆方向にも行えば, 䌚話ができる. これでむンタヌネッ ト電話の最䜎限䞭の最䜎限の機胜はできおしたった. もちろんそれは nc コマンドの機胜を䜿っおいるからで, 我々はい わば nc コマンドの䞭身も理解しお (C プログラムずしお) 䜜るのが目暙であるし, それをクリアしおなお䜙力があれば, 効率的な転送方法, よりよい笊号化, 倚者間通話などの高床な課題ぞ進んでいく事ができる. これらは nc をこねくり回 すだけではできない.


4. nc でアプリケヌションプロトコルを理解する nc はネットワヌクず暙準入出力を盎接結ぶアプリケヌションで, 䜿い方を工倫するず, アプリケヌションがネットワヌ クにどのようなデヌタを流しおいるのかを調べる事ができる. それにより, 普段䜿っおいるむンタヌネットを甚いたアプ リケヌションが, 意倖ず簡単な仕組み (プロトコル) でできおいるずいう事を知るだろう. ここでは詊しに, りェブブラり ザずりェブサヌバの間で流れおいるデヌタを調べおみる. 䟋えばりェブブラりザを立ち䞊げお, 適圓なりェブペヌゞを閲芧しおいるずする. 各りェブペヌゞには URL (Uniform

Resource Locator) ずいう名前 (䟋: http://www.ee.t.u-tokyo.ac.jp/j/banner/life.html) が぀いおおり, 珟圚衚瀺され おいるペヌゞの URL はりェブブラりザ䞊郚のアドレスバヌに衚瀺される.

URL はプロトコル, ホスト名 (+ ポヌト番号), パス名 (+ その他の郚分) からなっおおり, 䞊蚘の䟋では, • プロトコル = http • ホスト名 (+ ポヌト番号) = www.ee.t.u-tokyo.ac.jp (ポヌト番号は省略されおおり, その堎合 80 ずなる) • パス名 (+その他) = /j/banner/life.html である. りェブブラりザがこの URL を受け取ったずきに行う動䜜は,

(1) www.ee.t.u-tokyo.ac.jp ずいうホスト䞊の 80 番ずいうポヌト䞊で, サヌバプロセスが埅ち受け状態にある事を仮 定し, そこぞ接続する. もちろんそれに先立っお, DNS を甚いお IP アドレスを求める.

(2) 接続したら, 「/j/banner/life.html ずいうペヌゞをよこせ」ずいうリク゚ストを送信する. (3) 送信したら, サヌバから返事を受け取り, それを衚瀺する. 無事ペヌゞの内容が取埗できる堎合もあれば, そんな ペヌゞはない, などの゚ラヌが返される事もある. ステップ 2 で, 正確にどのような文字列を送れば良いのか, ステップ 3 で, 正確にどのような文字列が返されるのかは,

HTTP (Hyper Text Transfer Protocol) ずいうプロトコルで芏定されおいるのだが, ここではその内容その物よりも, その「調べ方」が䞻題である. ステップ 1: たず, nc コマンドを甚いお, 取り合えず適圓なポヌトで埅ち受けにはいるだけの, web サヌバをでっち䞊 げる.

$ nc -l 50000 ステップ 2: りェブブラりザ (䟋えば Firefox) を立ち䞊げ, このサヌバから, /j/banner/life.html ずいうペヌゞを取埗す るよう, リク゚ストする. 䟋えば䞊蚘で立ち䞊げたサヌバの IP アドレスが 192.168.1.100 だったずするず, 以䞋を アドレスバヌに入力する.

http://192.168.1.100:50000/j/banner/life.html 圓初の URL のホスト名郚分 (www.ee.t.u-tokyo.ac.jp) を, 192.168.1.100:50000 に眮き換えた物になっおいる事 および, :50000 で, ポヌト番号が指定されおいる事に泚意. これを省略するず, プロトコルごずに定たった既定の ポヌト番号 (http の堎合 80 番) が甚いられる. するず, ブラりザが䞊蚘で立ち䞊げたサヌバに接続し, リク゚ストを送る. そのリク゚ストは圓然ながら, nc なん ちゃっおサヌバプロセスの暙準出力ずしお, 芳察する事ができる. 詳现はブラりザによっおも異なるが, 以䞋のよ うな感じの文字列が衚瀺される事だろう.

$ nc -l 50000 GET /j/banner/life.html HTTP/1.1 Host: 191.168.1.100:50000 User-Agent: Mozilla/5.0 (X11; U; Linux i686; ja; rv:1.9.0.6) \ Gecko/2009020911 Ubuntu/8.04 (hardy) Firefox/3.0.6 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ja,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip,deflate


Accept-Charset: Shift JIS,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive

ここでは詳现は重芁でないが, 最初の行で, 欲しいペヌゞの名前が, GET ずいうコマンドずずもに指定されおいる 事に泚意しお欲しい. ステップ 3: ここで本来のサヌバであればすぐに返事を返すが, この停サヌバは䜕もしないので, ブラりザは返事埅ち状 態になる. サヌバプロセスを匷制終了する, でたらめな文字列を返す, しばらく攟眮する, など適圓な事をやっお, ブラりザがどのように反応するかを芋おみよ. ステップ 4: では, たずもなりェブサヌバはどのような返事を返すのかを芋おみよう. そのため今床は nc にクラむアン ト (ブラりザ) の圹をやらせる. たず䞊蚘の実隓をもう䞀床行っお, 今床はクラむアントからの文字列をリダむレク トしお, 適圓なファむル名 (䟋: request) で保存しおおく.

$ nc -l 50000 > request request には, 䞊で芋たような文字列が保存されるはずである (確認せよ). そしお今床は本物のりェブサヌバに向 けお, nc コマンドを甚いお接続し, そのファむルの内容を送り぀け, 結果をたた別のファむル名にリダむレクトし お保存する. もちろん宛先ポヌト番号も本来の物 (80) を甚いる.

$ nc -q -1 www.ee.t.u-tokyo.ac.jp 80 < request > response -q -1 は, 「終了するな」ずいう意味のオプションで, これを぀けないず nc コマンドが request を読みきったずこ ろで即座に終了しおしたう (結果, 䜕も衚瀺されない). 数秒も埅おば response は埗られおいるはずなので Ctrl-C で終了する. ステップ 5: response を開いおみれば, サヌバからどのような文字列が返されるのかが分かる. 実はこのたたでは, ペヌ ゞがない (Not Found) ずいう結果が返されおいるはずである. その理由は, request の 2 行目

Host: 192.168.1.100:50000 にある. これは, request を送る宛先のホスト名が曞かれおいるのだが, これを受け取ったサヌバの了解 (「自分は

www.ee.t.u-tokyo.ac.jp ずいう名前の Web サヌバである」) ず食い違うために生ずる. この行を手動で, Host: www.ee.t.u-tokyo.ac.jp ず修正した䞊で同じ実隓を行えば, なんずなく芋芚えのある (?) 文字列が返っおくるはずである. 文字列にはプロトコルのヘッダず, ペヌゞの本䜓がかかれおいるのだが, 最初の空行たでがヘッダである. そこを捚おお,

$ firefox response ずでもしおみれば, 芋芚えのあるペヌゞが出おくる事だろう.

5. iperf でネットワヌクのバンド幅を枬定する iperf コマンドは, nc コマンドず䜿い方が䌌おいる. ただし目的はネットワヌクの性胜 (バンド幅) の枬定であり, サヌ バを立ち䞊げ, クラむアントを立ち䞊げるず勝手に 10 秒ほどデヌタを流しお, ネットワヌクのバンド幅 (転送速床) ã‚’è¡š 瀺しおくれる.

本課題 5.11 iperf コマンドを甚いお, 友達のマシンの間でバンド幅を枬定せよ. たくさんの人 (たたはたくさんのプ


ロセス間) で同時に枬定するずどうなるか. その状況で, nc で音声を流しおみるずどうなるか?


第6日 1. 抂

゜ケットプログラミング (クラむアント)

芁

いよいよネットワヌクを甚いた通信を自前で行うプログラムの䜜成に入る. それには UNIX でも Windows でも, æš™ 準的に提䟛されおいる「゜ケット」ずいうプログラミングむンタフェヌス (API) を甚いる. 今回は゜ケットのクラむアントを䜜るための API に぀いお説明する. 前回やったように, ゜ケットのクラむアントは, あるポヌト䞊で「埅ち受け」状態になっおいるサヌバがいるずいう前提で, その IP アドレスずポヌトに向かっお接続す る (電話をかける). 接続埌はデヌタの送受信 (䌚話) ができる, ずいう物である. 前述したずおり IP の䞊に構築されたプロトコルに UDP ず TCP があり, 埌者が信頌性 (到達保蚌) を提䟛する. ずり あえず最初はそちらが䜿いやすいだろうずいうこずで, 以䞋の説明では TCP を甚いるず仮定しお具䜓的な説明をする.

TCP を䜿っお実際に通信するための手順は以䞋の通り. 括匧内が実際に呌び出す関数名である. ステップ 1: ゜ケットを䜜る (socket) ステップ 2: 接続する (connect) ステップ 3: デヌタの送信 (send たたは write), 受信 (recv たたは read) を行う ステップ 4: 通信終了埌, ゜ケットを閉じる (close) ファむル入出力ずの類掚で蚀えば, socket は open ず䌌おいる. send, recv はそれぞれ write, read ず䌌おいる. 実際,

send の代わりに write, recv の代わりに read を甚いる事もできる. close もファむルを閉じるずきず実際に同じ API を 甚いる. ぀たり UNIX においおは゜ケットはファむルディスクリプタの䞀皮なのである. 䜙分なステップは connect ず いう事になるが, これは別のプロセスに接続するずいう, ファむル入出力では必芁なかった物だから, 䜙分なステップに なるのもうなづける. あえおもう少し実際のプログラム颚に曞けば以䞋のような手順になる.

unsigned char data[N]; int s = socket∗ (); connect∗ (s, ”192.168.1.100”, 50000); read(s, data, N);

# data に N バむトたでのデヌタを受け取る

data[0] = . . .; data[1] = . . .; . . .; write(s, data, N);

# data から N バむトたでのデヌタを送る

close(s); もちろん write, read は繰り返し甚いおも, どのような順番で甚いおも良い. なお, UDP を甚いる堎合, 倧雑把に蚀えば䞊蚘の connect ずいうステップが省略され, send/recv のたびに

sendto/recvfrom ずいう API 関数を甚いる. sendto/recvfrom は send/recv に加えお宛先の IP アドレス, ポヌト を指定する匕数を持぀ (぀たり, TCP では通信盞手を前もっお䞀぀に限定しおいるのに察し, UDP では送る床に自由に 宛先を倉えるこずができるずいうこずである). 残念ながら実際の゜ケット API は匕数などがもう少し倚い䞊に, 名前もややこしい (䞊で関数名に ∗ を぀けおいるの は, 実際の API ずは匕数などが異なるずいう事を明瀺するため). あくたでそれぞれの API に枡しおいる物は, 䞊のよう な情報だずいう事を芋倱わないように, 衚面䞊のややこしさはある皋床受け入れおもらうしかないのだが, なぜこんな事 になっおいるのかずいう事情説明を少ししおおく. 「゜ケット」はプロセス間で通信を行うための汎甚的なむンタフェヌ スずしお蚭蚈されおいる. IP 通信だけを察象ずしおいるのではない. 䟋えば同䞀ホスト内の耇数プロセス間で通信を行


うための, UNIX ドメむン゜ケットずいう物がある. IP 通信にしおも IPv4 ず IPv6 がある. ゜ケット API は, それら すべおをほが同䞀のむンタフェヌスで䜿えるように蚭蚈されおいる.

• このため䞀々, ゜ケット API の匕数に「私はどの䜓系 (IPv4, IPv6, UNIX ドメむン etc.) で通信がしたいです」 ずいう事を明瀺的に指定する必芁がある ⇒ 䜙分な匕数が増える.

• TCP, UDP ずいうような, IP にのみ通甚する固有名詞も API の衚面にあからさたに珟れる事はない. だから, TCP を䜿っお通信する時に, socket(TCP) ずでも曞ければただいい物を, socket(. . . , SOCK STREAM) などず いう, 間接的な物の蚀い方になる. ⇒ 匕数の意味が分かりにくい.

• 通信手段が異なればアドレスの衚珟も違うので, ゜ケットの API には, IP アドレスを衚すようなあからさたな匕数 は盎接珟れない. 䟋えば IP 通信では, 通信の宛先は, IP アドレスずポヌトで指定するが, UNIX ドメむン通信では, ファむルのパス名 (“/tmp/foo” などの名前) で指定する. だから connect の匕数を, connect(”192.168.1.100”,

50000) ずするわけには行かない. そこですべおの通信手段のアドレスを包含したようなデヌタ型 (sockaddr) が, API の衚面には珟れ, それに無理やり, IP アドレスを枡すような仕組みが甚いられる. ⇒ 匕数の枡し方が分かり にくく, C 蚀語に習熟しおいない人には難解に感じられる.

• 同じ理由で, man ペヌゞで socket の API を芋おも, IP 通信をするずきの固有のやり方たでは曞いおいない. 䟋 えば man connect を芋おも, IP アドレス 192.168.1.100 のポヌト 50000 に぀なぐ方法が曞いおいない. 以䞊の心の準備を元に, 実際の API を解説する.

2. API 説 明 2.1 #include ファむル IP 通信をするにあたっお必芁なヘッダファむルがある. 悪いこずにこれらは以䞋で説明する API 関数のマニュアル ペヌゞには出おいない. 理由は䞊蚘で述べたずおりで, ゜ケットはあくたで「汎甚的な」プロセス間通信であるため, 個々 の通信プロトコルに䟝存した情報は茉せ切れないため (実際本圓に茉せきれないかどうかは怪しい. ドキュメントの保守 性ずある皮の矎的感芚により茉せおいない, ずいうのが本圓のずころか) である. 結論を蚀うず, 以䞋の API の man ペヌゞに指定されおいるヘッダファむルに加え, IP では以䞋を#include しおおけ ばよいだろう.

#include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> なおこれらの情報源は,

$ man 7 ip $ man 7 tcp $ man 7 udp である. その他適宜, TCP/IP プログラミングに関する曞籍を参照するずよい.

2.2 socket ゜ケットを䜜る.

int s = socket(PF INET, SOCK STREAM, 0); ファむルの open に盞圓する物だず思えばよい. 返り倀はファむルディスクリプタである (し぀こくここでも, man socket で, 成功の確認方法ず#include すべきヘッダファむルを調べよ, ず蚀っおおく). 以降で䜿う関数に, ここで返された倀 を枡す. これも open ず, read/write/close ずの関係に䌌おいる. 匕数の,

• PF INET は, IPv4 ずいう通信䜓系 (ドメむン) を甚いる事を指定しおいるちなみに IPv6 は PF INET6, UNIX ドメむンは, PF UNIX.


• SOCK STREAM は, TCP を甚いた通信を指定しおいる. ちなみに UDP は, SOCK DGRAM. むメヌゞずしおは, 通信盞手ずの間に接続を確立するのは, 䞡者の間に糞電話を匕っ匵っお結ぶような物だが, ゜ケット はその端っこ, ぀たり糞電話の玙コップ郚分である.

2.3 connect 指定した IP アドレスずポヌト番号に接続する.

connect を呌び出すために, IP アドレスずポヌト番号をどうにかしお枡す必芁がある. connect(s, ”192.168.1.100”, 50000) ずでも曞ければ簡単なのだが, 前述した通り, IP 以倖の事も考えお API が蚭蚈されおいるため, 枡し方が回りく どい. 以䞋は, IP アドレス 192.168.1.100 のポヌト 50000 に接続するための基本フォヌムである.

struct sockaddr in addr; addr.sin family = AF INET;

# これは IPv4 のアドレスです

addr.sin addr.s addr = inet addr(”192.168.1.100”); addr.sin port = htons(50000);

# IP アドレスは. . . です # ポヌトは. . . です

int ret = connect(s, (struct sockaddr *)&addr, sizeof(addr));

# 遂に connect

やっおいる事は, 最終的に connect に addr ずいう構造䜓のアドレス (&addr) を, そのサむズ (sizeof(addr)) ずずもに枡 しおいる. addr のデヌタ型は, sockaddr in ずいう物で, これは IPv4 アドレスを衚しおいる. それに先立぀ 3 行で, そ の構造䜓の芁玠を埋めおいる.

• sin family ずいう芁玠には, このアドレスがどのような通信䜓系のアドレスであるかを栌玍する. addr.sin family = AF INET; はそれが, IPv4 のアドレスである事を瀺しおいる. これは, connect が受け取った匕数を芋お, 確 かにこの人は IP 通信のためのアドレスを枡しおきおいる, ずいう事を理解するのに甚いられる.

• sin addr.s addr ずいう芁玠に宛先 IP アドレスを栌玍する. そのために, ”192.168.1.100” のような, 文字列によ る IP アドレスの衚蚘を, 32 bit の圢匏に倉換する関数 inet addr を呌んでいる.

• sin port ずいう芁玠には, ポヌト番号を栌玍する. addr.sin port = 50000 ずそのたた代入すれば良さそうな物だ が, 现かい事情でそうは行かず, htons ずいう関数を呌んで, 倉換しおやる必芁がある. 事情は埌に説明する. し぀こく蚀うが䞊のコヌドを䜿う堎合に, connect が成功した事を確認しないで先ぞ進む事はくれぐれもないように. 非 垞に間違いをしやすいずころである. たた䞊蚘を䜿うには色々#include が必芁なのでそれもお忘れなく. 泚: inet addr vs. inet aton

マニュアルによるず inet addr は䜿わずに, inet aton を぀かえずある. だから,

addr.sin addr.s addr = inet addr(”192.168.1.100”); は,

inet aton(”192.168.1.100”, &addr.sin addr); ず曞く事が掚奚されおいる. 理由は inet addr にぱラヌを怜査する方法がないから. inet aton は返り倀でそれが区別 でき, IP アドレスずしお䞍正な文字列を枡すず, 0 が返る. ここでは説明のため, 芋た目のわかりやすい inet addr を䜿っ お説明した. さんざん゚ラヌ怜査をしおおけずいっおいるので, もちろん本圓は inet aton が掚奚である.

2.4 send/recv (たたは write/read) 無事接続が成功したらデヌタの送受信ができる. send ≈ write, recv ≈ read ず思っおよく, 実際埌者を䜿っおも良い. 䞡者の違いは, send/recv は, write/read よりもひず぀匕数が倚く, その匕数でいく぀かオプションを指定する事ができ る事である. が, この実隓においおはさしあたり䜿う必芁はない (ここで send/recv に蚀及するのは䞻に, そちらの方が

Windows 環境などを含めお, 「普通」ずみなされおいるから, ずいうだけの理由). n = send(s, data, N, 0); たたは

n = write(s, data, N);


n = recv(s, data, N, 0); たたは

n = read(s, data, N); 動䜜に぀いおは read, write ず同じだから説明するたでもないだろう.

2.5 close/shutdown close(s); open したファむルを閉じるのず党く同じ API を甚いる. これ以降は, この゜ケットを甚いる事はできない. たた, read/recv は, 盞手が close を呌び出し, か぀盞手がそれ以前に送ったデヌタをすべお受け取った埌, 0 を返す

(ファむルを最埌たで読み終わった埌ず同じ挙動をする). そこでこの堎合も, ゜ケットから「EOF を読んだ」「EOF を 受け取った」などずいう衚珟をする事がある. 実際にはファむルを読んでいるわけではないのだが, 慣習ずしおこのよう な蚀い方をする.

close をするず, 以降デヌタの送信 (write)・受信 (read) ずもにできなくなるが, 「自分はもうデヌタを送り終わった」 ずいう事を盞手に教え぀぀, ただやっおくるデヌタを受けずる事だけはしたい, ずいう状況がよくある. この様な時, 自 分はもうデヌタを送り終わった事を通知するために close を呌んでしたうず, もうデヌタを受け取る事ができず, こたる 事になる. そのような時に甚いるのが shutdown ずいう API である.

shutdown(s, SHUT WR); を呌ぶず, 盞手は (こちらが send したすべおのデヌタを読み終わった埌)EOF を受け取る. しかし, こちらは䟝然ずしお

read は可胜である.

本課題 6.1 以䞋のような動䜜をする C プログラム client recv.c を゜ケット API を甚いお䜜れ.

• コマンドラむンで指定した IP アドレスずポヌトに接続する • 接続したサヌバがデヌタを送っおくるずいう前提で, デヌタを EOF に到達するたで読んで読んだデヌタを暙準 出力に曞く

できたら, nc コマンドでサヌバを立お, client recv でそれに぀ないでデヌタを送受信しおみよ. リダむレクションを 利甚しお, nc コマンドに倧きめのファむルを送らせ, 受け取った方もファむルにそれを保存せよ. 䞡者は党く同じ内容ず なるはずである. その事を確かめよ. それには, nc コマンドを甚いお受け取ったデヌタを元のマシンに送り返しお, diff コマンドを甚いお比范するずよいかもしれない. たた, ファむルサむズが䞀臎しおいる事, md5sum ずいうコマンドで衚 瀺されるハッシュ倀が䞀臎しおいる事を芋る事もしお芋るず良い.

$ md5sum filename 1a24874fc4ea61095d0cf16da5ee8516 filename

本課題 6.2 数秒の音デヌタを返しお接続を切るサヌバをこちらで甚意する. そこに接続しお, 出力を/dev/dsp にリダ むレクトする事で, 音をながしおみよ.

$ ./client recv 133.11.238.11 50000 > /dev/dsp

デヌタを受けずるだけでなく, 送っおから受け取るプログラムも曞いおみよう.


本課題 6.3 以䞋のような動䜜をする C プログラム client send recv.c を゜ケット API を甚いお䜜れ.

• コマンドラむンで指定した IP アドレスずポヌトに接続する • 暙準入力が EOF を返すたで, 暙準入力からデヌタを読んではそれを接続先に送る事を繰り返す • デヌタを送り終わったら shutdown(s, SHUT WR) で, 送信デヌタの終わりを接続先に通知する • その埌, 接続先からデヌタを EOF たで受け取る

芁するにデヌタを送るだけ送っお, あずは受け取るずいうクラむアントである. 受け取ったデヌタをそのたた送り返すだけのサヌバをこちらで甚意する. 本プログラムをそこに接続しお送ったのず 同じデヌタが返っおくる事を確かめよ.

$ ./client send recv 133.11.238.11 50001 < orig file > output file これで, orig file ず output file は同䞀のファむルずなるはずである. diff コマンドで比范しおみよ.

$ diff orig file output file トピック: なぜこんなに汚いむンタフェヌス?

若干本題ずそれるが, C 蚀語の汚いずころずよりストレスなく付き合え

るようになるための雑孊.

connect の匕数の枡し方はどう芋おもきれいな物ではない. struct sockaddr in addr; addr.sin family = AF INET; addr.sin addr.s addr = inet addr(”192.168.1.100”); addr.sin port = htons(50000); int ret = connect(s, (struct sockaddr *)&addr, sizeof(addr)); 倧元の理由は䜕床も述べおいる通り, ゜ケットが IP 以倖の通信にも䜿えるように (アドレスの衚珟圢匏などが異なっお いおも䜿えるように) 蚭蚈されおいるからである. なぜそれを目暙に蚭蚈するずかくも汚い事になっおしたうのかを説明 しおおく.

C 蚀語をはじめずする倚くの蚀語では, 関数の呌び出し圢匏 (匕数の数やその型) は基本的には関数ごずに䞀意でなく おはならないずいう制限がある. 䟋えばある時は double 型の匕数を 3 ぀, ある時は文字列型の匕数を 7 ぀受けずる, な どずいう関数 (可倉匕数関数) を曞くずいう事は, 基本的にはない (厳密には嘘. printf などがたさにその䟋倖になっおい るのだが, そのような関数を曞くのは面倒な䞊, どんなデヌタ型に察しおも䜿えるわけではない. ここでこれ以䞊の深入 りはしない). そこで, connect ずいう䞀぀の関数に, どうしたらある堎合は IPv4 アドレスを, ある堎合には IPv6 アドレスを, たた ある堎合には UNIX ドメむン゜ケットのアドレスを枡すのか, ずいう事が問題ずなる. そのために C 蚀語で垞套手段ず しお䜿われるのが, 構造䜓 (struct) に必芁なデヌタを栌玍し, そのポむンタを枡す, ずいう手法である. 埩習になるが, 必芁な情報をすべお䞀぀の倉数にたずめるために構造䜓がある. 䟋えば, 3 次元空間内の点を衚す構造 䜓ずしお,

struct point { double x; double y; double z; }; ずいう, 3 ぀の double を䞀぀にたずめた構造䜓を定矩しおおけば,

struct point p;


で, p は, p.x, p.y, p.z ずいう 3 ぀の double の情報を保持できる倉数ずなる. 関数に枡すずきも, p 䞀぀を枡せば, 3 ぀の

double を枡したのず同じ効果がある. connect の API では, 通信䜓系ごずにそのアドレスを衚す構造䜓が定矩されおい る. IPv4 であれば sockaddr in, IPv6 であれば sockaddr in6, UNIX ドメむン通信であれば, sockaddr un のように. あずはこのように定矩されたアドレス構造䜓の倉数を connect に枡せば良さそう, ずいう事になる.

struct sockaddr in ipv4 addr; struct sockaddr in6 ipv6 addr; struct sockaddr un unix addr; ... connect∗ (s1, ipv4 addr, . . . ); connect∗ (s2, ipv6 addr, . . . ); connect∗ (s3, unix addr, . . . ); のように. しかし残念ながら䞀぀の関数 (connect) の䞀぀の匕数 (第 2 匕数) が, ある時は sockaddr in 構造䜓, ある時は

sockaddr in6 構造䜓, たたある時は sockaddr un 構造䜓を受けずるなどずいう噚甚な事は (C 蚀語では) できない. しかしそれが, 構造䜓ではなく, 構造䜓ぞのポむンタならば蚱される. だから, ある時は sockaddr in 構造䜓ぞのポむ ンタ, ある時は sockaddr in6 構造䜓ぞのポむンタ, たたある時は sockaddr un 構造䜓ぞのポむンタを受けずる匕数, ず いう物は䜜る事ができる. それが connect の 2 番目の匕数である. ぀たり,

struct sockaddr in ipv4 addr; struct sockaddr in6 ipv6 addr; struct sockaddr un unix addr; ... connect∗ (s1, &ipv4 addr, . . . ); connect∗ (s2, &ipv6 addr, . . . ); connect∗ (s3, &unix addr, . . . ); は機胜する. 圢匏䞊, その匕数の型は, sockaddr 構造䜓ぞのポむンタ, ずいう事になっおいるため, 枡す際にキャストを する

struct sockaddr in ipv4 addr; struct sockaddr in6 ipv6 addr; struct sockaddr un unix addr; ... connect∗ (s1, (struct sockaddr *)&ipv4 addr, . . . ); connect∗ (s2, (struct sockaddr *)&ipv6 addr, . . . ); connect∗ (s3, (struct sockaddr *)&unix addr, . . . ); 事で, 型を connect が想定しおいる物ず (圢匏的に) 䞀臎させる. たた, このような事をするずきは, 受け取った方でどの 皮類のデヌタを実は受け取ったのか, ず蚀う事が分かるように, 構造䜓の決たった䜍眮に, 皮類を衚すタグを぀けおおく のが普通である. それが, sin family ずいう芁玠である. なぜ構造䜓その物だずダメなのに, ポむンタなら良いのか? ぀たらない答えはそれが決たりだから, ずいう物だが, な ぜそのような決たりになっおいるのかには, C 蚀語凊理系の仕組みをある皋床知るず玍埗できる理由がある. それは, ポむンタずは所詮, アドレスの事に過ぎないので, 実は䜕ずいう構造䜓ぞのポむンタであろうずそのサむズが同 じだからである. 䟋えば 32 bit のマシンではすべおのポむンタは 32 bit の敎数䞀個に過ぎない. したがっお, sockaddr in 構造䜓ぞのポむンタも, sockaddr in6 構造䜓ぞのポむンタも, sockaddr un 構造䜓ぞのポむンタも, すべお同じ仕組みで 枡される. だからある関数ぞ匕数を枡すのに, ある時は sockaddr in 構造䜓ぞのポむンタ, ある時は sockaddr in6 構造 䜓ぞのポむンタ, たたある時は sockaddr un 構造䜓ぞのポむンタが枡されたずしおも, 各匕数が枡される堎所がずれるよ うな事はなく, 受け取る方に難しさは発生しない.


䞀方これが, 構造䜓その物を枡す (ポむンタを枡すのず䜕が違うのか分からないかもしれないが, ここでは構造䜓の䞭 身をすべお枡す事, ず思っおくれれば良い) ずなるず, 構造䜓は䞭身が違えばサむズも異なるため, 䞀぀の匕数のサむズ が堎合によっお違う, ずいう事になる. そのような匕数を枡されお, 受け取る偎がそれを垞に正しく受け取る事は, 逆立 ちしおもできないずは蚀わないが, ややこしい (か぀遅い) 凊理が必芁になる. 䟋えば匕数を単にメモリ䞊に隙間なく䞊 べお枡すような方匏では, 他の匕数の栌玍堎所などが狂っおきお困った事になる. そこでこれらを間違いなく枡そうず思 うず, どの匕数がどの堎所にあるかなどの情報も, 合わせお枡すなどの凊理が, 関数呌び出しの床に必芁になる. 「どの 匕数がどの堎所にあるかなどの情報を枡す」ずいうのは, どの匕数がどのアドレスにあるか, ずいう事だから, ポむンタ を枡すずいうのずほずんど同じ事である. そんな事をこっそりやるくらいだったら, 必芁に応じおプログラムを曞く方に やらせお, 普段から遅くなるような事はしない, ずいうのが C 蚀語の基本スタンスである. 以䞊の話から, connect を䜿うための雛圢が,

struct sockaddr in addr; addr.sin family = AF INET; addr.sin addr.s addr = . . . ; addr.sin port = . . . ; int ret = connect(s, (struct sockaddr *)&addr, sizeof(addr)); のようになる事たでは玍埗できたのではないかず思う. connect が第 3 匕数ずしお sizeof(addr)—第 2 匕数で枡された構 造䜓の倧きさ— を受け取っおいるのは, 絶察必芁ずいう分けではないかもしれないが, 䟋えばアドレスの䜓系によっお は必芁なサむズが可倉ずいう事もありうるため, この様になっおいる物ず玍埗しよう (IPv4 の堎合, IP アドレス 32 bit, ポヌト番号 16 bit ず決たっおおり, サむズは自ずず分かる). たた, 受け取った偎で第 2 匕数をコピヌする, などの凊理 を簡単に行うためにも, 枡されおいた方が䟿利であろう.

sin addr.s addr に぀いおは, “192.168.1.100” ずいうアドレスを 13 文字の文字列ずしお衚珟するのではなく, 8 bit ×4 の 32 bit で衚すずいう方法を採甚しおいるので, inet addr ずいう関数でその倉換を行っおいる. sin port に぀いおは, な ぜ敎数をそのたた代入するだけではダメで, htons などずいう関数を呌んでいるのか? htons は, host-to-network-short の略で, short (16 bit) 敎数の, 1 バむト目ず 2 バむト目の䞊び方 (どちらが MSB 偎でどちらが LSB 偎か) に぀いお, そ れを「ネットワヌクバむト順序」なる物にする関数である. 䞀般に CPU では, 8 bit を䞀぀の敎数ずみなした挔算, 16 bit を䞀぀の敎数ずみなした挔算, 32 bit を䞀぀の敎数ず みなした挔算, . . . などが呜什ずしお提䟛されおいる. それに察応しお, メモリのある番地 (X) から 8 bit を読み蟌む呜 什, メモリの連続した二぀の番地 (X, X+1) から 16 bit を読み蟌む呜什, メモリの連続した四぀の番地 (X, X+1, X+2,

X+3) から 32 bit を読み蟌む呜什, などが提䟛されおいる. ある CPU の「バむト順序」ずいうのは, 䟋えば X, X+1 の 二぀の番地から 16 bit を読み蟌む呜什を発行した時に, どちらのアドレス (X or X+1) から出おきた 8 bit が, 読み蟌た れた 16 bit の MSB (Most Significant Byte; 16 bit 䞭の䞊䜍 16 bit) になるか, ずいう事を芏定しおいる物である. こ れは CPU によっお異なっおおり, Intel x86 系は Little Endian ずいっお, X+1 番地の方が MSB になる. 他の倚くのマ シンでは, Big Endian ずいっお, X 番地の方が MSB になる. ネットワヌクバむト順序も Big Endian である. だから,

htons の正䜓は, Little Endian の CPU 䞊では 16 bit の MSB ず LSB を入れ替える (Big Endian ならなにもしない), ずいう事である. 通信を実珟するパケットの䞭には, 送信元アドレス, ポヌトや受信宛お先アドレス, ポヌトなどが含たれおいる. その 衚珟 (ここで問題ずなるのはポヌト番号) がホストごずに異なっおいるのは困るから, 通信をする際には党䞖界のマシン で共通の順序を決めおおきたい. それがネットワヌクバむト順序である. もちろんそんな物は connect 関数の䞭でやっ おあげお, 䜕も枡す方がそれに統䞀しおやらなくおもいい, ずいう気もするが, sockaddr in ずいう構造䜓の衚珟は, (bit 列のレベルで)CPU のバむト順序によらない物ずしたかった, ずいう事ではないかず思われる.



第7日 党時間実習ずする. 遅れおいる人はこの時間で远い぀く. 䜙裕のある人は, 次回を予習する, 発展課題の構想を緎っお議 論する, などの時間ずしお䜿う.



第 8 日 ゜ケットプログラミング (サヌバ) 1. 抂

芁

今回は゜ケットのサヌバ偎の API を説明する. これで, クラむアント・サヌバずも自前のプログラムで通信ができる 事になる. ゜ケットのサヌバは, あるポヌト䞊で「埅ち受け」状態に入り, クラむアントからの接続 (connect) を受け付 ける. 「埅ち受け」関数から返るず, その時点でクラむアントずの接続が確立しおおり, デヌタの送受信をする事ができ る. 接続が確立するたでの手順がクラむアントに比べるずさらにややこしいが, 䞀旊接続が確立した埌のやり方はクラむ アントずサヌバで党く同じである. ステップ 1: ゜ケットを䜜る (socket) ステップ 2: どのポヌトで埅ち受けるか決める (bind) ステップ 3: 埅ち受け可胜宣蚀 (listen) ステップ 4: クラむアントが connect するたで埅぀ (accept) ステップ 5: デヌタの送信 (send たたは write), 受信 (recv たたは read) を行う ステップ 6: 通信終了埌, ゜ケットを閉じる (close) もう少し実際のプログラム颚に曞けば以䞋のような手順になる.

unsigned char data[N]; int ss = socket∗ (); bind∗ (ss, 50000); ∗

listen (ss);

# 埅ち受け番号は 50000 に蚭定 # 埅ち受け可胜宣蚀

∗

s = accept (ss); close(ss);

# 接続が来るたで埅機 # これ以䞊接続を受け付けない堎合

read(s, data, N); data[0] = . . .; data[1] = . . .; . . .; write(s, data, N); close(s); もちろん write, read は繰り返し甚いおも, どのような順番で甚いおも良い. close(ss) は, これ以䞊接続を受け付けな い—぀たりこれ以降 accept を呌ぶ぀もりがない—䜍眮で呌び出す.

2. API 説 明 bind, listen, accept ずいう 3 ぀のステップ以倖はクラむアントず共通である. 以䞋では, ss が socket から返された倀 を栌玍しおいるずする (ss = socket(. . . )).

2.1 bind bind は, 「゜ケットに, ポヌト番号などの名前を割り圓おる」システムコヌルである. 本実隓の文脈に合った圢で蚀う ず, 「将来どのポヌトで埅ち受けるかを指定する」システムコヌルである. ここでも, ゜ケットが汎甚的な (IP 通信に限 らない) むンタフェヌスずしお蚭蚈されおいるため, その枡しかたが回りくどい (bind(ss, 50000) では枈たない). 以䞋が基本フォヌムである.

struct sockaddr in addr; addr.sin family = AF INET;

# 最終的に bind に枡すアドレス情報 # このアドレスは IPv4 アドレスです


addr.sin port = htons(50000);

# ポヌト. . . で埅ち受けしたいです

addr.sin addr.s addr = INADDR ANY;

# どの IP アドレスでも埅ち受けしたいです

bind(ss, (struct sockaddr *)&addr, sizeof(addr)); 党䜓ずしおやっおいる事は, 自分がどのポヌトず, どの IP アドレスで埅ち受けをする぀もりなのかを, addr ずいう構造 䜓倉数 (のアドレス) を枡す事で, bind システムコヌルに教える事である. この枡し方自身は connect ず䌌おいるので今 床は長い説明の必芁はないだろう. それに先立ち addr ずいう構造䜓の芁玠を埋めおいるのがその䞊の 3 行である. 「どの IP アドレスで埅ち受けをする぀もりなのか」に぀いお, そんなもの自分の IP アドレスに決たっおいるだろう ずいう疑問がわくだろう. そしお実際, 䞊蚘ではここを INADDR ANY ず指定しお, 「どこでもいい (自分の持぀すべ おのアドレス)」ず蚀っおいる. 耇数の IP アドレスを持っおいおそのうちの䞀郚の IP アドレスに察する接続のみ受け付 ける, ずいう堎合にはここにその IP アドレスを指定する事になる.

bind でよく発生する゚ラヌ: “Address already in use” (し぀こい!) bind に限らずすべおのシステムコヌルの成 功確認をする事. そしお, bind を甚いおいるず非垞によく目にする゚ラヌがこれである. これは, 「芁求したポヌト番号がすでに䜿われおいる」ずいう事である. 自分の車のナンバヌプレヌトを, 暪浜 500 あ

11-11 にしようず思ったら, すでに登録枈みだった, ずいうのず同じである. そしお, 自分で適圓なポヌト番号を決めお プログラムを曞いおいるず, そのプログラムを間違えお立ち䞊げっぱなしでもう䞀個立ち䞊げようずしたずきなどにお きがちである. 基本的には, その゜ケットを close すればそのポヌト番号は再利甚可胜になる. そしおその゜ケットを䜿っおいるプロ グラムが終了すれば自動的に゜ケットは close されるので, プログラム終了によっおそのポヌト番号は再利甚可胜になる ず考えおよい. ただし, close しおから実際に再利甚できるようになるたでに少し時間がかかる. それは, 䞀床そのポヌト を䜿ったからには, そのポヌトをめがけお倖からパケットが飛んでくる可胜性があるからである.

2.2 listen listen は, 「接続受付開始宣蚀」である. これをサヌバが行った時から, クラむアントの接続芁求 (connect システム コヌル) は成功するようになる. たた, その埌サヌバは実際に接続芁求がくるのを埅぀ (accept システムコヌルを発行す る) 事ができるようになる. 基本フォヌムは以䞋の通り.

listen(ss, 10); 第 2 匕数 (ここではいい加枛に 10 ずしおある) は, 耇数の connect 芁求がほが同時に殺到した時に, どのくらいの芁求を 萜ずさずに凊理するか, ずいう事を制埡するパラメヌタである. この倀を越える connect 芁求がほが同時に殺到するず, そのうちのいく぀かはえらく時間がかかったり, 最悪の堎合タむムアりトしお倱敗したりするようになる. この実隓で䜜 るプログラムでは, 10 ずでもしおおけば十分だろう.

2.3 accept accept は, 遂にクラむアントからの接続を埅぀操䜜である. listen ずの違いが分かりにくいかもしれないが, 䟋えお蚀 えば listen は, 電話線をゞャックに差し蟌む操䜜, accept は電話の前で電話が鳎るのをひたすら埅぀操䜜である.

accept の結果, クラむアントず通信をするための新たな゜ケットが返り倀ずしお返される. accept に枡した゜ケット で通信をするのではない事に泚意. 実際 accept はこれ以降も新しい接続を受け付ける事ができる (し, そうあっおほし い) ので, これは自然な API ず蚀えるだろう. たた accept の結果, サヌバに䜕ずいうアドレスのクラむアントが接続を しおきたのかも返される. これは必芁がなければ無芖しお構わない. 基本フォヌムは以䞋の通り.

struct sockaddr in client addr; socklen t len = sizeof(struct sockaddr in); int s = accept(ss, (struct sockaddr *)&client addr, &len); client addr は, 情報を accept に枡すためのパラメヌタではなく, accept から, 接続しおきたクラむアントに関する情報 を受けずるためのパラメヌタである. accept から返った時に, client addr の芁玠 sin addr.s addr, sin port が埋められ


おいる, ずいう仕組みである. それに䌎っお, 第 3 匕数も枡した構造䜓のサむズその物ではなく, 返された構造䜓のサむ ズを受け取る倉数 (のアドレス) ずなっおいる (ただし枡す際に, 第 2 匕数で枡した構造䜓のサむズを入れおおく必芁が あるので泚意. これは, 䜕バむトの入れ物を甚意しお埅っおいるかをシステムに教えるために必芁である). たた, 返り倀 は (成功しおいれば) 新しい゜ケットで, 接続しおきたクラむアントずの送受信 (send/recv) をするのはこの゜ケットを 䜿っお行う.

本課題 8.1 以䞋のような動䜜をする C プログラム, serv send.c を曞け.

$ ./serv send h ポヌト番号 i h ファむル名 i ずしお起動するず, ポヌト番号で接続埅ちに入る. 接続しおきたクラむアントに, h ファむル名 i の䞭身を送り぀ける.

぀たり, 課題 6.2 で client recv の盞手をしたサヌバの動䜜である. client recv ず接続しおみお動䜜を確認せよ. 圓然 ながら, h ファむル名 i ずしお, /dev/dsp を遞び, クラむアント偎で/dev/dsp ぞ出力すれば, サヌバから入力した音をク ラむアントで鳎らす事ができる.

3. ネットワヌクの遅延枬定 以前に iperf でネットワヌクの転送速床 (バンド幅) を枬定したが, 今床は遅延を枬っおみよう. 遅延ずいうのは, ある

1 バむトが送信元を離れおから, 目的地ぞ到着するたでの時間である. ただし, 片道の時間を枬るのは難しいので, 埀埩 時間 (Round Trip Time; RTT) を枬る. それに察し, 性胜の指暙ずしおよく䜿われるのは転送速床 (1 秒あたり䜕バむト送れるか) である. 䞡者は䌌おいるが 異なる指暙である. 道に䟋えるず, 転送速床は道の倪さ (䜕車線あるか) に盞圓する. 遅延は目的地たでの距離である. 䞡者が異なるずいう事を玍埗するたずえ話をするず, 䞭東から石油を運ぶのに, 飛行機ではなくなぜ遅いタンカヌを䜿 うのか? それは蚀うたでもなくタンカヌの方が䞀床に積める量が倚いからである. もちろんある日䞭東を出発した石油 䞀滎が日本に届くたでの時間 (遅延) は, タンカヌの方が圧倒的に遅いのだが, それは石油を毎日別のタンカヌで運び぀ づける事でカバヌできる. ぀たり, 䞀日䞀隻のタンカヌを出せば, 日本には䞀日䞀タンカヌ分の量 (バンド幅) で石油が届 く事になる. 飛行機で同じだけのバンド幅を出すのは難しい事だろう. だから, 遅延が倧きくおもバンド幅が倧きいネッ トワヌクや, その逆ずいうのが存圚する. この話からも分かる通り, 転送速床は倧きなデヌタを送るのにかかる時間を支配する. 遅延は小さなデヌタを送るのに かかる時間を支配する. 逆に枬定もそのようにすれば良いずいう事になるだろう.

本課題 8.2 以䞋のような動䜜をするプログラム pingpong s.c ず pingpong c.c を曞け.

•

$ ./pingpong s SZ N は, クラむアントからの接続を埅぀. 接続しおきたら 1 バむトのメッセヌゞを送信する. 以降は, SZ バむトの メッセヌゞを受け取ったらすぐさた 1 バむトのメッセヌゞを送信する, を N 回繰り返す.

•

$ ./pingpong c SZ N は, サヌバぞ接続し, 1 バむトのメッセヌゞを受け取る. 以降は, SZ バむトのメッセヌゞを送り, すぐさた 1 バ むトのメッセヌゞを受信する, を N 回繰り返す. この N 回にかかる時間を枬定し, 1 埀埩あたりの平均時間 t̄ を 蚈算しお衚瀺する. それずずもに, 転送バンド幅 (N / t̄) も衚瀺する.

䞡者を組み合わせれば, ネットワヌク性胜が枬定できるだろう. メッセヌゞが最小のずきの埀埩時間はどれほどか (N を増やしお枬定する事). いろいろな SZ に察しお転送速床を枬り, gnuplot で可芖化せよ. SZ は 1 バむトから 10MB


皋床たで, 等比玚数的に刻みを入れおデヌタを取る事. SZ を十分倧きくしたずきの転送速床は iperf ず同じくらい出 おいるか?

サヌバの擬䌌コヌドは以䞋のような感じ.

... s = accept(ss, . . . ); 1 バむト送る; for (i = 0; i < N; i++) { SZ バむト受け取る; 1 バむト送る; } これはクラむアント

... connect(s, . . . ); 1 バむト受け取る; t0 = 珟圚時刻; for (i = 0; i < N; i++) { SZ バむト送る; 1 バむト受け取る; } t1 = 珟圚時刻; 1 埀埩あたりの時間ず転送速床を衚瀺; 䞊蚘で珟れた「珟圚時刻」を埗る方法に぀いお. gettimeofday ずいうシステムコヌルを甚いる. 䟋によっお man で調 べる事. 以䞋のような関数を䜜っおしたうず良い.

/* 珟圚時刻 (単䜍: 秒) を浮動小数点数で返す */ double current time() { struct timeval tp; gettimeofday(&tp, NULL); return tp.tv sec + tp.tv usec * 1.0E-6; }

4. 基本的なむンタヌネット電話の完成

本課題 8.3 2 ぀のホスト間で䌚話ができる, 基本的なむンタヌネット電話機胜を実珟せよ. ぀たり, 片方のホストのマ むクに向かっお喋るずもう片方のスピヌカに音が鳎る. それを飜きるたで続けおいられるような物である. サヌバずク ラむアントがどのような手順を守っおデヌタを送り合えば良いかを考えお, それぞれの動䜜を実珟せよ.

ここたでを必須課題ずする. 埌は䜙力に応じおこの電話に機胜, 性胜, 蚭蚈その他の発展を斜しおもらいたい. 以䞋は, 候補ずしお考えおもらいたいずころである.

• 無音状態でネットワヌクをほずんど消費しないような, ネットにやさしい通信方法


• より䞀般に, 音声を送るための, 効率の良い笊号化 (音デヌタの衚珟. 圧瞮) • 発生から音が届くたでの遅延を小さくする • 音質を䞊げる. それずデヌタサむズの䞡立 • 倚者間通話を可胜にする (䞀人が喋った声は残り党員に届く) • 倚者間通話で, 通話が始たった埌新しい参加者を受け付けられるようにする 以降の内容は, これたで話した内容を単玔に組み合わせるだけでは無理で, 発展的な内容や, 本テキストで説明しおい ない API を自習する必芁がある物もある. 適宜挔習 HP 䞊で内容を補足しながら進める.


G3.

情報: 第 3 郚


第9日 党時間実習ずする. 遅れおいる人はこの時間で远い぀く. 䜙裕のある人は発展課題に取り組む時間ずしお䜿う.



第 10 日 党時間実習ずする. 遅れおいる人はこの時間で远い぀く. 䜙裕のある人は発展課題に取り組む時間ずしお䜿う.



第 11 日 発衚の時間. この日たでに課題を動かし, その成果をスラむドに曞いお発衚にたずめる事を目指す. 動いた゜フトのデモ ンストレヌションを行うこず. スラむドを䜜るには, OpenOffice.org, Microsoft PowerPoint, TEX などの゜フトを甚い る. 詳现ルヌルは実隓時間内および挔習 HP 䞊で発衚する.



第 12 日 発衚の時間. この日たでに課題を動かし, その成果をスラむドに曞いお発衚にたずめる事を目指す. 動いた゜フトのデモ ンストレヌションを行うこず. スラむドを䜜るには, OpenOffice.org, Microsoft PowerPoint, TEX などの゜フトを甚い る. 詳现ルヌルは実隓時間内および挔習 HP 䞊で発衚する.


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.