このトピックでは、HTTPプロトコルを使用して、サーブレットからユーザーにファイルを転送するための小さいながら効果的な方法について説明します。 使用者:
- Apache Tomcat
- Apacheポータブルランタイムライブラリ
- Apache Tomcatネイティブライブラリ
- ユーザーにファイルを提供する必要があるサーブレット
もちろん、サーブレットファイルのレンダリングはパフォーマンスの点ではあまり良くありません。 まず、スクリプトをまったく使用せずに静的コンテンツをレンダリングするのが最善です。 しかし、それなしではできないこともあります。 次に、ほとんどの場合、データの戻りは次のようになります。
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
long writed = 0; byte [] buffer = new byte [BUFFER_LENGTH]; int readed = in .read(buffer, 0, BUFFER_LENGTH); while (readed != -1) { out .write(buffer, 0, readed); writed += readed; readed = in .read(buffer, 0, BUFFER_LENGTH); } * This source code was highlighted with Source Code Highlighter .
NIOに関する本を読んで顕微鏡を使用した後、これをもう少し効果的なツールに再作成できます。
- public static long transfer(File file、OutputStream out )throws IOException {
- return transfer(file、0、file.length()、 out );
- }
- パブリック 静的 ロング転送(ファイルファイル、 ロングポジション、 ロングカウント、
- OutputStream out )throws IOException {
- FileChannel in = new FileInputStream(file).getChannel();
- {
- long writed = in .transferTo(位置、カウント、チャンネル
- .newChannel( out ));
- 書かれたリターン ;
- } 最後に {
- in .close();
- }
- }
*このソースコードは、 ソースコードハイライターで強調表示されました。
ただし、サーバーのスタックトレースを慎重に調査した人は、TomcatのOutputStreamがチャネル経由の送信をサポートしていないことを知っています。
このアプローチの明らかな欠点は次のとおりです。
- パフォーマンス。 この場合、Javaで記述されたコードは、ファイルからOutputStreamに直接コピーできる場合、ネイティブコードよりも明らかに遅くなります。
- メモリ使用量。 開発者にとって、各ストリームを他のいくつかのバッファード(入力|出力)ストリームで保持し、ラップしないのは困難です。 ファイルの各部分は、順にRAMの3つか4つの場所に移動することがわかります(オペレーティングシステムのディスクキャッシュと、おそらくTCP / IPキャッシュを覚えておいてください)
- コードはプロセッサリソースを積極的に使用して、データの断片を前後にコピーします
ただし、Apache Tomcatでは、サーブレットが(便利なインターフェイスを介して)Apache Portable Runtimeライブラリの
apr_socket_sendfile関数を使用できます。 この関数は、ソケットへの入力ポインター、ファイル、および送信データの開始パラメーターと長さパラメーターを受け入れます(ファイル全体だけでなく、転送することもできます)。 この機能へのアクセスは、リクエスト属性(HttpServletRequest)を使用して行われます。 この機能を確認します。
- private static final String TOMCAT_SENDFILE_SUPPORT = "org.apache.tomcat.sendfile.support" ;
- 最終的なブールsendFileSupport = Boolean.TRUE.equals(リクエスト
- .getAttribute(TOMCAT_SENDFILE_SUPPORT));
*このソースコードは、 ソースコードハイライターで強調表示されました。
今なら:
- sendFileSupport == true
- コードの実行後すぐにファイルは削除されません
- ファイルサイズが2 GB未満
自分でファイルを転送する代わりに、Apache Tomcatに指示できます。
- private static final String TOMCAT_SENDFILE_FILENAME = "org.apache.tomcat.sendfile.filename" ;
- private static final String TOMCAT_SENDFILE_START = "org.apache.tomcat.sendfile.start" ;
- private static final String TOMCAT_SENDFILE_END = "org.apache.tomcat.sendfile.end" ;
- // Apache APRおよび/またはNIOを使用してファイルを転送します
- response.setBufferSize(1 << 18);
- request.setAttribute(TOMCAT_SENDFILE_FILENAME、file.getCanonicalPath());
- request.setAttribute(TOMCAT_SENDFILE_START、Long.valueOf(0));
- request.setAttribute(TOMCAT_SENDFILE_END、Long.valueOf(fileLength));
*このソースコードは、 ソースコードハイライターで強調表示されました。
2番目の制限は、サーブレットでの作業が終了した後にファイル転送プロセスが開始されるという事実によるものです。 3番目-明確ではありませんが、おそらくテストマシンに32ビットJVMと32ビットGentooがあるという事実(Tomcatは自分で2GB以上のファイルを与えたくありませんでした)。
その結果:
- ファイルが個別のネイティブスレッドで転送されるようになったため、動作中のJavaサーバースレッドの数が2〜3倍減少しました
- APRがオペレーティングシステムの機能を使用してファイル転送を最適化するため、CPU使用率が低下しました
- ヒープに残る「ガベージ」が少なくなり、ガベージコレクターのパフォーマンスが向上します。
もちろん、本番システムでは、ファイル全体だけでなく部分的にも提供できるようにする必要があり、ユーザーが既にファイルを持っている可能性も考慮する必要があります(プロセスNotModifiedSince)。
さらなる研究のために