1. カーネル空間およびユーザランドのソフトウェアの開発技術

  • オペレーティングシステム(基本ソフトウェア)はカーネルとユーザランドに大別
  • カーネルとユーザランドでは、CPU使用時の特権レベル、メモリアドレス空間が異なる
  • カーネルは高いCPU特権レベルで動作し、システムの各種のリソースの管理やハードウェアの直接制御などの低位の処理をおこなう
  • ユーザランドは低いCPU特権レベルで動作し、必要に応じてカーネルの機能をシステムコール経由で利用する
  • カーネルのメモリアドレス空間は、ユーザランドのプロセスからは保護されており(逆はアクセス可能)、仮想メモリのスワップ領域へスワップアウトされない
  • ユーザランドの個々のプロセスのアドレス空間は、他のプロセスから保護されていおり、必要に応じて仮想メモリのスワップ領域へスワップアウトされる
  • 両者は動作原理が異なるため、その開発に必要な技術も異なってくる

Next































システムコールによるカーネルとユーザランドの連携

  • システムコールは一般的にソフトウェア割り込みで実現され、CPUが特権レベルをカーネルモードへ切り替えてカーネル内部の処理を実行する
  • システムコール呼び出し元のプロセスは、コンテキスト(レジスタやスタックなどの状態)が退避されプロセスの実行は中断される(コンテキストスイッチ)
  • カーネル内部の処理でハードウェアの操作があり処理完了まで待ちが発生する場合、他のプロセスなどの処理へ制御を切り替える
  • ハードウェアの処理完了は割り込みによって通知され、カーネルはハードウェアからデータを読み出すなどの処理を再開する
  • カーネル内部の処理が完了すると、システムコール呼び出し元の中断されたプロセスは実行可能キューへ登録され、カーネルは他の処理へ制御を切り替える
  • カーネルが中断されたプロセスを実行可能キューから選択してそのプロセスに実行権が移ると、ユーザモードのCPU特権レベルで保存していたコンテキストを復元し中断直後の状態から実行が再開される
  • ユーザランド カーネル ハードウェア
    システムコール発行
    プロセス実行中断
    システムコール処理実行
    ハードウェア操作
    他の処理へ切り替え 処理実行
    他の処理の実行 処理完了
    ハードウェア割り込み
    他の処理の実行中断
    割り込みハンドラ実行
    システムコール処理再開
    システムコール処理完了
    プロセスを実行可能キューへ登録
    他の処理へ切り替え
    他の処理の実行完了
    実行可能キューからプロセス選択
    プロセス実行再開

Next































カーネルとユーザプロセスの仮想メモリ空間

  • FreeBSD/i386のカーネルとユーザプロセスの仮想メモリ空間の割り当て
  • アドレス カレントプロセス 待機プロセス
    0x00000000 ユーザプロセス text .... ....
    data .... ....
    bss .... ....
    heap .... ....
    空き .... ....
    stack .... ....
    0xbfbfe000 プロセス管理情報
    カーネル用stack(4+4KB)
    .... ....
    0xbfc00000 カーネル ページテーブル
    0xc0000000 KERNELBASE
    0xc011cc80 text
    data
    bss
    heap
    空き
    0xffffffff
  • 個々のユーザプロセスは約3GBの仮想メモリ空間を使用可能
  • すべての仮想メモリ空間に物理メモリが割り当てられるわけではなく、必要な分だけがページ(4KB)単位で割り当てられる
  • 仮想メモリ空間はいくつかの領域に分けられている
  • 領域名 説明
    text 実行形式のマシン語格納領域
    data 実行形式の初期化済み変数格納領域
    bss 実行形式の未初期化変数格納領域(プロセス生成時に0で初期化される)
    heap 動的に確保可能なメモリ領域で、アドレス増加方向へ成長
    stack サブルーチン呼び出しなどで消費・返却される領域で、アドレス減少方向へ成長

Next































オペレーティングシステムの基本構造

    ┌─────────────────────────┐
    │┌───────────────────────┐│
    ││            ユーザランドプログラム            ││
    │└───────────────────────┘│
    │              ↑        ┌───────────┐│ ユーザプロセス空間
    │              │        │       ライブラリ     ││
    │              │        └───────────┘│
    └───────│───────────↑─────┘
                    │                      │
    ┌───────↓───────────↓─────┐
    │┌───────────────────────┐│
    ││           システムコールインタフェース       ││
    │└───────────────────────┘│
    │┌─────────┐  ┌───────────┐│
    ││   プロセス管理   │  │   ファイルシステム   ││
    │└─────────┘  │ [バッファキャッシュ] ││
    │                        └───────────┘│
    │┌─────────┐  ┌───────────┐│ カーネル空間
    ││    メモリ管理    │  │    ネットワーク層    ││
    │└─────────┘  └───────────┘│
    │                                                  │
    │┌─────────┐  ┌───────────┐│
    ││  アーキテクチャ  │  │   デバイスドライバ   ││
    ││    依存コード    │  │                      ││
    │└─────────┘  └───────────┘│
    └──────↑────────────↑─────┘
                  │                        │
                  ↓                        ↓
    ┌─────────────────────────┐
    │                  ハードウェア                    │
    └─────────────────────────┘
    

Next































プログラム開発の視点からの比較

  • カーネルおよびユーザランドのプログラム開発の視点からの比較を以下にまとめる
  • カーネルユーザランド
    開発言語C言語、アセンブリ言語など
    比較的低位の伝統的言語に限定
    制限なし
    日進月歩でより高生産性の言語が誕生
    ライブラリ専用の伝統的ライブラリに限定 高生産性の言語ごとのライブラリやフレームワークが豊富
    開発環境テキストエディタ、make 統合開発環境など充実
    テスト自動化特に無し テスト支援ツール、カバレッジ取得ツールなど豊富
    デバッグprintf、カーネル組み込みデバッガ、クラッシュダンプ解析 各言語ごとにソースレベルのリアルタイムのデバッグ環境が用意
    例外発生時システムクラッシュ(システム停止)原因調査、修正後に即再実行可能
  • カーネルは生産性向上のための道具が乏しく、また不具合があった場合のデバッグの困難さから、初期段階からの品質が重要となる
  • ユーザランドはある程度のスキル不足は道具がカバーしてくれるものの、道具を使いこなせるかで生産性・品質に大きな差が生じる

Next































カーネルソースコードの構成

  • FreeBSD/i386のカーネルソースコードの構成
  • 分類ステップ数 (K steps)概要
    アーキテクチャ 非依存コード690 システムコールインタフェース、プロセス管理、ファイルシステム、メモリ管理、ネットワーク層など
    アーキテクチャ 依存コード110 アセンブリ言語関数、ISA/PCIバス、仮想記憶など
    デバイスドライバ850 増えつづける数百におよぶデバイスをサポート

    ※上記のステップ数は6.2-RELEASE時点のものであるが、最近のリリースでも大幅な増加は無い

  • Linuxにおいても同様の傾向
  • 最新のデバイスをサポートするため、デバイスドライバのコード量は増加しつづけている

Next































デバイスドライバ

  • デバイスドライバの基本構成は以下の3つ
    • 検出、初期化ルーチン
    • デバイスドライバTop Half
    • デバイスドライバBottom Half

  • 検出、初期化ルーチン
  • ハードウェアデバイスの存在を検出(probe)し、デバイス自身およびソフトウェア状態を初期化する。

  • デバイスドライバTop Half
  • システムコールインタフェース経由の入出力要求を処理し、必要に応じてカレントプロセスを中断させる(tsleep()を使用)。

  • デバイスドライバBottom Half
  • システムが割り込みを受け取った時に呼び出される割り込みハンドラであり、中断させたプロセスを再開させる(wakeup()を使用)。

    
    
     入力要求                                      出力要求
         │                                            │
         │  ┌──────────────────┐  │
         │  │┌──────┐    ┌──────┐│  │
         │  ││入力バッファ│    │出力バッファ││←┘
         │  │└──────┘    └──────┘│    Top Half
         └→│─┐      ↑                  │    │
        -----┼--┼------┼------------------┼----┼----
             │  │    ┌────────┐  │    │
             │  │    │割り込みハンドラ│  │    │    Bottom Half
             │  │    └────────┘  │    │
             │  │      ↑          ↑      │    │
             └─┼───┼─────┼───┼──┘
                 ↓      │          │      ↓
               ┌────────────────┐
               │      ハードウェアデバイス      │
               └────────────────┘
    

Next































カーネルのデバッグ

  • カーネルのデバッグの手法
    • printf()
    • Cコンパイラにアセンブリ言語ソースを生成させる
    • カーネル組み込みデバッガ
    • ソースレベルカーネルデバッガ

  • printf()
  • どこまで実行が進んだのか、あるいはどこで変数の内容が破壊されたのかを調べる原始的な方法。 突然リブートして出力内容を確認できない場合、調べたい個所に無限ループやhlt命令を埋め込む。
    printf()が使えない局面(ブートストラップの初期段階やスタンバイ状態からの復帰など)では、 替わりにBEEP音を鳴らしてどこまで処理が進んでいるかを調査する。

  • Cコンパイラにアセンブリ言語ソースを生成させる
  • 机上デバッグの段階でC言語ソースのレベルでは気づきにくいバグも、アセンブリ言語ソースの レベルで調べると判明するバグも多い。 カーネルのビルドにおいて、"cc -c"となっているところを"cc -S"に変えてアセンブリ言語ソースを生成させる。

  • カーネル組み込みデバッガ
  • カーネルに内蔵されているアセンブリ言語レベルの簡易デバッガとしてFreeBSDにはDDBがある。 カーネル内部処理での例外発生時にリブートする前に起動される。また、コンソールから「Ctrl+PrtSc(またはCtrl+Alt+Esc)」を 入力することで、手動で起動可能。

  • ソースレベルカーネルデバッガ
  • FreeBSDではシステムクラッシュ時に実メモリの内容を専用パーティションへ書き込み、リブート後に その内容をクラッシュダンプとしてファイルへ格納し、gdbによるソースレベルの解析をおこなうことが可能。 シリアルケーブル(NULLモデムケーブル)で接続されたPC側でgdbを起動してオンラインカーネルデバッグをおこなうことも可能。

Next































カーネル組み込みデバッガ: DDB

  • DDBの主なコマンド
  • コマンド 説明 使用例
    break ブレークポイントを設定 b seq_write
    delete ブレークポイントを解除 d seq_write
    continue 次のブレークポイントまで実行を継続する c
    show registers レジスタセットを表示する
    trace スタックトレースを表示する tr
    step シングルステップ実行(step into) s
    next 次のサブルーチンのreturnまで実行(step over) n
    examine 指定されたアドレスの内容を表示 x sequence_number
    write 指定されたアドレスに値を格納 write sequence_number 10
    call 指定されたサブルーチンをcallする call sync
    call boot
    help コマンド一覧を表示

Next































ソースレベルカーネルデバッガ: KGDB

  • KGDBはgdbのカーネルデバッグ用のフロントエンドであるため、使用方法の詳細はgdbのマニュアルを参照。
  • KGDBの使用例
  • [root@freebsd ~]# cd /sys/i386/compile/VMBSD
    [root@freebsd /sys/i386/compile/VMBSD]# kgdb kernel.debug /var/crash/vmcore.0
    [snip]
    panic: free: address 0xcd26abac(0xcd26a000) has not been allocated.
    
    cpuid = 0
    KDB: enter: panic
    Uptime: 17h13m27s
    Physical memory: 243 MB
    Dumping 58 MB: 43 27 11
    
    #0  doadump () at pcpu.h:246
    246             __asm __volatile("movl %%fs:0,%0" : "=r" (td));
    (kgdb) bt
    #0  doadump () at pcpu.h:246
    #1  0xc08aa777 in boot (howto=260) at ../../../kern/kern_shutdown.c:416
    #2  0xc08aaa12 in panic (fmt=Variable "fmt" is not available.
    ) at ../../../kern/kern_shutdown.c:590
    #3  0xc089716d in free (addr=0x0, mtp=0xc0d99140)
        at ../../../kern/kern_malloc.c:444
    #4  0xc074ce78 in seq_write (dev=0xc2597900, uio=0xcd26ac58, flag=0)
        at ../../../dev/seq/seq.c:97
    #5  0xc082b41f in devfs_write_f (fp=0xc2b5b3b8, uio=0xcd26ac58, 
        cred=0xc2a30c00, flags=0, td=0xc2d27a00)
        at ../../../fs/devfs/devfs_vnops.c:1509
    #6  0xc08e97f7 in dofilewrite (td=0xc2d27a00, fd=1, fp=0xc2b5b3b8, 
        auio=0xcd26ac58, offset=-1, flags=0) at file.h:239
    #7  0xc08e9ae8 in kern_writev (td=0xc2d27a00, fd=1, auio=0xcd26ac58)
        at ../../../kern/sys_generic.c:446
    #8  0xc08e9b6f in write (td=0xc2d27a00, uap=0xcd26acf8)
        at ../../../kern/sys_generic.c:362
    #9  0xc0bed483 in syscall (frame=0xcd26ad38) at ../../../i386/i386/trap.c:1111
    #10 0xc0bcf180 in Xint0x80_syscall () at ../../../i386/i386/exception.s:261
    #11 0x00000033 in ?? ()
    Previous frame inner to this frame (corrupt stack?)
    (kgdb) up
    #1  0xc08aa777 in boot (howto=260) at ../../../kern/kern_shutdown.c:416
    416                     doadump();
    (kgdb) up
    #2  0xc08aaa12 in panic (fmt=Variable "fmt" is not available.
    ) at ../../../kern/kern_shutdown.c:590
    590             boot(bootopt);
    (kgdb) up
    #3  0xc089716d in free (addr=0x0, mtp=0xc0d99140)
        at ../../../kern/kern_malloc.c:444
    444                     panic("free: address %p(%p) has not been allocated.\n",
    (kgdb) up
    #4  0xc074ce78 in seq_write (dev=0xc2597900, uio=0xcd26ac58, flag=0)
        at ../../../dev/seq/seq.c:97
    97              free(&seq_buf, M_TEMP);
    (kgdb) up
    #5  0xc082b41f in devfs_write_f (fp=0xc2b5b3b8, uio=0xcd26ac58, 
        cred=0xc2a30c00, flags=0, td=0xc2d27a00)
        at ../../../fs/devfs/devfs_vnops.c:1509
    1509            error = dsw->d_write(dev, uio, ioflag);
    (kgdb) down
    #4  0xc074ce78 in seq_write (dev=0xc2597900, uio=0xcd26ac58, flag=0)
        at ../../../dev/seq/seq.c:97
    97              free(&seq_buf, M_TEMP);
    (kgdb) list
    92                      sequence_number = val;
    93                      sx_xunlock(&sequence_lock);
    94                      break;
    95              }
    96
    97              free(&seq_buf, M_TEMP);
    98
    99              return (error);
    100     }
    101
    (kgdb) p sequence_lock
    $1 = {lock_object = {lo_name = 0xc0ca0122 "sequence", lo_flags = 36896768, 
        lo_data = 0, lo_witness = 0x0}, sx_lock = 1}
    (kgdb) quit
    

Next































[演習] 擬似デバイスドライバ seq の開発とデバッグ

  • 符号無し整数のシーケンス番号を生成するドライバ
  • デバイススペシャルファイル /dev/seq を読み出すことで、次のシーケンス番号が文字列で一行取得できる
  • 数値の文字列を /dev/seq に書き込むと、次のシーケンス番号を設定できる
  • シーケンス番号が最大値に達すると初期値の0に戻る
  • 基本的にアーキテクチャ非依存であるが、シーケンス番号の最大値はアーキテクチャ依存(32ビット版OSでは0xffffffff、つまり10進数で4,294,967,295)
  • 使用例
  • $ head -1 /dev/seq
    0
    $ head -1 /dev/seq
    1
    $ cat /dev/seq
    2
    3
    4
    (中略)
    123456
    ^C
    $
    

Next































[演習] 擬似デバイスドライバ seq の開発とデバッグ(続き)

  • ソースコードの主要部分
  •   34  static struct sx        sequence_lock;
      35  static unsigned long    sequence_number = 0;
      36  static char             sequence_buffer[DEV_BSIZE];
    [snip]
      47  /* ARGSUSED */
      48  static int
      49  seq_read(struct cdev *dev __unused, struct uio *uio, int flag)
      50  {
      51          int c = 0, error;
      52          void *seq_buf;
      53
      54          sx_xlock(&sequence_lock);
      55          snprintf(sequence_buffer, sizeof(sequence_buffer),
      56                  "%lu\n", sequence_number);
      57          seq_buf = (void *)sequence_buffer;
      58
      59          c = strlen(sequence_buffer);
      60          error = uiomove(seq_buf, c, uio);
      61          if (error == 0)
      62          {
      63                  sequence_number++;
      64          }
      65
      66          sx_xunlock(&sequence_lock);
      67          return (error);
      68  }
    
  • ソースコードの修正とカーネルのリビルドおよびインストール手順
  • 
    # cd /sys/dev/seq
    # vi seq.c
    
    # cd /sys/i386/compile/VMBSD
    # make
    # make install
    # shutdown -r now
    
    

Next































[演習] 擬似デバイスドライバ seq の開発とデバッグ(続き)

  • ミッション1
  • 現在、シーケンス番号を生成する関数 seq_read() は問題なく動作しているが、シーケンス番号を設定する関数 seq_write() はシステムクラッシュ(panic)を引き起こす。 カーネル組み込みデバッガDDBおよびカーネルデバッガKGDBを使用して原因を調査し、デバイスドライバのソースコードを適切に修正せよ。

  • ミッション2
  • シーケンス番号は32ビット長となっているが、これを64ビット長に拡張したい。 この要望に合うようにデバイスドライバのソースコードを適切に修正せよ。

  • ミッション3
  • デバイスドライバ seq のソースコードを参考に、POSIX Epoch (1970年1月1日0:00(GMT))からの経過秒数を示す文字列を 取得するデバイスドライバ time を作成せよ。

    カーネル内部の現在時刻の取得にはシステムコールインタフェース /sys/kern/kern_time.c:gettimeofday()、 経過秒数への変換はライブラリtime(3)のソース /usr/src/lib/libc/gen/time.c を参考にする。

    なお、新規のデバイスドライバのソースコードの組み込みには、/sys/conf/files、/sys/i386/conf/VMBSDにある seq 用の記述を参考にする。

    新規デバイスを追加したので、カーネルの再構築とリビルドを以下の手順でおこなう必要がある。

    # rm -rf /sys/i386/compile/VMBSD
    # cd /sys/i386/config
    # vi VMBSD     ←device timeを追加
    # config VMBSD
    # cd ../compile/VMBSD
    # make cleandepend; make depend
    # make
    # make install
    # shutdown -r now
    

    また、以下のようにカーネルモジュールとしてコンパイルし動的にロードすることで、 リブートすることなくデバイスドライバtimeのテストが可能。

    # cd /sys/modules/time
    # vi /sys/dev/time/time.c
    # make
    # make install
    # kldload time
    # cat /dev/time
    # kldunload time
    

Next































2. RDBMSとJavaによるWebアプリケーションの開発

  • Webアプリケーションの代表的要素技術の変遷
    • CGI (Common Gateway Interface)
    • Webの機能を動的に拡張するために考案されたサーバサイド技術。プロセス生成・消滅のオーバヘッドや状態保持の問題。

    • ASP (Active Server Pages)
    • Microsoftによって開発された技術で、HTMLにVBScriptを埋め込んだページをWebサーバのモジュールであるスクリプトエンジンによって実行する。 MS IISが前提環境。

    • JavaApplet
    • クライアントサイドの技術で、SunMicrosystemsによって開発されたJava言語で記述されたアプリケーションを、 Webブラウザ上にダウンロードして実行させる技術。 生産性、マルチメディア(特に音声や動画)の扱いが劣ることから、競合技術であるFlashにシェアを奪われる。

    • JavaScript
    • クライアントサイドの技術で、Netscape 社が開発したスクリプト言語で、Webブラウザ側にあるインタプリタで実行される。 Java言語とは互換性がまったく無い。Ajaxの基盤技術の一つになっている。

    • Servelet
    • JavaクラスをWebサーバへロードして、メソッドを呼び出すことで動的な応答をさせる技術。 Javaプログラムのメソッド内でHTMLを生成するため、ページのデザインが難しい。

    • JSP (Java Server Pages)
    • ASPに対抗して開発された技術で、ASPと同様にHTMLにJavaコードを埋め込む。Servletの弱点を克服。 ロジックとデザインの分離を可能とし、さまざまなプラットフォームで稼動できる。

    • ASP.NET
    • ASPやJSPで実現されたロジックとデザインの分離を、より明確にブロック分けしたMicrosoftによって開発された技術。 開発言語としてC#、VBなど複数のものが選択可能となり、どの言語でも同一の.NET Frameworkのライブラリが使用できる。

    • Ajax (Asynchronous JavaScript and XML)
    • サーバとの非同期通信というアプローチを採ったJavaScriptを主軸とした複合技術。 ページ遷移を行わずにコンテンツの動的更新が可能となる。

Next































Webアプリケーション実行環境の典型的構成

  • Web 3層構成
  • 
     ┌────────┐        ┌───┐    ┌───┐    ┌───┐
     │ Webクライアント│←──→│      │    │      │    │      │
     └────────┘        │      │    │      │    │      │
                                 │      │    │      │    │      │
                                 │      │    │      │    │      │
     ┌────────┐        │ Web  │    │  AP  │    │  DB  │
     │ Webクライアント│←──→│サーバ│←→│サーバ│←→│サーバ│
     └────────┘        │      │    │      │    │      │
                                 │      │    │      │    │      │
                                 │      │    │      │    │      │
                                 │      │    │      │    │      │
     ┌────────┐        │      │    │      │    │      │
     │ Webクライアント│←──→│      │    │      │    │      │
     └────────┘        └───┘    └───┘    └───┘
                                                                            
    
  • Webサーバ
  • WebクライアントからのHTTPによるアクセス要求を分散処理する。

  • APサーバ
  • HTTPトランザクションの一貫性を保持し、システム固有の処理をおこない、バックエンドで動作するデータベースなどの検索/加工処理などを司る。 今回の演習ではTomcatを使用する。

  • DBサーバ
  • システムのデータや管理情報を司る。今回の演習ではPostgreSQLを使用する。

Next































[演習]会議室予約システムの仕様

  • ユーザIDとメールアドレスを入力して、アカウント作成またはログイン
  • Enter your user name and mail address

    User Name Mail Address
  • 会議室の一覧を確認し、予約したい期間を入力
  • (User ID = 2)

    List of rooms

    NameCapacity
    room#0110
    room#0210
    room#0320
    room#0420
    room#0530

    From:
    Date Time

    To :
    Date Time

Next































[演習]会議室予約システムの仕様(続き)

  • 予約したい期間に空いている会議室の検索結果から、予約したい会議室を選択し予約確定
  • (User ID = 2)

    Available rooms

    NameCapacity
    room#0110
    room#0210
    room#0320
    room#0420
    room#0530

    From:
    Date Time

    To :
    Date Time

    Room(s):
  • 予約内容を確認し、ログアウト
  • (User ID = 2)

    Confirmation

    From:2012-11-01 10
    To:2012-11-01 11
    Rooms:1

Next































[演習]会議室予約システムの構築準備: DB作成

  • 以下のSQLを実行し、会議室予約システムで使用するDBを作成する。
  •    1  DROP SEQUENCE user_master_uids_seq;
       2
       3  CREATE SEQUENCE user_master_uids_seq
       4          START WITH 1
       5          INCREMENT BY 1
       6          NO MAXVALUE
       7          NO MINVALUE
       8          CACHE 1;
       9
      10  DROP TABLE user_master;
      11
      12  CREATE TABLE user_master (
      13          uid           INT
      14                        DEFAULT NEXTVAL('user_master_uids_seq'::text) NOT NULL,
      15          user_name     VARCHAR(128)
      16                        NOT NULL,
      17          mail_address  VARCHAR(128)
      18                        NOT NULL,
      19
      20          CONSTRAINT user_master_pk PRIMARY KEY (
      21                  uid
      22          )
      23  );
      24
      25  INSERT INTO user_master(user_name, mail_address)
      26      VALUES('iwasaki', 'iwasaki@freebsd.org');
      27
      28  DROP TABLE room_master;
      29
      30  CREATE TABLE room_master (
      31          rid           INT,
      32          room_name     VARCHAR(128),
      33          capacity      INT,
      34
      35          CONSTRAINT room_master_pk PRIMARY KEY (
      36                  rid
      37          )
      38  );
      39
      40  INSERT INTO room_master(rid, room_name, capacity) VALUES(1, 'room#01', 10);
      41  INSERT INTO room_master(rid, room_name, capacity) VALUES(2, 'room#02', 10);
      42  INSERT INTO room_master(rid, room_name, capacity) VALUES(3, 'room#03', 20);
      43  INSERT INTO room_master(rid, room_name, capacity) VALUES(4, 'room#04', 20);
      44  INSERT INTO room_master(rid, room_name, capacity) VALUES(5, 'room#05', 30);
      45
      46  DROP TABLE room_reservation;
      47
      48  CREATE TABLE room_reservation (
      49          rid           INT,
      50          reserved_from TIMESTAMP,
      51          reserved_to   TIMESTAMP,
      52          uid           INT,
      53
      54          CONSTRAINT room_reservation_pk PRIMARY KEY (
      55                  rid,
      56                  reserved_from,
      57                  reserved_to
      58          )
      59  );
      60
      61  INSERT INTO room_reservation(rid, reserved_from, reserved_to, uid)
      62          VALUES(1, TO_TIMESTAMP('2012-11-01 08', 'yyyy-mm-dd hh24'),
      63                    TO_TIMESTAMP('2012-11-01 12', 'yyyy-mm-dd hh24'), 1);
      64  INSERT INTO room_reservation(rid, reserved_from, reserved_to, uid)
      65          VALUES(2, TO_TIMESTAMP('2012-11-01 07', 'yyyy-mm-dd hh24'),
      66                    TO_TIMESTAMP('2012-11-03 20', 'yyyy-mm-dd hh24'), 1);
    
    

Next































[演習]会議室予約システムの構築準備: DB作成(続き)

  • DB作成用のSQLスクリプトファイルを用意しているので、それをpsqlへリダイレクト入力してDBを作成する。
  • $ psql < create_db.sql
    DROP SEQUENCE
    CREATE SEQUENCE
    DROP TABLE
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "user_master_pk"
     for table "user_master"
    CREATE TABLE
    INSERT 0 1
    DROP TABLE
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "room_master_pk"
     for table "room_master"
    CREATE TABLE
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    DROP TABLE
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "room_reservation_pk"
     for table "room_reservation"
    CREATE TABLE
    INSERT 0 1
    INSERT 0 1
    DROP FUNCTION
    CREATE FUNCTION
     is_occupied 
    -------------
     f
    (1 row)
    [snip]
     is_occupied
    -------------
     f
    (1 row)
    $
    

Next































[演習]会議室予約システムの構築準備: DB作成(続き2)

  • psqlを起動し、作成したDBの内容を確認する。
  • $ psql
    psql (8.4.4)
    Type "help" for help.
    
    iwasaki=> \?
    General
      \copyright             show PostgreSQL usage and distribution terms
      \g [FILE] or ;         execute query (and send results to file or |pipe)
      \h [NAME]              help on syntax of SQL commands, * for all commands
      \q                     quit psql
    
    Query Buffer
      \e [FILE]              edit the query buffer (or file) with external editor
      \ef [FUNCNAME]         edit function definition with external editor
      \p                     show the contents of the query buffer
      \r                     reset (clear) the query buffer
      \s [FILE]              display history or save it to file
      \w FILE                write query buffer to file
    
    Input/Output
      \copy ...              perform SQL COPY with data stream to the client host
      \echo [STRING]         write string to standard output
      \i FILE                execute commands from file
      \o [FILE]              send all query results to file or |pipe
      \qecho [STRING]        write string to query output stream (see \o)
    
    Informational
      (options: S = show system objects, + = additional detail)
      \d[S+]                 list tables, views, and sequences
      \d[S+]  NAME           describe table, view, sequence, or index
      \da[+]  [PATTERN]      list aggregates
      \db[+]  [PATTERN]      list tablespaces
      \dc[S]  [PATTERN]      list conversions
      \dC     [PATTERN]      list casts
      \dd[S]  [PATTERN]      show comments on objects
      \dD[S]  [PATTERN]      list domains
      \des[+] [PATTERN]      list foreign servers
      \deu[+] [PATTERN]      list user mappings
      \dew[+] [PATTERN]      list foreign-data wrappers
      \df[antw][S+] [PATRN]  list [only agg/normal/trigger/window] functions
      \dF[+]  [PATTERN]      list text search configurations
      \dFd[+] [PATTERN]      list text search dictionaries
      \dFp[+] [PATTERN]      list text search parsers
      \dFt[+] [PATTERN]      list text search templates
      \dg[+]  [PATTERN]      list roles (groups)
      \di[S+] [PATTERN]      list indexes
      \dl                    list large objects, same as \lo_list
      \dn[+]  [PATTERN]      list schemas
      \do[S]  [PATTERN]      list operators
      \dp     [PATTERN]      list table, view, and sequence access privileges
      \ds[S+] [PATTERN]      list sequences
      \dt[S+] [PATTERN]      list tables
      \dT[S+] [PATTERN]      list data types
      \du[+]  [PATTERN]      list roles (users)
      \dv[S+] [PATTERN]      list views
      \l[+]                  list all databases
      \dF[+]  [PATTERN]      list text search configurations
      \dFd[+] [PATTERN]      list text search dictionaries
      \dFp[+] [PATTERN]      list text search parsers
      \dFt[+] [PATTERN]      list text search templates
      \dg[+]  [PATTERN]      list roles (groups)
      \di[S+] [PATTERN]      list indexes
      \dl                    list large objects, same as \lo_list
      \dn[+]  [PATTERN]      list schemas
      \do[S]  [PATTERN]      list operators
      \dp     [PATTERN]      list table, view, and sequence access privileges
      \ds[S+] [PATTERN]      list sequences
      \dt[S+] [PATTERN]      list tables
      \dT[S+] [PATTERN]      list data types
      \du[+]  [PATTERN]      list roles (users)
      \dv[S+] [PATTERN]      list views
      \l[+]                  list all databases
      \z      [PATTERN]      same as \dp
    
    Formatting
      \a                     toggle between unaligned and aligned output mode
      \C [STRING]            set table title, or unset if none
      \f [STRING]            show or set field separator for unaligned query output
      \H                     toggle HTML output mode (currently off)
      \pset NAME [VALUE]     set table output option
                             (NAME := {format|border|expanded|fieldsep|footer|null|
                              numericlocale|recordsep|tuples_only|title|tableattr|
                              pager})
      \t [on|off]            show only rows (currently off)
      \T [STRING]            set HTML <table> tag attributes, or unset if none
      \x [on|off]            toggle expanded output (currently off)
    
    Connection
      \c[onnect] [DBNAME|- USER|- HOST|- PORT|-]
                             connect to new database (currently "iwasaki")
      \encoding [ENCODING]   show or set client encoding
      \password [USERNAME]   securely change the password for a user
    
    Operating System
      \cd [DIR]              change the current working directory
      \timing [on|off]       toggle timing of commands (currently off)
      \! [COMMAND]           execute command in shell or start interactive shell
    
    Variables
      \prompt [TEXT] NAME    prompt user to set internal variable
      \set [NAME [VALUE]]    set internal variable, or list all if no parameters
      \unset NAME            unset (delete) internal variable
    
    Large Objects
      \lo_export LOBOID FILE
      \lo_import FILE [COMMENT]
      \lo_list
      \lo_unlink LOBOID      large object operations
    
    iwasaki=> \d
                     List of relations
     Schema |         Name         |   Type   |  Owner
    --------+----------------------+----------+---------
     public | room_master          | table    | iwasaki
     public | room_reservation     | table    | iwasaki
     public | user_master          | table    | iwasaki
     public | user_master_uids_seq | sequence | iwasaki
    (4 rows)
    
    -- room_masterのすべてのレコードを取得
    iwasaki=> select * from room_master;
     rid | room_name | capacity
    -----+-----------+----------
       1 | room#01   |       10
       2 | room#02   |       10
       3 | room#03   |       20
       4 | room#04   |       20
       5 | room#05   |       30
    (5 rows)
    
    -- room_reservationのすべてのレコードを取得
    iwasaki=> select * from room_reservation;
     rid |    reserved_from    |     reserved_to     | uid
    -----+---------------------+---------------------+-----
       1 | 2012-11-01 08:00:00 | 2012-11-01 12:00:00 |   1
       2 | 2012-11-01 07:00:00 | 2012-11-03 20:00:00 |   1
    (2 rows)
    
    -- user_masterのすべてのレコードを取得
    iwasaki=> select * from user_master;
     uid | user_name |    mail_address
    -----+-----------+---------------------
       1 | iwasaki   | iwasaki@freebsd.org
    (1 row)
    
    -- room_reservationとuser_masterを内部結合してレコードを取得
    iwasaki=> select r.*, u.user_name from room_reservation r, user_master u
    iwasaki-> where r.uid = u.uid;
     rid |    reserved_from    |     reserved_to     | uid | user_name
    -----+---------------------+---------------------+-----+-----------
       1 | 2012-11-01 08:00:00 | 2012-11-01 12:00:00 |   1 | iwasaki
       2 | 2012-11-01 07:00:00 | 2012-11-03 20:00:00 |   1 | iwasaki
    (2 rows)
    
    -- room_masterにroom_reservationを外部結合して
    -- room_masterのすべてのレコードとroom_reservationの対応するレコードを取得
    iwasaki=> select m.*, r.uid from room_master m
    iwasaki->   left outer join
    iwasaki->   (select * from room_reservation
    iwasaki->    where ((TO_TIMESTAMP('2012-11-01 07', 'yyyy-mm-dd hh24') 
    iwasaki->              between reserved_from and reserved_to) or
    iwasaki->           (TO_TIMESTAMP('2012-11-01 09', 'yyyy-mm-dd hh24')
    iwasaki->              between reserved_from and reserved_to))
    iwasaki->   ) r
    iwasaki->   on m.rid = r.rid;
     rid | room_name | capacity | uid
    -----+-----------+----------+-----
       1 | room#01   |       10 |   1
       2 | room#02   |       10 |   1
       3 | room#03   |       20 |
       4 | room#04   |       20 |
       5 | room#05   |       30 |
    (5 rows)
    
    -- room_reservation更新のトランザクション
    iwasaki=> begin;
    BEGIN
    iwasaki=> update room_reservation set uid = 2 where rid = 1;
    UPDATE 1
    iwasaki=> select * from room_reservation;
     rid |    reserved_from    |     reserved_to     | uid
    -----+---------------------+---------------------+-----
       1 | 2012-11-01 08:00:00 | 2012-11-01 12:00:00 |   2
       2 | 2012-11-01 07:00:00 | 2012-11-03 20:00:00 |   1
    (2 rows)
    
    -- 更新をロールバックしてトランザクション取り消し
    iwasaki=> rollback;
    ROLLBACK
    iwasaki=> select * from room_reservation;
     rid |    reserved_from    |     reserved_to     | uid
    -----+---------------------+---------------------+-----
       1 | 2012-11-01 08:00:00 | 2012-11-01 12:00:00 |   1
       2 | 2012-11-01 07:00:00 | 2012-11-03 20:00:00 |   1
    (2 rows)
    
    iwasaki=> \q
    $
    

Next































[演習]会議室予約システムの構築準備: Javaクラスのコンパイル

  • JSPで使用するJavaクラスを、Javaコンパイラjavacを使用して以下の手順でコンパイルする。
  • $ cd course/day-2/roomReservation/WEB-INF/classes/
    $ javac -classpath . simpleApp/*.java
    
  • Javaクラスは2つあり、ひとつはDBアクセスをおこなうクラスDataAccess、もうひとつはユーザ管理をおこなうクラスUserManagerである。
  • この二つのクラスはパッケージsimpleAppとしてまとめられている。
  • 以下はクラスDataAccessのソース。シンプルなデータベース操作を実装している。
  •    1  package simpleApp;
       2
       3  import  java.sql.*;
       4  import  javax.sql.*;
       5  import  javax.naming.*;
       6
       7  public class DataAccess
       8  {
       9          private DataSource              ds_;
      10          private Connection              con_;
      11          private Statement               st_;
      12          private ResultSet               rs_;
      13
      14          public DataAccess()
      15          {
      16                  ds_ = null;
      17                  con_ = null;
      18                  st_ = null;
      19                  rs_ = null;
      20          }
      21
      22          public synchronized void open()
      23                  throws Exception
      24          {
      25                  Context ctx = new InitialContext();
      26                  ds_ = (DataSource)ctx.lookup("java:/comp/env/jdbc/postgres");
      27                  con_ = ds_.getConnection();
      28                  st_ = con_.createStatement();
      29          }
      30
      31          public ResultSet getResultSet(String sql)
      32                  throws Exception
      33          {
      34                  if (st_.execute(sql)) {
      35                          rs_ = st_.getResultSet();
      36                          return rs_;
      37                  }
      38                  return null;
      39          }
      40
      41          public void execute(String sql)
      42                  throws Exception
      43          {
      44                  st_.execute(sql);
      45          }
      46
      47          public PreparedStatement prepareStatement(String sql)
      48                  throws Exception
      49          {
      50                  return con_.prepareStatement(sql);
      51          }
      52
      53          public synchronized void close()
      54                  throws Exception
      55          {
      56                  if (rs_ != null ) {
      57                          rs_.close();
      58                  }
      59                  if (st_ != null ) {
      60                          st_.close();
      61                  }
      62                  if (con_ != null ) {
      63                          con_.close();
      64                  }
      65          }
      66  }
      67
    

Next































[演習]会議室予約システムの構築準備: Javaクラスのコンパイル(続き)

  • 以下はクラスUserManagerのソース。クラスDataAccessを使用し、ユーザ管理を実装している。
  •    1  package simpleApp;
       2
       3  import  java.sql.*;
       4  import  simpleApp.*;
       5
       6  public class UserManager
       7  {
       8          private DataAccess      da_;
       9
      10          public UserManager()
      11          {
      12                  da_ = new DataAccess();
      13          }
      14
      15          public int search(String user_name, String mail_address)
      16                  throws Exception
      17          {
      18                  int     uid = -1;
      19
      20                  try {
      21                          da_.open();
      22
      23                          String  sql = "SELECT uid FROM user_master WHERE " +
      24                                  "user_name=? AND mail_address=?";
      25                          PreparedStatement ps = da_.prepareStatement(sql);
      26                          ps.setString(1, user_name);
      27                          ps.setString(2, mail_address);
      28                          ResultSet rs = ps.executeQuery();
      29                          if (rs.next()) {
      30                                  uid = rs.getInt("uid");
      31                          }
      32                          rs.close();
      33                          ps.close();
      34                  } finally {
      35                          da_.close();
      36                  }
      37
      38                  return uid;
      39          }
      40
      41          public int add(String user_name, String mail_address)
      42                  throws Exception
      43          {
      44                  try {
      45                          da_.open();
      46
      47                          String sql = "INSERT INTO user_master" +
      48                                  "(user_name, mail_address) VALUES(?, ?)";
      49
      50                          PreparedStatement ps = da_.prepareStatement(sql)
      51                          ps.setString(1, user_name);
      52                          ps.setString(2, mail_address);
      53                          ps.executeUpdate();
      54
      55                          ps.close();
      56                  } finally {
      57                          da_.close();
      58                  }
      59
      60                  return search(user_name, mail_address);
      61          }
      62  }
      63
    
  • Java APIについては、以下のURLなどを参照。
  • http://java.sun.com/javase/ja/6/docs/ja/api/

Next































[演習]会議室予約システムの構築

  • ミッション1
  • 会議室の検索結果に、予約希望期間にすでに予約が入っている会議室も含まれているため、 その会議室を予約処理するとダブルブッキングが生じてしまう。 会議室の検索結果の表示の際にすでに予約済みの会議室が選択できないよう、search.jspを修正せよ。
    ヒント: ストアド関数is_occupied()の判定結果を基に、checkboxのdisabledを制御

    is_occupied(?, TO_TIMESTAMP('2012-11-01 08', 'yyyy-mm-dd hh24'),
                   TO_TIMESTAMP('2012-11-01 10', 'yyyy-mm-dd hh24'))
    

  • ミッション2
  • 会議室の検索結果の表示の際にすでに予約済みの会議室があった場合、予約した人と連絡が取れるよう e-mailアドレスが表示されると便利である。この要望に合うよう、search.jspを修正せよ。
    ヒント: room_masterにroom_reservationおよびuser_masterを外部結合

    NameCapacityReserved by
    room#0110 
    room#0210iwasaki@freebsd.org
    room#0320 
    room#0420 
    room#0530 

  • ミッション3
  • ミッション1でおこなった修正は会議室一覧の情報を取得しながら、会議室ごとに予約済みかを判定して会議室一覧の 情報を加工するものであり、扱う会議室の数が増えれば性能の問題が発生することは容易に想像できる。 この一連のDBへのアクセスは、実は一回のSELECTでおこなうことが可能である。 その方法を検討し、処理を効率的におこなうようsearch.jspを修正せよ。
    ヒント: ミッション2での修正により予約済みかの判定が可能

  • ミッション4
  • ログイン後、自分が予約した会議室の予約情報を表示し、取り消し処理がおこなえるようにしたい。 search.jspを修正し、取り消し処理をおこなうcancel.jspを作成せよ。 なお、cancel.jspでは予約取り消し(Cancel Reservations)ボタンとsearch.jspへ戻る(Back)ボタンを用意すること。

  • ミッション5
  • 複数の会議室を一度に予約する場合、他のユーザの処理と競合してデータの矛盾が発生する可能性がある。 simpleApp.DataAccessクラスを拡張し、トランザクションの機能を追加せよ。 またreserve.jspをトランザクションを使用してデータの矛盾が起こらないように修正せよ。
    具体的には、INSERT処理の前処理としてroom_masterの該当レコードをSELECT...FOR UPDATEでロック、 対象の会議室が予約希望期間に空いていることを最終確認し(空いていない場合はROLLBACKでトランザクションを終了させる)、 room_reservationへのINSERT処理をおこない、すべての会議室の予約が成功したらCOMMITでトランザクションを確定させる。 処理の途中で例外が発生した場合は、catch()ブロックの中でROLLBACKをおこなうこと。

  • ミッション6
  • このシステムではJSPファイルでSQLを記述しDBアクセスをおこなっているが、これはデザインとロジックの分離の観点から好ましくない。 simleApp.UserManagerクラスを参考に、会議室予約業務ロジックを担当するsimpleApp.ReservationManagerクラスを設計および実装し、 これを使用するようすべてのJSPファイルを修正せよ。

  • ミッション7
  • このシステムには隠された不具合が多数存在する。それを発見し修正せよ。

Next































3. 2時間で覚えるObjective-C

  • 特徴
    • C言語にSmalltalkライクなオブジェクト指向機能を持たせたC言語上位互換の言語
    • 同じくC言語をベースにしているC++とは、全く異なる言語仕様
    • @で始まるコンパイラディレクティブでクラス定義などをおこなう
    • メソッド呼び出しは[]で囲まれたSmalltalkライクなメッセージ式
    • Javaのような仮想マシンを持たずネイティブコンパイルされる
  • 歴史
    • 1983年にBrad Coxによって開発され、そのコンパイラやライブラリを支援するためにStepstone社が設立
    • 1988年にNeXT社のワークステーション用OSであるNEXTSTEPの開発言語として採用
    • 現在では主にアップルのMac OS XやiOS上で動作するアプリケーションの開発で利用

Next































メソッド呼びだしとクラス定義

  • メソッド呼びだし

    実行時のメッセージパッシングであり、その時渡されるメッセージ値をセレクタという

    // メッセージの送信
    // 単項メッセージ
    [receiver msg];
    // 引数付きメッセージ。この場合「msg:with:」で一つのセレクタ
    val = [receiver msg: arg1 with: arg2];
    // メッセージの入れ子
    val = [obj1 msg: [obj2 msg]];
    
    
  • クラス定義

    定義部(@interfaceディレクティブで開始)と実装部(@implementationディレクティブで開始)に分かれている

    // クラスの定義
    @interface MyObject : NSObject
    {
    	int val;
    	id obj;
    	double d;
    }
    
    + (void)classMethod: (id)arg;  // クラスメソッド
    - (id)method: (NSObject *)arg1 with: (int)arg2;  // インスタンスメソッド
    @end
    
    
    // 実装
    @implementation MyObject
    + (void)classMethod: (id)arg
    {
    	// 何かの処理
    }
     
    - (id)method: (NSObject *)arg1 with: (int)args2
    {
    	return obj;
    }
     
    // 典型的なinit
    - (id)init
    {
    	self = [super init]; // スーパークラスの呼びだし
    	if (self != nil) {
    		val = 1;
    		obj = [[SomeClass alloc] init];
    	}
    	return self;
    }
     
    // deallocは自身のリソースを解放してからスーパークラスへ処理を渡す
    - (void)dealloc
    {
    	[obj release];
    	[super dealloc];
    }
    @end
    
    

Next































Objective-Cの基本事項のまとめ

  • クラスメソッド(接頭辞+)とインスタンスメソッド(接頭辞-)
    + (void)classMethod: (id)arg;  // クラスメソッド
    - (id)method: (NSObject *)arg1 with: (int)arg2;  // インスタンスメソッド
    
  • インスタンス変数はあるが、クラス変数は無い
    @interface MyObject : NSObject {
    	int val;
    	id obj;
    	double d;
    }
    
    static int shared_val;	// クラス変数の代替手段
    
  • 慣習として新規オブジェクトの生成は+alloc(ファクトリメソッド)で、初期化は-init(イニシャライザ)
    id obj = [[MyObject alloc] init];
    
  • +newは+allocと-initをまとめておこなう
    id obj = [MyObject new];
    
  • 自分自身を示す特殊変数selfとスーパークラスを示す特殊名称super
    - (id)init
    {
    	self = [super init]; // スーパークラスの呼びだし
    	if (self != nil) {
    		val = 1;
    		obj = [[SomeClass alloc] init];
    	}
    	return self;
    }
    
  • オブジェクトを表す汎用型id
    id obj = [[MyObject alloc] init];
    obj = [[MyObject2 alloc] init];
    
  • 保護レベルは@public(公開)、@protected(継承クラスのみ)、@private(インスタンス内のみ)
    @interface MyObject : NSObject {
    @public
        int val;
    @protected
        id obj;
    @private
        double d;
    }
    
  • #includeの強化版#importプリプロセッサディレクティブ(多重includeしてもエラーとならない)
    #import <stdio.h>
    #import <Foundation/Foundation.h>
    
  • 一行コメントに//が使用可能
    /*
     * C言語のコメント
     */
    // 一行コメント
    

Next































Objective-Cのサンプルプログラム

    #import <stdio.h>
    #import <Foundation/Foundation.h>
    
    int
    main(int argc, char *argv[])
    {
    	int rv = 0;
    
    	id pool = [NSAutoreleasePool new];
    	{
    		if (argc != 2) {
    			NSString *msg =
    				[NSString stringWithFormat:
    					@"usage: %s url_string", argv[0]];
    			printf("%s\n", [msg UTF8String]);
    			rv = 1;
    			goto out;
    		}
    
    		NSString *address = [NSString stringWithCString: argv[1]];
    		NSURL *url = [NSURL URLWithString: address];
    		NSURLRequest *request = [NSURLRequest requestWithURL: url];
    		NSURLResponse *response = nil;
    		NSError *error = nil;
    		NSData *data = [NSURLConnection
    		                    sendSynchronousRequest: request
    		                    returningResponse: &response
    		                    error: &error];
    		if ([[error localizedDescription] length] > 0) {
    			NSLog(@"ConnectionError: %@",
    				[error localizedDescription]);
    			rv = 2;
    			goto out;
    		}
    
    		[data writeToFile: @"output.dat" atomically: YES];
    	}
    out:
    	[pool release];
    	return rv;
    }
    

Next































サンプルプログラムの解説

  • NSAutoreleasePoolは自動解放プール機能を提供するクラス
    • オブジェクト生成の際に暗黙的に自動解放プールに登録
    • プール解放のタイミングで登録されたオブジェクトを解放
    	id pool = [NSAutoreleasePool new];
    
    	[pool release];
    
  • NSStringは文字列を扱うクラス
    • @"Hello World"のような文字リテラルはNSStringクラスのインスタンスとして扱えるオブジェクト定数(NSConstantStringクラスのインスタンス)
    	NSString *msg =
    		[NSString stringWithFormat:
    			@"usage: %s url_string", argv[0]];
    	printf("%s\n", [msg UTF8String]);
    
    	NSString *address = [NSString stringWithCString: argv[1]];
    
  • NSURLはURL(Uniform Resource Locator)を扱うクラス
    • URLWithStringメッセージなどを使用してインスタンスを作成する
    	NSURL *url = [NSURL URLWithString: address];
    
  • NSURLRequestはURLをロードするためのリクエストをカプセル化したクラス
    • requestWithURLメッセージなどを使用してインスタンスを作成する
    	NSURLRequest *request = [NSURLRequest requestWithURL: url];
    
  • NSURLConnectionはURLに接続してデータ通信をおこなうクラス
    • 同期通信の場合はsendSynchronousRequest:returningResponse:error:メッセージを使用する
    	NSData *data = [NSURLConnection
    	                    sendSynchronousRequest: request
    	                    returningResponse: &response
    	                    error: &error];
    
  • NSURLResponseはサーバからのレスポンスを管理するクラス
    • nilで初期化しておく
    • レスポンスのContentLengthはexpectedContentLengthメッセージで取得可能
    	NSURLResponse *response = nil;
    
  • NSErrorはエラー情報をカプセル化したクラス
    • nilで初期化しておく
    • エラーメッセージはlocalizedDescriptionメッセージで取得可能
    	NSError *error = nil;
    
    	if ([[error localizedDescription] length] > 0) {
    
  • NSDataはバイナリデータ格納用のクラス
    • NSURLConnectionの同期通信のメッセージの戻り値を格納する
    	NSData *data = [NSURLConnection
    	                    sendSynchronousRequest: request
    	                    returningResponse: &response
    	                    error: &error];
    
    	[data writeToFile: @"output.dat" atomically: YES];
    
  • NSLogはコンソールに文字列を出力する関数
    • 書式はprintf()などで使用するものと互換性あり
    • %@はインスタンスを文字列化したものを出力する書式
    	NSLog(@"ConnectionError: %@", [error localizedDescription]);
    

Next































NSURLConnectionの同期通信と非同期通信

  • 同期通信はクライアントからサーバへリクエスト送信後、レスポンスが返ってくるまで待ち続ける
    	NSData *data = [NSURLConnection
    	                    sendSynchronousRequest: request
    	                    returningResponse: &response
    	                    error: &error];
    
    	// サーバからレスポンスが返ってくるまで停止
    
    	if ([[error localizedDescription] length] > 0) {
    		NSLog(@"ConnectionError: %@",
    			[error localizedDescription]);
    		rv = 2;
    		goto out;
    	}
    
  • 非同期通信はクライアントからサーバへリクエスト送信後、レスポンスを待たずに処理が進む
    	MyConnectDelegate *delegate = [[MyConnectDelegate alloc] init];
    	NSURLConnection *connection = [NSURLConnection
    					connectionWithRequest: request
    					delegate: delegate];
    
    	// サーバからのレスポンスを待たずに処理が進む
    
    	if (!connection) {
    		NSLog(@"Failed to init connection");
    		rv = 2;
    		goto out;
    	}
    
    	[[NSRunLoop currentRunLoop] run];
    
  • 非同期通信をおこなうにはDelegate(メッセージ処理の委譲)を使用する
  • NSConnectionオブジェクトへの入力の処理を待つため、現在のスレッドの実行ループを呼び出す

Next































NSURLConnectionの非同期通信のDelegate

  • NSURLConnectionのconnectionWithRequest:delegate:メッセージでDelegateへ委譲されるメッセージ処理には以下のようなものがある
    /**
     * Instructs the delegate that authentication for challenge has
     * been cancelled for the request loading on connection.
     */
    - (void) connection: (NSURLConnection *)connection
      didCancelAuthenticationChallenge:
        (NSURLAuthenticationChallenge *)challenge;
    
    /*
     * Called when an NSURLConnection has failed to load successfully.
     */
    - (void) connection: (NSURLConnection *)connection
      didFailWithError: (NSError *)error;
    
    /**
     * Called when an NSURLConnection has finished loading successfully.
     */
    - (void) connectionDidFinishLoading: (NSURLConnection *)connection;
    
    /**
     * Called when an authentication challenge is received ... the delegate
     * should send -useCredential:forAuthenticationChallenge: or
     * -continueWithoutCredentialForAuthenticationChallenge: or
     * -cancelAuthenticationChallenge: to the challenge sender when done.
     */
    - (void) connection: (NSURLConnection *)connection
      didReceiveAuthenticationChallenge:
        (NSURLAuthenticationChallenge *)challenge;
    
    /**
     * Called when content data arrives during a load operations ... this
     * may be incremental or may be the compolete data for the load.
     */
    - (void) connection: (NSURLConnection *)connection
      didReceiveData: (NSData *)data;
    
    /**
     * Called when enough information to build a NSURLResponse object has
     * been received.
     */
    - (void) connection: (NSURLConnection *)connection
      didReceiveResponse: (NSURLResponse *)response;
    
    /**
     * Called with the cachedResponse to be stored in the cache.
     * The delegate can inspect the cachedResponse and return a modified
     * copy if if wants changed to what whill be stored.
    * If it returns nil, nothing will be stored in the cache. */ - (NSCachedURLResponse *) connection: (NSURLConnection *)connection willCacheResponse: (NSCachedURLResponse *)cachedResponse; /** * Informs the delegate that the connection must change the URL of * the request in order to continue with the load operation.
    * This allows the delegate to ionspect and/or modify a copy of the request * before the connection continues loading it. Normally the delegate * can return the request unmodifield.
    * The redirection can be rejectected by the delegate calling -cancel * or returning nil.
    * Cancelling the load will simply stop it, but returning nil will * cause it to complete with a redirection failure.
    * As a special case, this method may be called with a nil response, * indicating a change of URL made internally by the system rather than * due to a response from the server. */ - (NSURLRequest *) connection: (NSURLConnection *)connection willSendRequest: (NSURLRequest *)request redirectResponse: (NSURLResponse *)response;
  • 詳細は以下を参照
  • GNUstep Base Library API
    (http://www.gnustep.org/resources/documentation/Developer/Base/Reference/index.html)

    /usr/local/GNUstep/System/Library/Headers/Foundation/

Next































NSURLConnectionのDelegate実装例

    @interface MyConnectDelegate : NSObject
    {
    	// 更新可能なバイナリデータ格納用オブジェクト
    	NSMutableData *result;
    }
    @end 
    
    @implementation MyConnectDelegate
    - (void)connection: (NSURLConnection *)connection
      didReceiveResponse: (NSURLResponse *)response
    {
    	NSLog(@"didRecieveResponse: %@", response);
    	// レスポンスを受け取ったらNSMutableDataインスタンス生成
    	result = [[NSMutableData alloc] init];
    }
    
    - (void)connection: (NSURLConnection *)connection
      didReceiveData: (NSData *)data
    {
    	NSLog(@"didReceiveData");
    	// データを受け取ったらNSMutableDataインスタンスへ追加
    	[result appendData: data];
    }
    
    - (void)connectionDidFinishLoading: (NSURLConnection *)connection
    {
    	NSLog(@"connectionDidFinishLoading");
    	// ロードが完了したらファイルへ出力
    	[result writeToFile: @"output.dat" atomically: YES];
    }
    @end
    

Next































NSURLConnectionでPOSTメソッドのリクエストを送信

  • NSRLRequestの代りにNSMutableURLRequestを使用してPOSTメソッドのリクエストを組み立てる
  • 
    NSString *address = @"http://localhost:8180/roomReservation/login2.jsp";
    NSURL *url = [NSURL URLWithString: address];
    
    // POSTデータを作成
    NSString *param = @"user_name=iwasaki&mail_address=iwasaki@freebsd.org";
    NSData *body = [param dataUsingEncoding: NSUTF8StringEncoding];
    
    // 更新可能なrequest
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: url];
    [request setHTTPMethod: @"POST"];
    [request setValue: @"application/x-www-form-urlencoded"
    	forHTTPHeaderField: @"content-type"];
    [request setHTTPBody: body];
    
    NSURLResponse *response = nil;
    NSError *error = nil;
    NSData *data = [NSURLConnection
                        sendSynchronousRequest: request
                        returningResponse: &response
                        error: &error];
    

Next































[演習]Objective-C

  • ミッション1
  • サンプルプログラムで解説したurlget.mは、引数で指定されたURLにアクセスして コンテンツをファイルoutput.datに格納する。 生成されたoutput.datのサイズが正しいかを確認できるよう、レスポンスの ContentLengthをコンソールに出力するよう修正せよ。

     $ ./urlget http://localhost:8180/
    
  • ミッション2
  • urlget2.mはDelegateを使用したサンプルプログラムの非同期通信版である。 GUIアプリケーションでは同期通信をおこなうと画面表示処理が止まってしまうため、 非同期通信をおこなうことが多い。 urlget2.mの実行中にダウンロードの進行状況をパーセント表示するよう、プログラムを修正せよ。

     $ ./urlget2 http://localhost:8180/docs/changelog.html
    [snip]
     44 % completed (116768 / 260027 bytes)
    [snip]
    100 % completed (260027 / 260027 bytes)
    
  • ミッション3
  • urlget2の非同期通信中にエラーが発生した場合でも、プログラムの終了コードは0となってしまう。 実行ループ終了後にDelegateから通信成功/失敗のステータスを取得できるようにし、失敗していた場合に終了コード3となるようにプログラムを修正せよ。

    
    
    
  • ミッション4(おまけ)
  • logincheck.mは会議室予約システムのアカウントログイン確認プログラムである。 http://localhost:8180/roomReservation/login2.jspをターゲットに引数で指定されたuser_nameとmail_addressをPOSTメソッドで送信する。 ログイン後の遷移先コンテンツに文字列「List of rooms」が含まれるか否かでログイン成否を判断している。 現状このプログラムは未完成であるが、正しく動作するようプログラムを修正せよ。

     $ ./logincheck iwasaki iwasaki@freebsd.org
    

Next