« 2012年06月 | メイン | 2012年08月 »

2012年07月 アーカイブ

2012年07月03日

おとうさんはプログラマ その1 〜アイちゃん、プログラムのせかいを知る〜

アイちゃんは好奇心旺盛(こうきしんおうせい)な女の子。
ある日、アイちゃんはおとうさんのおしごとにきょうみをもちました。

「おとうさん、おとうさん」
「ン? アイちゃん、なに?」
「おとうさんのおしごとって、プログラマなんでしょ?」
「うん、そうだよ」
「プログラマってどういうことするの?」
「そうだね。たとえば、パソコンやスマートフォンでうごくアプリケーションを作ってるんだ」
「へぇ。どうやって作るの?」
「むずかしいこと聞くね。プログラムというのを書くんだけど・・・。う〜ん、どうやってお話したらわかりやすいかなぁ」

その時、おとうさんはゆかにころがるオモチャのロボットに気づきました。

「よし、おてつだいロボットを作ることを考えてみようか」
「おてつだいロボット?」
「うん。おてつだいロボットにやってほしいことをつたえるプログラムを考えてみよう」
「何をしてもらおうかなぁ」
「じゃあ、カレーを作ってもらう方法(ほうほう)にしよう」
「あれ? 『カレーを作って!』じゃだめなの?」
「おてつだいロボットはカレーを知らないよ。それに、料理(りょうり)のやり方も知らないよ」
「え〜」
「何にも知らないあかちゃんみたいなものなんだ。だからやることをぜんぶ教えてあげないといけないよ」
「じゃあ、カレーの作り方をじゅんばんに教えてあげるといいの?」
「そうだね。やってみようか」

そう言って、おとうさんはかみをつくえの上に広げ、その上にえんぴつをおきました。

「ここに、ロボットにやってほしいことを書いてみよう」
「うん」

アイちゃんはちょっと考えて、紙にやってほしいことを書きだしました。

「できた!」
「見せて見せて」
ざいりょうを切る
にる
カレーのルーを入れる
おさらにごはんを入れる
ごはんにカレーをかける
「おてつだいロボットの気もちになって考えてみようか。『ざいりょうってなんだろう』っと思わないかな」
「え? それも教えてあげないといけないの?」
「そうだよ。おてつだいロボットは何も知らないから」
「う〜ん。じゃあこんなかんじかな?」
にんじんを切る
じゃがいもを切る
タマネギを切る
おにくを切る
にる
カレーのルーを入れる
おさらにごはんを入れる
ごはんにカレーをかける
「どうやってにるのかな?」
「あ〜、『にる』じゃロボットさんはわからないのかぁ。じゃあこれならどうかな」
にんじんを切る
じゃがいもを切る
タマネギを切る
おにくを切る
ナベにざいりょうを入れる
ナベに水を入れる
火をつける
カレーのルーを入れる
おさらにごはんを入れる
ごはんにカレーをかける
「そうだね。もっとおいしいカレーを作るために、ちょっと書きかえていいかな?」
「うん。いいよ!」
にんじんを切る
じゃがいもを切る
タマネギを切る
おにくを切る
火をつける
ナベにざいりょうを入れる
いためる(やく)
ナベに水を入れる
カレーのルーを入れる
おさらにごはんを入れる
ごはんにカレーをかける
「なんか、おかあさんのお料理(りょうり)の本みたいになってきたね」
「そうだね。でも、おてつだいロボットは、おかあさんみたいに料理(りょうり)のことを知らないから、もっとやり方をおしえてあげないといけないんだ」
「え〜、もっとなの?」
「うん。たとえばこんなかんじ」

おとうさんがつぎつぎを書いていくのをアイちゃんは目を丸くして見ています。
かわむききをじゅんびする
ほうちょうをじゅんびする
まないたをじゅんびする
ボウルをじゅんびする
ナベをじゅんびする
オタマをじゅんびする
にんじんを切る
じゃがいもを切る
タマネギを切る
おにくを切る
コンロにナベをのせる
火をつける
あぶらを入れる
ナベにざいりょうを入れる
いためる(やく)
ナベに水を入れる
まつ
カレーのルーを入れる
こげないようにかきまぜる
おさらをじゅんびする
すいはんきをひらく
おさらにごはんを入れる
すいはんきをとじる
ごはんにカレーをかける
「うわぁ、たくさんあるねぇ」
「もっとくわしく書けるけどとりあえずこのぐらいにしておこうか」
「えっ!まだ足りないの?」
「うん。ぜんぜん足りないよ」
「ええっ!」
「たとえば、ほうちょうがどこにあるのか教えてあげないといけないし、ほうちょうが引き出しに入っているなら引き出しのあけ方も教えてあげないといけないんだよ」
「ロボットって頭がいいって思ってたんだけどちがうんだね・・・」
「うん。頭がいいようにみえるのは、そういうふうにプログラムされているからなんだ」
「そのプログラムを作るのがプログラマのおしごとなんだね!おとうさん、すごい!」

おとうさんは、心の中で大よろこびしているのをアイちゃんに気づかれないように言いました。

「プログラムでは、こうやっておてつだいロボットにおねがいをしていくんだけど、このひとつひとつを『命令(めいれい)』っていうんだ」
「『命令(めいれい)』ってなんかえらい人みたいだね」
「たしかに、人にいうとこわいことばにきこえるね。こういう『命令(めいれい)』をたくさんあつめてプログラムを作るんだよ。せっかくだから、プログラムがどういうものかつづきをやってみる?」
「うん!」
「よし!その前にちょっときゅうけい!」

(つづく)

おとうさんはプログラマ その2 〜アイちゃん、プログラムの『くりかえし』を知る〜

おとうさんにプログラマがどういうものなのか教えてもらったアイちゃん。
こんどは、もうすこしくわしくプログラムを教えてもらうことになりました。

「じゃあつづきをはじめるよ〜」
「は〜い!」
「さっきの紙を見せて」
かわむききをじゅんびする
ほうちょうをじゅんびする
まないたをじゅんびする
ボウルをじゅんびする
ナベをじゅんびする
オタマをじゅんびする
にんじんを切る
じゃがいもを切る
タマネギを切る
おにくを切る
コンロにナベをのせる
火をつける
あぶらを入れる
ナベにざいりょうを入れる
いためる(やく)
ナベに水を入れる
まつ
カレーのルーを入れる
こげないようにかきまぜる
おさらをじゅんびする
すいはんきをひらく
おさらにごはんを入れる
すいはんきをとじる
ごはんにカレーをかける
「こんどはこの『にんじんを切る』って『命令(めいれい)』をもっとくわしく書いてみようか」

そう言いながら、おとうさんは新しい紙をつくえの上におきました。

「は〜い」
にんじんのかわをむく
にんじんを小さく切る
「う〜ん。これぐらいしか思いつかないなぁ」
「じゃあ、おとうさんが書いてみよう」
れいぞうこからにんじんをとり出す
にんじんのかわをむく
にんじんをまないたの上におく
にんじんを小さく切る
にんじんをボウルに入れる
「こんどは『にんじんを小さく切る』をもっともっとくわしく書いてみよう」
「そうか!おてつだいロボットは、にんじんの切り方や、どれくらい小さく切るのかわからないんだね!」
「そうだね。じゃあ、書いてみようか」
にんじんをたてにおく
にんじんを半分に切る
にんじんをよこにおく
にんじんを2cmずつ切る
「おっ、アイちゃん、だんだんわかってきたね」
「すごいでしょ!」
「でも、おてつだいロボットは『ずつ』ってことばもわからないんだよ」
「えー!『ずつ』は『ずつ』だよぉ〜」
「『にんじんを2cmずつ切る』をもっとくわしく書けるかな?ほうちょうのうごきをていねいに書いてごらん」
「う〜ん・・・」
にんじんの右のはしっこにほうちょうを合わせる
ほうちょうを上にあげる
ほうちょうを2cm左にうごかす
ほうちょうを下にさげる
書きつづけているアイちゃんのうしろでおとうさんはニコニコしています。
にんじんの右のはしっこにほうちょうを合わせる
ほうちょうを上にあげる
ほうちょうを2cm左にうごかす
ほうちょうを下にさげる
ほうちょうを上にあげる
ほうちょうを2cm左にうごかす
ほうちょうを下にさげる
ほうちょうを上にあげる
ほうちょうを2cm左にうごかす
ほうちょうを下にさげる
「ストップ!」

おとうさんがとつぜん大きな声で言いました。

「びっくりした!おとうさん、大きな声出さないでよ〜」
「ごめんごめん。でも、今アイちゃんが書いていた『命令(めいれい)』を見てどう思う?」
「何回も同じこと書いていてつかれた」
「そうだよね。ちょっとムダだよね。こういう時にはロボットに『反復(はんぷく)』をおねがいするんだ」
「『反復(はんぷく)』?」
「うん。かんたんに言うと『くりかえす』ことだね。ちょっとおとうさんが書きかえてみるね」
にんじんの右のはしっこにほうちょうを合わせる
ここからくりかえす
  ほうちょうを上にあげる
  ほうちょうを2cm左にうごかす
  ほうちょうを下にさげる
ここまでをくりかえす
「あっ!すごい!かんたんになった!」
「『命令(めいれい)』を何回も書くのはつかれるけど、おてつだいロボットはつかれないからね。こういう時には『くりかえす』『命令(めいれい)』を教えてあげれば、アイちゃんはらくちんだよね」
「うん。らくちんにはなったけど...。『にる』はわからないのに『くりかえす』ことはわかるのね」
「あ〜、いいところに気づいたね。おてついロボットが『くりかえす』ことを知らなくても『にる』ことは教えられるよね。でも『にる』ことを知っていても『くりかえす』ことは教えられないんだ」
「えっ?どういうこと?」
「ロボットに歩く『命令(めいれい)』を考えてみようか」
「う〜ん、一回歩くことは教えられそう」
「それで一歩だね」
「もう一歩すすもうとするとまた『命令(めいれい)』しないと・・・」
「それで二歩だよね」
「それを何回も『命令(めいれい)』する」
「じゃあ、『くりかえす』ことを『命令(めいれい)』できるかな?」
「『今言ったことをくりかして!』って言いたくなっちゃう」
「そう。どれだけアイちゃんがくふうしても、『くりかえす』ことは『命令(めいれい)』できないんだ。だから、おてつだいロボットは本当に大切な『命令(めいれい)』だけはわかるようになってるんだ」
「何も知らないあかちゃんだって言ってたのに・・・」
「ごめんごめん。でも、ロボットが知っている『命令(めいれい)』は本当に少ししかないんだよ」
「もしかして、ほかにもあるの?」
「うん。みっつあるんだけど、じゅんばんに教えてあげるからお楽しみ!」

(つづく)

おとうさんはプログラマ その3 〜アイちゃん、プログラムの『分かれ道』を知る〜

「じゃあ、さっきの話にもどってにんじんを切る『命令(めいれい)』のつづきを見てみよう」
にんじんの右のはしっこにほうちょうを合わせる
ここからくりかえす
  ほうちょうを上にあげる
  ほうちょうを2cm左にうごかす
  ほうちょうを下にさげる
ここまでをくりかえす
「何回も何回も『くりかえす』とどうなるか考えてみて」
「にんじんがぜんぶちいさくなる!」
「じゃあ、100回『くりかえす』とどうなるかな?」
「え〜、100回やらなくてもだいじょうぶだよぉ〜」
「でも、このままだとおてついロボットはずっと『くりかえす』よ。さいごまで切りおわったことを、おてつだいロボットに教えてあげないといけないね」
「う〜ん、むずかしいよぉ」
「よし!じゃあヒント!ロボットさんにしつもんしてごらん」
「しつもん?」
「そうだよ。しつもん!」
「うん」
にんじんの右のはしっこにほうちょうを合わせる
ここからくりかえす
  ほうちょうを上にあげる
  ほうちょうを2cm左にうごかす
  ほうちょうを下にさげる
  さいごまで切りおわった?
ここまでをくりかえす
「これでいいの?」
「そうだね。よくできました。ここからはおとうさんが書くね」
にんじんの右のはしっこにほうちょうを合わせる
ここからくりかえす
  ほうちょうを上にあげる
  ほうちょうを2cm左にうごかす
  ほうちょうを下にさげる
  さいごまで切りおわった?
    →『はい』のとき
       くりかえしをおわる
    →『いいえ』のとき
       つづける
ここまでをくりかえす
アイちゃんの目がまた丸くなりました。

「わぁ!」
「こうやって『命令(めいれい)』が分かれることを、むずかしいことばで『分岐(ぶんき)』っていうんだ」
「『分岐(ぶんき)』?」
「わかりやすく言うと『分かれ道』かなぁ」
「『命令(めいれい)』の『分かれ道』!おもしろ〜い!」
「でもね、これだとちょっとこまったことになるんだ。おてつだいロボットに、とっても小さいにんじんがわたされたらどうなるかな?」
「そんなに小さなにんじんってあるかなぁ」

おとうさんはなるほどと思い、少し考えてこう言いました。

「おかあさんが切ったにんじんが、れいぞうこに入っていたら?」
「小さいにんじんがわたされちゃうね・・・。何も切らないのにほうちょうが一回うごいちゃう・・・。どうしよう・・・」
「『分かれ道』を『くりかえし』のはじめにしてごらん」
にんじんの右のはしっこにほうちょうを合わせる
ここからくりかえす
  さいごまで切りおわった?
    →『はい』のとき
       くりかえしをおわる
    →『いいえ』のとき
       つづける
  ほうちょうを上にあげる
  ほうちょうを2cm左にうごかす
  ほうちょうを下にさげる
ここまでをくりかえす
「こうかな?でも、まだ切っていないのに『さいごまで切りおわった?』って聞くってなんかへんなかんじ・・・」
「じゃあ、『分かれ道』を少しかえてみよう。ここはおとうさんが書くね」
にんじんの右のはしっこにほうちょうを合わせる
ここからくりかえす
  のこりのにんじんは2cmよりも大きい?
    →『はい』のとき
       ほうちょうを上にあげる
       ほうちょうを2cm左にうごかす
       ほうちょうを下にさげる
       さいごまで切りおわった?
    →『いいえ』のとき
       くりかえしをおわる
ここまでをくりかえす
「わぁ。『分かれ道』っておもしろいねぇ」
「この『分かれ道』も大切な『命令(めいれい)』なひとつなんだよ」
「あっ。ふたつ目だ」
「そうだね。『くりかえし』と『分かれ道』で大切な『命令(めいれい)』がふたつだね」
「あとひとつだ」
「さっきは『くりかえし』の中で『分かれ道』をつかったけど、『分かれ道』だけでもつかえるんだよ」
「どうやってつかうの?」
「おとうさんが書いてみるね」
「うん」
『今は朝ですか?』としゃべる
 →『はい』のとき
   『おはようございます』としゃべる
 →『いいえ』のとき
   『今は昼ですか?』としゃべる
    →『はい』のとき
      『こんにちは』としゃべる
    →『いいえ』のとき
      『こんばんは』としゃべる
「よし、できた」
「わぁ!すごい!ロボットさんとお話しているみたい!」
「頭のいいおてつだいロボットに見えるよね」
「うん」

(つづく)

おとうさんはプログラマ その4 〜アイちゃん、プログラムの『じゅんばんにする』を知る〜

「じゃあ、大切な『命令(めいれい)』のさいごのひとつ、もしかしたら一番大切な『命令(めいれい)』かもしれないよ」
「なんだろう?楽しみ」
「じつはね、もうやってるんだよ」
「えっ?ふたつしか教えてもらってないよ?」
「うん。じゃあ、これを見てみようか」

おとうさんはそう言いながら、さっき書いた紙をつくえの上に広げました。
かわむききをじゅんびする
ほうちょうをじゅんびする
まないたをじゅんびする
ボウルをじゅんびする
ナベをじゅんびする
オタマをじゅんびする
にんじんを切る
じゃがいもを切る
タマネギを切る
おにくを切る
コンロにナベをのせる
火をつける
あぶらを入れる
ナベにざいりょうを入れる
いためる(やく)
ナベに水を入れる
まつ
カレーのルーを入れる
こげないようにかきまぜる
おさらをじゅんびする
すいはんきをひらく
おさらにごはんを入れる
すいはんきをとじる
ごはんにカレーをかける
「おとうさん、これがどうしたの?」
「ロボットさんは『命令(めいれい)』をどうするんだったっけ?」
「えっと・・・。じゅんばんにやってくれるんだよね」
「でも、アイちゃんは『じゅんばんにする』ってことをおてつだいロボットに教えたかな?」
「教えていないけど・・・」
「そうだよね」
「もしかして『じゅんばんにする』ことも、ロボットさんは知らないの?」
「あかちゃんは『じゅんばんにする』って知らないよね」
「え〜っ!?」

アイちゃんは、これまでで一番目を丸くしておどろきました。

「『じゅんばんにする』をむずかしいことばでいうと『順次(じゅんじ)』と言うんだよ」
「『じゅんばんにする』ことも知らないなんて・・・」
「『くりかえす』と『分かれ道』、そして『じゅんばんにする』がとても大切な『命令(めいれい)』だってことがわかった?」
「うん!」
「よし。もう少しだけつづけようか。その前にちょっときゅうけいしよう」
「は〜い!」

(つづく)

おとうさんはプログラマ その5 〜アイちゃん、『命令(めいれい)のかたまり』に名前をつける〜

「さてさて、アイちゃん。さっき書いた『命令(めいれい)』を見てみようか」
にんじんの右のはしっこにほうちょうを合わせる
ここからくりかえす
  のこりのにんじんは2cmよりも大きい?
    →『はい』のとき
       ほうちょうを上にあげる
       ほうちょうを2cm左にうごかす
       ほうちょうを下にさげる
       さいごまで切りおわった?
    →『いいえ』のとき
       くりかえしをおわる
ここまでをくりかえす
「よく見ると『にんじんを切る』ための『命令(めいれい)』があつまっているってわかるけど、パッと見てわかりにくいよね」
「うん。ちょっとわかりにくい・・・」
「こういう時にはわかりやすくなるように、『命令(めいれい)のかたまり』に名前をつけることができるんだ。こんなふうに書きかえて・・・」
●めいれいのかたまりの名前『にんじんを2cmずつ切る』
   にんじんの右のはしっこにほうちょうを合わせる
   ここからくりかえす
     のこりのにんじんは2cmよりも大きい?
       →『はい』のとき
          ほうちょうを上にあげる
          ほうちょうを2cm左にうごかす
          ほうちょうを下にさげる
          さいごまで切りおわった?
       →『いいえ』のとき
          くりかえしをおわる
   ここまでをくりかえす
「もしかして、一回名前をつけたら、『にんじんを2cmずつ切る』っておねがいするだけでいいの?」
「うん。この『命令(めいれい)のかたまり』だけだとにんじんが丸くなるけど、こうおねがいしたら小さくできるよ」
にんじんをたてにおく
めいれいのかたまり『にんじんを2cmずつ切る』
にんじんをよこにおく
めいれいのかたまり『にんじんを2cmずつ切る』
「小さくなった!」
「よし。つぎは『じゃがいもを切る』『命令(めいれい)』をくわしく書いてみよう」
「うん!」

アイちゃんは『命令(めいれい)』を書こうとしましたが、そのまま考えこんでしまいました。
そんなアイちゃんを、おとうさんはまたニコニコして見ています。

「おとうさん、これって『にんじんを切る』ににてる・・・」
「そうだね。『にんじんを切る』時とほとんど同じだよね。さっき名前をつけたにんじんの『命令(めいれい)のかたまり』を書きかえて、じゃがいもでもつかえるようにしよう。こんなふうに・・・」
●めいれいのかたまりの名前『(  ア  )を2cmずつ切る』
   (  ア  )の右のはしっこにほうちょうを合わせる
   ここからくりかえす
     のこりの(  ア  )は2cmよりも大きい?
       →『はい』のとき
          ほうちょうを上にあげる
          ほうちょうを2cm左にうごかす
          ほうちょうを下にさげる
          さいごまで切りおわった?
       →『いいえ』のとき
          くりかえしをおわる
   ここまでをくりかえす
「あなうめもんだいみたい!」
「そうだね。これで『じゃがいもを切る』はこんなふうに『命令(めいれい)』できるようになるよ」
めいれいのかたまり『( じゃがいも )を2cmずつ切る』
「じゃがいももかんたんに切れたね」
「にんじんをぜんぶ切るときはこんなかんじに・・・」
にんじんをたてにおく
めいれいのかたまり『( にんじん )を2cmずつ切る』
にんじんをよこにおく
めいれいのかたまり『( にんじん )を2cmずつ切る』
「今作っているのはカレーだから2cmでいいけど、チャーハンを作る時はもっと小さく切りたいよね。だから、『命令(めいれい)のかたまり』をまた書きかえて・・・」
●めいれいの名前『(  ア  )を(  イ  )ずつ切る』
   (  ア  )の右のはしっこにほうちょうを合わせる
   ここからくりかえす
     のこりの(  ア  )は(  イ  )よりも大きい?
       →『はい』のとき
          ほうちょうを上にあげる
          ほうちょうを(  イ  )左にうごかす
          ほうちょうを下にさげる
          さいごまで切りおわった?
       →『いいえ』のとき
          くりかえしをおわる
   ここまでをくりかえす
「チャーハンにつかうにんじんは、この『命令(めいれい)のかたまり』をつかって、こうやっておねがいできるよ」
にんじんをたてにおく
めいれいのかたまり『( にんじん )を( 5mm )ずつ切る』
にんじんをたおす
めいれいのかたまり『( にんじん )を( 5mm )ずつ切る』
にんじんをよこにおく
めいれいのかたまり『( にんじん )を( 5mm )ずつ切る』
「すごい!これで、色んなざいりょうを色んな大きさに切ることをロボットさんにおねがいできるね!」
「こうやって『命令(めいれい)のかたまり』を作りながら、おてつだいロボットに色んなおねがいをできるようにしていくんだ」
「ざいりょうをやいたりごはんをたいたりするのも、たくさんの『命令(めいれい)』をあつめておねがいするんだね」
「おりょうりだけじゃなくて、おそうじやおせんたくも同じだよ。さて、これでおとうさんのプログラムのお話はおわり!」
「おとうさん!ありがとう!プログラムがんばってね!」

おとうさんのおしごとのお話をたくさん聞けて、アイちゃんはとてもまんぞくそうです。

(おわり)

2012年07月08日

JettyでWebSocketを使うときのコールバックイベントの実行順

以下、Jetty 8.1.4.v20120524の話。

JettyでWebSocketサーバを実装する場合、WebSocketServletはWebSocketインターフェースを具象化したクラスを返す必要がある。
また、JettyをWebSocketクライアントとして使った場合もWebSocketインターフェースを使う。

さらに、WebSocketインターフェースは、OnFrame・OnControl・OnTextMessage・OnBinaryMessageの4つのインターフェースに派生する。

WebSocketインターフェースは以下の2つの抽象メソッドを持つ。
// WebSocket
void onOpen(Connection connection);
void onClose(int closeCode, String message);
OnFrame・OnControl・OnTextMessage・OnBinaryMessageaの4つのインターフェースに定義されている抽象メソッドは以下。
// OnFrame
boolean onFrame(byte flags,byte opcode,byte[] data, int offset, int length);
void onHandshake(FrameConnection connection);

// OnControl
boolean onControl(byte controlCode,byte[] data, int offset, int length);

// OnTextMessage
void onMessage(String data);

// OnBinaryMessage
void onMessage(byte[] data, int offset, int length);
OnTextMessageインターフェースやOnBinaryMessageインターフェースはメッセージの受信に使用でき、OnControlインターフェースは切断処理やPing/Pong処理に、OnFrameインターフェースは低レイヤーの処理に使える。
これら全て用途が違うため、4つ全部を具象化することもあり得る。

では、どういう順番で各メソッドは呼ばれるのか。
WebSocketサーバ上で、メソッドが呼ばれたタイミングで引数と共にprintlnして処理順を調べた。
処理の流れは、OnFrameインターフェースの抽象メソッドを具象化したメソッドが呼ばれた後に、受け取ったopcodeにあった別のインターフェースの具象メソッドが呼ばれるような具合。
ハンドシェイクの処理はまだWebSocketの世界に入っていないので、他とは異質な流れとなっている。
// 接続
doWebSocketConnect / request : (GET /)@1508249638 org.eclipse.jetty.server.Request@59e61026 / protocol : null
onHandshake / connection : WSFrameConnection@672ae2bc l(0:0:0:0:0:0:0:1%0:8080)<->r(0:0:0:0:0:0:0:1%0:50671)
onOpen / connection : WSFrameConnection@672ae2bc l(0:0:0:0:0:0:0:1%0:8080)<->r(0:0:0:0:0:0:0:1%0:50671)

// テキストメッセージ受信
onFrame / flags : 8 / opecode : 0x1 / data length : 8192 / offset : 6 / length : 6
onMessage / data : foobar

// 切断
onFrame / flags : 8 / opecode : 0x8 / data length : 8192 / offset : 6 / length : 0
onControl / controlCode : 0x8 / data length : 8192 / offset : 6 / length : 0
onClose / closeCode : 1005 / message : null
以下、検証コード。
mavenでjetty:runをgoalに設定し実行。
CheckWebSocketServlet.java
package com.kanasansoft.JettyWebSocketEventCheck;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

@WebServlet("/*")
public class CheckWebSocketServlet extends WebSocketServlet {

    private static final long serialVersionUID = 1L;

    public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
        System.out.println(
            "doWebSocketConnect"
                + " / request : "     + request
                + " / protocol : "    + protocol
        );
        return new CheckWebSocket();
    }

}
CheckWebSocket.java
package com.kanasansoft.JettyWebSocketEventCheck;

import org.eclipse.jetty.websocket.WebSocket;

public class CheckWebSocket implements WebSocket, WebSocket.OnFrame, WebSocket.OnControl, WebSocket.OnTextMessage, WebSocket.OnBinaryMessage {

    public void onHandshake(FrameConnection connection) {
        System.out.println(
            "onHandshake"
                + " / connection : "  + connection
        );
    }

    public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) {
        System.out.println(
            "onFrame"
                + " / flags : "       + flags
                + " / opecode : "     + "0x" + Integer.toString(opcode, 16)
                + " / data length : " + data.length
                + " / offset : "      + offset
                + " / length : "      + length
        );
        return false;
    }

    public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
        System.out.println(
            "onControl"
                + " / controlCode : " + "0x" + Integer.toString(controlCode, 16)
                + " / data length : " + data.length
                + " / offset : "      + offset
                + " / length : "      + length
        );
        return false;
    }

    public void onOpen(Connection connection) {
        System.out.println(
            "onOpen"
                + " / connection : "  + connection
        );
    }

    public void onClose(int closeCode, String message) {
        System.out.println(
            "onClose"
                + " / closeCode : "   + closeCode
                + " / message : "     + message
        );
    }

    public void onMessage(String data) {
        System.out.println(
            "onMessage"
                + " / data : "        + data
        );
    }

    public void onMessage(byte[] data, int offset, int length) {
        System.out.println(
            "onMessage"
                + " / data length : " + data.length
                + " / offset : "      + offset
                + " / length : "      + length
        );
    }

}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.kanasansoft.JettyWebSocketEventCheck</groupId>
  <artifactId>JettyWebSocketEventCheck</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>JettyWebSocketEventCheck</name>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlets</artifactId>
      <version>8.1.4.v20120524</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-websocket</artifactId>
      <version>8.1.4.v20120524</version>
    </dependency>
    </dependencies>
</project>

2012年07月14日

JettyのWebSocketでPing/Pongフレームを送る

皆さん! Pingフレーム送ってますか?
WebSocketは、無通信状態が続くと勝手に切断されちゃうことがあるので、Ping/Pongフレームは結構大切ですよ!
Pingフレームは双方向のハートビートに、Pongフレームは一方向のハートビートに使えます。
相手に鼓動が届くなんて、ロマンティックですね。

そして、Jettyさんの話。
WebSocketプロトコルにはドラフト版からRFC版までいくつかのバージョンがあります。
多くのWebSocketのライブラリは、バージョンが固定だったり、起動時にバージョンを指定するようになっています。
接続してくるクライアントのWebSocketのバージョンに合わせて、処理を切り替えることのできるサーバは少ないのですが、そのうちのひとつがJettyなんです。
Jettyさんは、バージョンの差異をラップし、sendMessageメソッドやcloseメソッドなんかを用意しています。
Jettyさん、懐が深いですね。

でも、その包容力のせいか、ちょっと困ったチャンなところがあって、Ping/Pongフレームを送るのがけっこうめんどうなんですね。
テキストやバイナリのメッセージを送るメソッドなんかはあるんですが、Ping/Pongフレームを送る専用のメソッドがないんです。
なんでかって?
実は、古いバージョンのWebSocketプロトコルにはPing/Pongフレームそのものがないんです。
sendPingメソッドやsendPongメソッドを準備しても、古いバージョンでは遅れないので、何もしないメソッドになってしまいます。
「送るよ!」って言いながら実は送ってないなんてウソは、Jettyさんは許せないようです。
うん。
ウソは良くない!

そうは言っても、Ping/Pongフレームを送りたいことがあるのも事実。
でも、Controlフレームを直接送ることでPing/Pongをおくることができます。
う〜ん。
Jettyさん、さすが!!!

下にJettyでPing/Pongフレームを3秒間隔で交互に送る方法と、Ping/Pongフレームを受け取ったことを検知する方法のサンプルを載せておきますね。
WebSocketPingServlet.javaとWebSocketPing.javaを見てね!

あと、sendPingとsendPongをもった専用のクラスを作って、Jettyさんにウソつきになってもらう方法も載せちゃいます!
WebSocketPingSenderServlet.javaとWebSocketPingSender.javaとWebSocketPingPongSender.javaですよ!
WebSocketPingServlet.java
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

@SuppressWarnings("serial")
@WebServlet("/*")
public class WebSocketPingServlet extends WebSocketServlet {

    public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
        return new WebSocketPing();
    }

}
WebSocketPing.java
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import org.eclipse.jetty.websocket.WebSocket;

public class WebSocketPing implements WebSocket.OnFrame, WebSocket.OnControl {

    FrameConnection frameConnection;

    Timer timer = new Timer();

    TimerTask timerTask = new TimerTask() {
        public void run() {
            try {
                Thread.sleep(3000);
                System.out.println("send ping");
                if (frameConnection.isPing((byte)0x02)) {
                    frameConnection.sendControl((byte)0x02, null, 0, 0);
                } else if (frameConnection.isPing((byte)0x09)) {
                    frameConnection.sendControl((byte)0x09, null, 0, 0);
                } 
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(3000);
                System.out.println("send pong");
                if (frameConnection.isPong((byte)0x03)) {
                    frameConnection.sendControl((byte)0x03, null, 0, 0);
                } else if (frameConnection.isPong((byte)0x0a)) {
                    frameConnection.sendControl((byte)0x0a, null, 0, 0);
                } 
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    public void onOpen(Connection connection) {
        timer.schedule(timerTask, 0, 3000);
    }

    public void onClose(int closeCode, String message) {
        timer.cancel();
    }

    public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) {
        return false;
    }

    public void onHandshake(FrameConnection connection) {
        frameConnection = connection;
    }
    public void onMessage(byte[] data, int offset, int length) {
    }

    public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
        if (frameConnection.isPing(controlCode)) {
            onPing(data, offset, length);
        }
        if (frameConnection.isPong(controlCode)) {
            onPong(data, offset, length);
        }
        return false;
    }

    void onPing(byte[] data, int offset, int length) {
        System.out.println("receive ping");
    }

    void onPong(byte[] data, int offset, int length) {
        System.out.println("receive pong");
    }

}
WebSocketPingSenderServlet.java
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

@SuppressWarnings("serial")
@WebServlet("/*")
public class WebSocketPingSenderServlet extends WebSocketServlet {

    public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
        return new WebSocketPingSender();
    }

}
WebSocketPingSender.java
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import org.eclipse.jetty.websocket.WebSocket;

import com.kanasansoft.AttachWebSocketPingPongSender.WebSocketPingPongSender;
import com.kanasansoft.AttachWebSocketPingPongSender.WebSocketPingPongSender.PingPongSender;

public class WebSocketPingSender implements WebSocket.OnFrame, WebSocket.OnControl {

    PingPongSender sender;
    FrameConnection frameConnection;

    Timer timer = new Timer();

    TimerTask timerTask = new TimerTask() {
        public void run() {
            try {
                Thread.sleep(3000);
                System.out.println("send ping");
                sender.sendPing();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(3000);
                System.out.println("send pong");
                sender.sendPong();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    public void onOpen(Connection connection) {
        timer.schedule(timerTask, 0, 3000);
    }

    public void onClose(int closeCode, String message) {
        timer.cancel();
    }

    public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) {
        return false;
    }

    public void onHandshake(FrameConnection connection) {
        frameConnection = connection;
        sender = WebSocketPingPongSender.getSender(connection);
    }
    public void onMessage(byte[] data, int offset, int length) {
    }

    public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
        if (frameConnection.isPing(controlCode)) {
            onPing(data, offset, length);
        }
        if (frameConnection.isPong(controlCode)) {
            onPong(data, offset, length);
        }
        return false;
    }

    void onPing(byte[] data, int offset, int length) {
        System.out.println("receive ping");
    }

    void onPong(byte[] data, int offset, int length) {
        System.out.println("receive pong");
    }

}
WebSocketPingPongSender.java
package com.kanasansoft.AttachWebSocketPingPongSender;

import java.io.IOException;

import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.WebSocket.FrameConnection;

public class WebSocketPingPongSender {

    private enum PingPong {

        D00_AND_NONE   (null      , null      ),
        D06            ((byte)0x02, (byte)0x03),
        D08_AND_RFC6455((byte)0x09, (byte)0x0a);

        private Byte ping;
        private Byte pong;

        PingPong(Byte ping, Byte pong) {
            this.ping = ping;
            this.pong = pong;
        }

        Byte getPing() {return ping;}
        Byte getPong() {return pong;}

        boolean isSame(FrameConnection connection) {
            return connection.isPing(getPing()) && connection.isPong(getPong());
        }

        static PingPong get(FrameConnection connection) {
            if (D06            .isSame(connection)) {return D06            ;}
            if (D08_AND_RFC6455.isSame(connection)) {return D08_AND_RFC6455;}
            return D00_AND_NONE;
        }

    }

    public static PingPongSender getSender(FrameConnection connection) {
        switch(PingPong.get(connection)) {
        case D06: return new PingPongSenderD06(connection);
        case D08_AND_RFC6455: return new PingPongSenderD08AndRFC6455(connection);
        }
        return new PingPongSenderD00AndNone(connection);
    }

    public static abstract class PingPongSender {

        FrameConnection frameConnection = null;

        public PingPongSender(FrameConnection frameConnection) {
            this.frameConnection = frameConnection;
        }

        public void sendPing() throws IOException {
            sendPing(null, 0, 0);
        }
        public void sendPing(String content) throws IOException {
            byte[] data = content.getBytes(StringUtil.__UTF8);
            sendPing(data, 0, data.length);
        }
        public abstract void sendPing(byte[] data, int offset, int length) throws IOException;

        public void sendPong() throws IOException {
            sendPong(null, 0, 0);
        }
        public void sendPong(String content) throws IOException {
            byte[] data = content.getBytes(StringUtil.__UTF8);
            sendPong(data, 0, data.length);
        }
        public abstract void sendPong(byte[] data, int offset, int length) throws IOException;

    }
    static class PingPongSenderD00AndNone extends PingPongSender {
        public PingPongSenderD00AndNone(FrameConnection frameConnection) {
            super(frameConnection);
        }
        @Override
        public void sendPing(byte[] data, int offset, int length) {}
        @Override
        public void sendPong(byte[] data, int offset, int length) {}
    }
    static class PingPongSenderD06 extends PingPongSender {
        public PingPongSenderD06(FrameConnection frameConnection) {
            super(frameConnection);
        }
        @Override
        public void sendPing(byte[] data, int offset, int length) throws IOException {
            frameConnection.sendControl(PingPong.D06.getPing(), data, offset, length);
        }
        @Override
        public void sendPong(byte[] data, int offset, int length) throws IOException {
            frameConnection.sendControl(PingPong.D06.getPong(), data, offset, length);
        }
    }
    static class PingPongSenderD08AndRFC6455 extends PingPongSender {
        public PingPongSenderD08AndRFC6455(FrameConnection frameConnection) {
            super(frameConnection);
        }
        @Override
        public void sendPing(byte[] data, int offset, int length) throws IOException {
            frameConnection.sendControl(PingPong.D08_AND_RFC6455.getPing(), data, offset, length);
        }
        @Override
        public void sendPong(byte[] data, int offset, int length) throws IOException {
            frameConnection.sendControl(PingPong.D08_AND_RFC6455.getPong(), data, offset, length);
        }
    }
}