サーバーサイドからのプッシュ配信を実現するフレームワークとしてCometというのがあります。これは昔からありまして、うちの研究室でも数年前の卒論で利用してました。で、今年も利用することになったんですが、ノウハウを整理しきれてなくて卒論だけ見てもわかりにくい状況になってましたので、ここであらためて整理しておきます。
12/6 プログラムに一部追記しました。
Cometの原理
httpで接続したコネクションを接続しっぱなしにして、レスポンスのストリームにデータを流し込むことでプッシュを実現します。
クラス構成
サーバー側では少なくとも3つのクラスが必要です
- Cometサーバ実装クラス ・・・ プッシュするためのコネクションを確保するクラス。サーブレットです
- Cometハンドルクラス ・・・ CometHandlerを実装したクラス。ここにプッシュで送信する処理を記述します。
- ユーザプログラム ・・・ Cometを利用するプログラムです。
関連ライブラリ
Glassfishには
というライブラリが同梱されています。このファイル名で探せば出てくるはずですから、見つけたらクラスパスに設定しましょう。
Cometサーバプログラム
クライアントとの接続を確保します。まず、init内では
- Cometエンジンの登録
- Cometエンジンの設定
を行います。そして、ユーザからコネクション要求があれば、processRequest(サーブレットのdoGet、doPost相当)内で、以下のように書かれている処理を行います。
- Cometエンジンの呼び出し
- Cometハンドラーへのコネクションストリームの受け渡し
- Cometハンドラーの登録
-
- @Override
- public void init(ServletConfig config) throws ServletException {
- cometContextPath = config.getServletContext().getContextPath() + "/comet";
- System.out.println("cometContextPath:"+cometContextPath);
- CometContext context = CometEngine.getEngine().register(cometContextPath);
- context.setExpirationDelay(10 * 60 * 60 * 1000);
- }
- protected void processRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter out = response.getWriter();
-
- CometHandler handler = new MyHandler();
- handler.attach(out);
- CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
- context.addCometHandler(handler);
- }
//初期設定
@Override
public void init(ServletConfig config) throws ServletException {
cometContextPath = config.getServletContext().getContextPath() + "/comet";
System.out.println("cometContextPath:"+cometContextPath);
CometContext context = CometEngine.getEngine().register(cometContextPath);
context.setExpirationDelay(10 * 60 * 60 * 1000);
}
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//CometHandlerクラスの登録
CometHandler handler = new MyHandler();
handler.attach(out);
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.addCometHandler(handler);
}
Cometハンドラー Cometハンドラーとは何か?それは、Cometでの配信時の処理を記述するクラスです。ここで大切なのは、onEventメソッドです。これは、Pushする際に呼ばれるメソッドです。ここでwriterに対して書き込みを送ることで、クライアントにその内容が配信されます。なぜでしょう?それは、ここのwriterというのは、上記のサーバプログラムでハンドラーを登録する際にattachメソッドにて受渡したHttpServletResponseのWriterの事なのです。
- public class MyHandler implements CometHandler<PrintWriter> {
-
- private PrintWriter writer = null;
-
- public void onEvent(CometEvent event) throws IOException {
- if (CometEvent.NOTIFY == event.getType()) {
-
- String id = (String) event.attachment();
- writer = response.getWriter();
- writer.write(id);
- writer.flush();
- event.getCometContext().resumeCometHandler(this);
- }
- public void onInitialize(CometEvent event) throws IOException {
- System.out.println("Init");
- }
-
- public void onTerminate(CometEvent event) throws IOException {
- onInterrupt(event);
- }
-
- public void onInterrupt(CometEvent event) throws IOException {
- writer.close();
- event.getCometContext().removeCometHandler(this);
- }
-
- public void attach(PrintWriter writer) {
- this.writer = writer;
- }
- private void removeThisFromContext() throws IOException {
- response.getWriter().close();
- CometContext context =
- CometEngine.getEngine().getCometContext(cometContextPath);
- context.removeCometHandler(this);
- }
public class MyHandler implements CometHandler<PrintWriter> {
private PrintWriter writer = null;
public void onEvent(CometEvent event) throws IOException {
if (CometEvent.NOTIFY == event.getType()) {
String id = (String) event.attachment();
writer = response.getWriter();
writer.write(id);
writer.flush();
event.getCometContext().resumeCometHandler(this);
}
public void onInitialize(CometEvent event) throws IOException {
System.out.println("Init");
}
public void onTerminate(CometEvent event) throws IOException {
onInterrupt(event);
}
public void onInterrupt(CometEvent event) throws IOException {
writer.close();
event.getCometContext().removeCometHandler(this);
}
//クライアントとのストリームを受け取る
public void attach(PrintWriter writer) {
this.writer = writer;
}
private void removeThisFromContext() throws IOException {
response.getWriter().close();
CometContext context =
CometEngine.getEngine().getCometContext(cometContextPath);
context.removeCometHandler(this);
}
ユーザプログラム ユーザプログラムでの記述は下記の通りです。以下はサーブレットの例ですが、ポイントは最後の行です。notifyメソッドを呼ぶことで、CometハンドラークラスのonEventが呼ばれることになります。つまり、プッシュしたい時にnotifyを呼び出せば良いわけです。引数は、ハンドラーのonEventメソッドでeventのアタッチメントとして取り出すことができます。
- protected void processRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- ServletConfig config=this.getServletConfig();
- String id = request.getParameter("id");
- CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
- context.notify(id);
- }
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletConfig config=this.getServletConfig();
String id = request.getParameter("id");
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.notify(id);
}
以上の3つを見比べることで、Cometが何をやっているのかわかるでしょう。コメットのサーバの設定や、クライアントプログラムの記述については、後日書こうかと思ってます。