via http://docs.jboss.org/netty/3.2/guide/html_single/index.html
この章ではcore機能がNettyでどのように提供され、そしてこのcore機能上にどのような構成でネットワークアプリケーション開発スタックが提供されているか検証します。上記の図をよく記憶に焼き付けて、この章を読み進めてください。
Nettyは、バイトシーケンスを表現するために、NIOのByteBufferではなく、独自のバッファAPIを使用しています。このアプローチは、ByteBufferを使用するのに比べて大きな利点を持っています。Nettyの新しいバッファタイプChannelBufferはByteBufferにおけるいくつかの問題を解決し、ネットワークアプリケーションで開発者の日常のニーズを満たすといった側面から設計されています。いくつかの特筆すべき機能を一覧を示します:
- 必要に応じて独自のバッファタイプを定義できます
- 透過的なゼロコピーは内蔵の複数のバッファタイプで提供されます
- 動的なバッファタイプはすぐに使える形で提供されており、StringBufferのようにその容量はオンデマンドで拡張されます
- もうflip()を呼ぶ必要はありません
- 大抵の場合に、ByteBufferよりも高速に動作します
より詳細な情報は、org.jboss.netty.buffer package description を参照してください。
Javaでの伝統的なI/OのAPIは、異なるトランスポートタイプごとに別々のタイプとメソッドを提供しています。例えば、java.net.Socketおよびjava.net.DatagramSocketは共通のスーパータイプを持っていないため、それぞれソケットI/Oの実行方法が異なっています。
この不一致は、あるトランスポートから別のトランスポートに移行しようとした場合、非常に退屈で困難な作業が伴います。このトランスポート間の移植性の欠如は、アプリケーションのネットワーク層を書き換えて、複数のトランスポートをサポートする必要がある問題になります。論理的に、多くのプロトコルは、TCP/IP、UDP/IP、SCTP、およびシリアルポートの通信など、複数のトランスポート上で実行することができます。
さらに状況を悪くするのは、従来のブロッキングするI/O(OIO)のAPIと互換性のない、New I/O(NIO)APIが導入され、そしてこれはNIO.2(AIO)でも同様です。なぜなら、これらのAPIはデザインとパフォーマンス特性が互いに異なるため、大抵の場合、アプリケーション側で実装前に、どのAPIを使用するべきかを決める必要性がでてきてしまいます。
例えば、あなたが提供しようとしているサーバでクライアントの数が非常に少なく、またOIOを使用してソケットサーバを書くことはNIOを使用するよりもはるかに簡単な為、OIOでアプリケーションを構築したとしましょう。しかし、あなたのビジネスが急激に成長し、サーバーが同時に1万ものクライアント数をサポートしなければならなくなった場合に問題がおきます。初めからNIOを選択することもできますが、NIOセレクタAPIの複雑さのために、それが迅速な開発を妨げ、実装ではるかに長い時間を要することになることもあります。
NettyはPoint to Pointのやり取りで必要なすべてを抽象化されている、Channel と呼ばれる汎用的な非同期I/Oのインタフェースを持っています。これは、あるNettyのトランスポート上でアプリケーションを書くことで、他のNettyのトランスポート上で実行できるようになります。Nettyはひとつの汎用的なAPIをとおしてアプリケーションを構築でき、またいくつかの主要なトランスポートのサポートを用意しています。
- NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),
- OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),
- OIO-based UDP/IP transport, and
- Local transport (See org.jboss.netty.channel.local).
とあるトランスポートから、他のトランスポートへの切り替えは、異なる ChannelFactory の実装を選択するように、ただ数行の変更を行うだけです。
また、コンストラクタの呼び出し方法を数行変更するだけで、まだ対応していない新しいトランスポート、例えばシリアルポート通信のトランスポートでも動作できるように変更することもできます。さらに、Nettyは非常に高い拡張性を持っているため、コアAPIを拡張して独自のトランスポートを追加することもできます。
明確に定義され、拡張可能なイベントモデルがイベント駆動型アプリケーションには必須です。NettyはI/Oに焦点を当てた、明確に定義されたイベントモデルを持っています。これは各イベントタイプが厳密な型の階層によって互いを区別できるため、既存のコードを壊すことなく独自のイベントタイプを定義できます。この点は他のフレームワークと大きく異なるところです。多くのNIOフレームワークは、イベントモデルがないか、もしくはあったとしても非常に機能が制限されています。これらは拡張可能ではない、もしくは拡張できたとしても独自のイベントタイプを追加することで、よく既存のコードを壊しがちです。
ChannelEvent クラスは、ChannelPipeline 内の ChannelHandler のリストによって処理されます。pipelineは、どのようにイベントを処理するのか、pipeline内でそれぞれのHandlerがどのように相互作用するのかを、ユーザが完全に制御できるように、高度な Intercepting Filter パターンで実装されています。例えば、データをソケットから読み込まれるときに何をするかを定義することができます。
public class MyReadHandler implementsSimpleChannelHandler
{ public void messageReceived(ChannelHandlerContext
ctx,MessageEvent
evt) { Object message = evt.getMessage(); // Do something with the received message. ... // And forward the event to the next handler. ctx.sendUpstream(evt); } }
また、他のハンドラは、書き込み操作を要求されたときに何をするかを定義することができます。
public class MyWriteHandler implementsSimpleChannelHandler
{ public void writeRequested(ChannelHandlerContext
ctx,MessageEvent
evt) { Object message = evt.getMessage(); // Do something with the message to be written. ... // And forward the event to the next handler. ctx.sendDownstream(evt); } }
イベントモデルの詳細については、ChannelEvent クラスと ChannelPipeline のAPIドキュメントを参照してください。
先ほど説明したcoreコンポーネントの上に、既に存在する様々なネットワークタイプのアプリケーションの実装を可能にする為、Nettyはさらに多くの開発ペースを加速する高度な機能のセットを提供します。
Section 1.8の "Speaking in POJO instead of ChannelBuffer" で示したように、常にビジネスロジックからプロトコル処理を分離することが良い方法です。しかしゼロからこのアイデアを実装する場合、いくらか複雑になることがあります。例えば、メッセージの断片化に対処する必要があるでしょう。一部のプロトコルは多層です(すなわち、他の下位レベルのプロトコルの上に構築されます)。また、あるプロトコルはステートマシンを実装することが、非常に複雑な場合があります。
その為、良いネットワークアプリケーションフレームワークは拡張可能、再利用可能、ユニットテスト可能、および保守性の高く、ユーザーのコーデックを生成する多層コーデックをサポートするフレームワークを提供する必要があります。
Nettyは、それが単純かどうか、またバイナリかテキストかなどには関係なく、プロトコルのコーデックを記述する際に遭遇するであろう様々な問題に対処するため、coreの上に構築された基本な、また高度なコーデックをいくつか用意しています。
古いブロッキングI/Oとは異なり、NIOでSSLをサポートすることは重要なタスクです。あなたは、単にデータの暗号化または復号化にストリームをラップすることはできませんが、javax.net.ssl.SSLEngineを使用する必要はあります。SSLEngineは、SSLそのままに非常に複雑な状態マシンを持っています。このような暗号スイートおよび暗号鍵のネゴシエーション(または再ネゴシエーション)、証明書の交換と検証など、すべての可能な状態を管理する必要があります。またSSLEngineは、想定外かもしれませんが、完全にスレッドセーフではありません。
Nettyでの、SslHandler の役割はすべての血みどろの細かな作業と、SSLEngineの落とし穴の面倒を見ることです。あなたがやらなければならないことは、ChannelPipeline に SslHandler を挿入することだけです。この機能により、非常に簡単に StartTLS のような高度な機能も実装することができます。
HTTPは、間違いなくインターネットで最も一般的なプロトコルです。サーブレットコンテナのように数多くのHTTPの実装が既にあります。にもかかわらず、なぜNettyは、core上にHTTPの実装を持っているのでしょうか?
NettyのHTTPのサポートは、既存のHTTPライブラリとは大きく異なります。それはあなたがHTTPメッセージの低レベルでのやり取りを完全に制御できるところです。それは基本的にHTTPコーデックとHTTPメッセージのクラスの組み合わせなので、強制スレッドのモデルを使用しなければならないといった制限はありません。つまり、あなたが思い通りに、独自のHTTPクライアントまたはサーバを書くことができます。あなたは、スレッドモデル、接続のライフサイクル、チャンクエンコーディングなど、HTTPの仕様レベルで様々な制御が可能となっています。
その高度なカスタマイズ性のおかげで、次のような非常に効率的なHTTPサーバを書くことができます。
- 持続的な接続とサーバープッシュが必要なサーバ(例えば Comet や WebSocket )
- 全メディアがストリーミングされるまで、接続を開いたままにする必要があるメディアストリーミングサーバ(例えば2時間の映画)
- メモリを苦しめることなく大きなファイルのアップロードを可能にするファイルサーバ(1GBレベルのファイルのアップロード)
- 非同期のサードパーティWebサービスに1万もの接続をするスケーラブルなマッシュアップクライアント
Google Protocol Buffer は、時間とともに進化してきた高度で効率的なバイナリプロトコルを迅速に実装するための理想的なソリューションです。ProtobufEncoder と ProtobufDecoder を使用すると、Netty codecへGoogle Protocol buffers Compiler (protoc) によって生成されるメッセージのクラスを変えることができます。サンプルのプロトコル定義 から、どうのように簡単に、高性能なバイナリプロトコルのクライアントとサーバを作成することができますを示している "LocalTime"の例を見てください。
この章では、機能単位の観点からNettyの全体的なアーキテクチャを見てきました。Nettyは、シンプルでありながら強力なアーキテクチャを備えています。それは3つのコンポーネント、Buffer、Channel、そしてEvent Modelで構成されています。また、すべての高度な機能は、この3つのコアコンポーネントの上に構築されています。より高度な機能を理解するためには、この章でカバーされた3つの機能についてよく理解しておく必要があります。
まだ全体的なアーキテクチャで、どのように各機能が連携して動作しているか見みえない場合があります。もしそうなら、それはこのマニュアルを改善するために私たちに連絡をください。