java.nioに触ってみた(UDP通信編)

大容量のデータを高速通信をしようとして、UDPデータ送受信のプログラムを書いていたのだが、どうにもパフォーマンスが出なくて悩んでいた。所謂、以下にあるようなマルチスレッド型のサーバを書いていた。

...
DatagramSocket socket = new DatagramSocket(4649);
byte[] buffer = new byte[8192];

while(true) {
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    socket.receive(packet);
    
    byte[] receiveData = new byte[8192];
    System.arraycopy(packet.getData(), 0, receiveData, 0, packet.getData().length);
    
    Worker worker = new Worker(receiveData, packet.getSocketAddress());
    pool.execute(worker);
}
...

詳しくは書いてないけど、Workerってのが受け取ったパケット一つを処理するワーキングスレッドで、パケットを受け取るたびにスレッドプールにワーカを渡して処理させている。こんな感じで書いていたのだが、パフォーマンスがちっとも出ない。数十MByteのデータを秒間3MByte/sくらいのスピードで送るとパケットの取りこぼしが大量に発生する。

冷静に考えると、スレッドが多すぎたのだ。8KByteのパケットで秒間1MByte/secを出そうとしたら、単純計算で128スレッドが1秒間に生成されることになる。スレッドプールで使い回しているとはいえ、これではパフォーマンスは出ない。

C言語だと、select()/poll()あたりを利用することで、データを受け取るまでブロックしないということが出来るので、わざわざマルチスレッドにする必要はない。で、Javaでこれを実現するためにはNew IOというパッケージを使う必要があるみたいだ。

というわけで、ざっくり調べつつ書いてみた。TCPでのNew IOの使い方の資料はいっぱいあるんだが、UDPだととたんに少なくなって結構苦労したが、やってみれば実はTCPと大差ない感じだった。

DatagramChannel datagramChannel;
Selector selector;
....
datagramChannel = DatagramChannel.open();
datagramChannel.socket().setReuseAddress(true);
datagramChannel.socket().bind(new InetSocketAddress(port));
datagramChannel.configureBlocking(false);
           
selector = Selector.open();

datagramChannel.register(selector, SelectionKey.OP_READ, new IOHandler());

まずは、上記のような感じでチャンネルとセレクタを初期化する。チャンネルっていうのは入出力をラップするものだと思えばよくって、DatagramChannelだとかServerSocketChannel, SocketChannel, FileChannelなどがある。ファイル記述子とその操作をラップしたオブジェクトだと思えばいいのかな。で、セレクタってのはselect()/poll()に対応するものだと思えばいい。

上記のコードではDatagramChannelを初期化して、セレクタと関連づける。このチャンネルが読み込み可能な状態になる(=パケットを受信する)と、IOHandlerに処理が委譲される。IOHandlerは自分で定義したクラスで、実際にパケットを受信したときの処理を行う。

while (selector.select() > 0) {
    Set<SelectionKey> keys = selector.selectedKeys();
    for(Iterator<SelectionKey> it = keys.iterator(); it.hasNext(); ) {
        SelectionKey key = it.next();
        it.remove();
                    
        Handler handler = (Handler)key.attachment();
        handler.handle(key);
    }
}

上記は実際のサーバプロセスのコード。セレクタのselect()メソッドでイベントを待つ。イベントを取得するとselectedKeys()メソッドで、イベントが発生したチャンネル一覧を取得する。その後のfor分でチャンネル毎に処理を行う感じ。attachment()関数でregister時にアタッチしたオブジェクトを取得可能なので、ここでIOHandlerオブジェクトが取得できる。複数のチャンネルで待っている場合は、ハンドラを別々に登録することで、チャンネル毎の処理を分けて記述することが出来る。ちなみに、UDP通信だと別にいらない気もする…。

interface Handler {
    public void handle(SelectionKey key) throws ClosedChannelException, IOException;
}

class IOHandler implements Handler {
    @Override
    public void handle(SelectionKey key) throws ClosedChannelException, IOException {
        DatagramChannel channel = (DatagramChannel)key.channel();
        ByteBuffer buf = new ByteBuffer(8192);
        SocketAddress address = channel.receive(buf);
        buf.flip();
        channel.send(buf, address);
    }
}

で、実際に処理を行うハンドラクラスが上記のような感じ。別段、handle()メソッドの中身を直接サーバプロセス内のwhile文に書いちゃっても結果は一緒。DatagramChannelでは、receiveとsendを使ってクライアント側とやりとりを行う。receiveの返り値にクライアントのSocketAddressが返ってくるので、それを利用してデータを送り返す感じになる。上記のコード例だと、やってきたデータを熨斗はつけないまでも、そのまま送り返す感じになっている。

これでパケット受信毎にプロセスを作らない仕組みが出来た。select()/poll()っぽい感じのコードがNew IOのライブラリを使うことで書くことが出来る。別段New IOなんて新しいパッケージでもないんだけど、Javaのフィーチャーの中ではあまりメジャーじゃないし、自分でも使ったことが無かったから結構新鮮。

実はtomcatも5.5からNew IOになってるとか。やっぱり有名どころのオープンソースのコードはざっとでいいから目を通しておかないと、世の中の潮流から取り残されちゃうね。そのうちファイル転送におけるNew IOを使った場合と使わなかった場合の性能比較でもやってみようかなと思う。

ちなみに、New IOに関して言えばマイコミジャーナルライトニングJavaあたりに結構詳しく書いてあるので、そっち見た方がいいかも。UDPで書いてる記事が見あたらなかったのでさっくり書いてみた。でもまぁ、大して変わらんよなw

天皇杯準決勝・横浜F・マリノス v. ガンバ大阪

3月の6万人集めた開幕戦で始まった2008シーズン。ガンバ相手に0-1という結果で敗退し、シーズンは終わった。

双方とも死力を尽くした120分はまさに死闘だった。ガンバは強行日程だったし、マリノスは延長で山瀬功治の負傷、清水範久の退場。次に決勝があるとは思えない悲壮感漂う試合だった。

サポーターも声を切らさず、最後まで戦ったと思う。勝負を分けたのは、選手やサポーターの勝ちたい気持ちの差ではなかったと思う。ちょっとした運だとは思うがそれを掴めたのはACLチャンピオンのガンバで、掴めなかったのが4年無冠のマリノスなんだろう。

悔しいし、なにか心にぽっかり穴が空いたような気分だけど、14年ぶりにベスト4まで連れて来てくれた選手達に感謝です。

その後、新潟のオフィシャルから大島加入のニュースを見た。その時になってようやく終わったという実感が沸いた。

そして、新しいシーズンが始まるんだろう。

苦しい話は今後たくさんありそうだけど、どんなときでも不変なのがサポーターじゃないかな。

自分が何も出来ないと思うのは諦念でしかないし、自分には何かが出来ると思うのは思い上がりでしかない。ここ最近の自分の座右の銘です。やればいいんだよ、っていうだけです。結果ってのは次にやることへのフィードバックに過ぎないよね。

来年もその言葉を胸に、頑張ろうと思います。

放置しておくと書かなくなりそうなので

とりあえず箇条書き程度に近況報告。

  • J32節。必死の千葉を気持ちで上回る勝利(0-3)。諦めない千葉の選手とサポーターに好感を持った。
  • J33節。またもや必死の東京ヴェルディ松田直樹ループシュート長谷川アーリアジャスールのプロ初ゴールに狂喜乱舞(2-0)。
  • アメリカに留学するために会社を辞める社員の送別会。ムービー作成初体験。結構楽しい。
  • J34節。浦和ホームで大量得点(1-6)。祭りじゃー。
  • KORG nanoPAD買った。
  • 酷い日本が出てくることで話題のRED ALERT3を買った。
  • 会社の忘年会でXactiもらった。嬉しい。
  • 天皇杯準々決勝。この時期の鳥栖遠征はお財布に優しくない。内容が微妙ながらも無事勝利(1-3)。
  • 久々にドラム叩く。間違いなくアヒトイナザワの影響を受けてる。やっぱりやってて一番楽しい楽器だなぁ。
  • 2年ぶりくらいにP2Pのプログラミング。Webアプリケーション作るよりコードを書いてる気分になれるな。

余裕があればそのうち個別エントリ起こすかもしれんが、多分無いと思うw

またperfumeを聴いてる

ブログのタイトルがこれなんだから、もうちょっとNumber Girlの話に触れろよとか思いつつ、またperfumeの話。

新曲「Dream Fighter」の歌詞がジワジワと自分の心を侵食している感じ。

もし辛い事とかがあったとしてもそれは君がきっとずっと
諦めない強さを持っているから僕らも走り続けるんだ

ここが一番好きな歌詞。なんていうか、下積み生活が長くて、2畳のスペースがあればプロとして完璧な踊りを見せ、先日武道館ライブを成功させた彼女たちに言われると凄くグッと来る。本当に走り続けて来た人が歌ってるからこそ心に響く。アイドルっていうのはやっぱり自分が「こうありたい」と思う姿の偶像であって、きっと見る人によって多様な姿を見せるんだろう。そこでシンクロしてしまったらそれは本当にもう、好きになるしかないよね。

大学時代の先輩の結婚式二次会に行ってきた

実は結婚式の二次会みたいな、微妙に居場所が定まってなくて大半の知らない人に囲まれる空間が結構苦手である。でも大学時代に本当にお世話になった人たちで、祝福の気持ちは直接会って伝えたかったので参加することにした。

自分の話になるけど、昔の自分はよく自分の境遇だったり能力不足だったりを自虐的に口に出すことが多い人間だった。その裏には多分、自分の吐いた自分に対する侮蔑の言葉を他人に否定してもらいたかったっていうのが多分あったんだと思う。で、この先輩方、特に新郎の方はそういった自分の言葉の裏にあるものを全部見抜いた上で、ストレートな言葉を返してくれたのを覚えている。それは若かった頃の自分には結構堪えたりもしたのだが、それ以来は自分の伝えたいことを言葉の裏に隠して、それを他人に理解してもらおうと期待することが無くなった。これは自分にとって、凄く前向きな変化で、それに気づかせてくれたこの先輩には本当に頭が上がらない。

とまぁ、向こうにとっては数多くいる後輩の一人に過ぎないのだが、自分にとっては凄く大きな存在の人だったりするわけで、その人たちの結婚祝いとなれば絶対にその場にいたいと思った次第。

新郎が二次会の実質司会をやってたり、新婦が話を振られるたびに「いやまぁ…別に…」的な反応だったのが、いつも見慣れてた先輩方と同じ姿で、それが逆に本当に素敵に見えた。今日の名言は新婦の締めの挨拶での「新郎が心を込めて書いたありきたりな挨拶文を、私が心を込めて棒読みします」。本当に棒読みだよこの人!!!新郎の一人称で書かれてる文章そのまま読んでるから、登場人物に新婦しかいなかったりとかwww

ちなみに俺のSO905iCSのバッテリーが会場に着いた途端に切れたので、写真を撮りそこなったのが心残り。それでも、とても素敵な時間を過ごさせてもらいました。

こういうイベントの帰り道、特に一人になると少し気分が落ち込む。賑やかな会場とのコントラストで寂しくなるのもあるだろうし、他人の人生のターニングポイントを目の当たりにしてきたわけだから、自分のこととか考え込んじゃうんだろうな。

ともかく、末永くお幸せに。

天皇杯 浦和レッズ v. 横浜F・マリノス

天皇杯5回戦が今年からはシーズン中に行われるようになった。というわけで4回戦でコンサドーレ札幌を1-0で下した横浜F・マリノスと、同じく4回戦で愛媛FCを延長で辛くも下した浦和レッズの対戦となった。場所は香川県の丸亀。首都圏チーム同士の試合が地方で行われるのも天皇杯の風物詩。応援に行く身としては面倒この上ない。

さて、4回戦の札幌と5回戦の浦和。目指すところはそれぞれ違っていても、それぞれの問題を抱えているチームらしく、そんな浦和のゴール裏。

またマンション建設反対かよ!

やたら長い一発幕は既に浦和のゴール裏の名物というか、定番になってる気がする。宮崎吐夢風に言えば「ダンマクは長けりゃいいってもんじゃないってことを肝に銘じておいてくださーい」という感じか。長すぎて本当に言いたいことがよく分からんぜ。選手が出てくる前に片付けるあたり、「誰に」言いたいのかは札幌サポーターよりは分かりやすいし、目の前の試合に対してはちゃんと切り替えてる感じ。何だかんだ言って、サポーターの熟成度は札幌とは比較にならんよなぁとか思った。

試合は前半の早い時間帯に狩野健太田中隼磨のゴールで横浜が2点リードする理想的な展開も、前半終了間際と後半開始直後に1点ずつ入れられ同点に。その後は横浜GK榎本哲也のスイッチが入ったらしく神がかり的な好セーブを連発。試合は延長になり、横浜がもう一度盛り返す空気を作るも決着までは至らず、PKへ。俺が横浜ゴール裏に行き始めてから3年くらい経つが、天皇杯5回戦が90分で終わった記憶が無いぜ。

PK戦はひたすらに横浜のボールを蹴る選手、あるいはGKの榎本哲也の名を残りわずかのあらん限りの体力で叫ぶ。祈りにも近い想いを声に乗せてPKの行われている反対側ゴール裏まで届けようと、ゴール裏は必死。その声に応えるよう、次々とPKを成功させる横浜。一方の浦和も失敗しない。

横浜も浦和も5人全員がPKを成功させ、サドンデスに。

先行横浜6人目は栗原勇蔵。いつものフリーキックと対照的なやわらかいキックで難なく成功。浦和の6人目は鈴木啓太。コースの甘かった鈴木啓太のキックを「カンで飛んだ」榎本哲也がセーブ。この瞬間に横浜の天皇杯準々決勝進出が決定。

そこから先の記憶は曖昧だったりする。

ただ、いい年こいた大人が揃いも揃って泣き顔だったのは覚えてる。もちろん自分も例外なく。後から思い返すとかなり照れくさくはあるけれども、その瞬間のゴール裏は本当に美しかったと思う。あの時、一緒に泣いて、一緒に抱き合って喜べる仲間がたくさんいたことは本当に幸せだった。

試合終了の瞬間に大の大人が一斉に泣き崩れるほどの強い「勝つ」という気持ち。これを残りの試合でどれだけもち続けられるか。残留のためでも、天皇杯のためでも、ましてやACLのためなんかではなく。目の前の1試合にどれだけ全力を注げるか。そしてそれを続けられるか。

今はもう、千葉戦のことしか頭の中に無いです。

残留争いの直接対決ということもあって、勝てば一気に残留が近づく試合ではあるけど、そのこと自体をこの試合に向けてのモチベーションにしたくない。もっとシンプルに「勝つってことはこんだけ嬉しいんだぜ」っていうのをモチベーションにしたいね。

あんまり写真取れなかったけど、試合終了後のスタジアムの写真をなんとなく撮ってみた。

ここから丸亀駅までこの時点からあと1時間以上かかるとは、このとき夢にも思っていなかった…。