あいな研究室ブログ

アクセスカウンタ

help リーダーに追加 RSS Cometでチャット

<<   作成日時 : 2008/01/17 14:53   >>

トラックバック 0 / コメント 0

@ITでTomcat 6で実現! Ajaxを超える通信技術Cometという連載があって、その中でチャットを作っていたので
自分の環境でも作ってみた。

Cometについてはこちらを参照


使用した環境は
JDK 1.6.0_02
Tomcat6.0.14
eclipse-jee-europa-fall2

完成後のディレクトリイメージはこんな感じ。
C:.
│    .classpath
│    .project
│    
├─.settings
│        org.eclipse.jdt.core.prefs
│        org.eclipse.jst.common.project.facet.core.prefs
│        org.eclipse.wst.common.component
│        org.eclipse.wst.common.project.facet.core.xml
│        
├─build
│    └─classes
│        └─chat
│                CometServlet$MessageSender.class
│                CometServlet.class
│                
├─src
│    └─chat
│            CometServlet.java
│            
└─WebContent
    │    index.html
    │    
    ├─js
    │        common.js
    │        
    ├─jsp
    │        CometChat.jsp
    │        CometChatSender.jsp
    │        
    ├─META-INF
    │        MANIFEST.MF
    │        
    └─WEB-INF
        │    web.xml
        │    
        └─lib

workspaceイメージはこんな感じ
画像


では、順に作成していきます。
1.プロジェクト作成
  このとき、Target RuntimeにTomcat6.0を指定して、
  サーバも一緒に作ってしまいましょう。

2.server.xml修正
  [1.]で作成されたサーバのserver.xmlを
  以下のように修正します。

修正前
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>

修正後(※1)
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="10000" redirectPort="8443"
scheme="http" />


3.jsp作成、js作成(※2)
  WebContent配下に其々js,jspディレクトリを作成し
  common.js
function createHttpRequest() {

if(window.ActiveXObject){
try {
// MSXML2
return new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
// MSXML
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
return null;
}
}

} else if(window.XMLHttpRequest){
return new XMLHttpRequest();
} else {
return null;
}
}

function postMessage( user , message , fileName , async ) {
var httpoj = createHttpRequest();

httpoj.open( 'POST' , fileName , async );
httpoj.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded; charset=UTF-8');

httpoj.onreadystatechange = function() {
if (httpoj.readyState==4) {
on_loaded(httpoj);
}
}

httpoj.send( 'user=' + user + '&message=' + message );
}
function on_loaded(oj) {
res = oj.responseText;
}


  CometChat.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>TestCometChat</title>
</head>
<frameset rows="90%,10%">
<frame name="display_messages" src="../CometServlet">
<frame name="message_sender" src="CometChatSender.jsp">
</frameset>
</html>


  CometChatSender.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>TestCometChat Sender</title>

</head>
<body>
<form method="POST" action="--WEBBOT-SELF--">
ユーザ名:<input type="text" name="user" size="20">
メッセージ:<input type="text" name="message" size="60">
<input type="button" value="送信"
onclick="postMessage(user.value, message.value,
'../CometServlet', true)">
</form>
</body>
</html>


4.web.xml修正
  以下の記述を追加します。
  (eclipseでServletを作成すると自動で追加される。)
<servlet>
<description></description>
<display-name>CometServlet</display-name>
<servlet-name>CometServlet</servlet-name>
<servlet-class>chat.CometServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CometServlet</servlet-name>
<url-pattern>/CometServlet</url-pattern>
</servlet-mapping>


5.サーブレット作成(※3)
  CometServlet.javaを作成します。

package chat;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;

public class CometServlet extends HttpServlet implements CometProcessor {

private static final long serialVersionUID = 1L;

private ArrayList<HttpServletResponse> connections = new ArrayList<HttpServletResponse>();
protected MessageSender messageSender = null;

public void init() throws ServletException {
messageSender = new MessageSender();
Thread messageSenderThread =
new Thread(messageSender, "MessageSender[" + getServletContext().getContextPath() + "]");
messageSenderThread.setDaemon(true);
messageSenderThread.start();
}

public void destroy() {
connections.clear();
messageSender.stop();
messageSender = null;
}

/**
* Process the given Comet event.
*
* @param event The Comet event that will be processed
* @throws IOException
* @throws ServletException
*/
public void event(CometEvent event) throws IOException, ServletException {

// Note: There should really be two servlets in this example, to avoid
// mixing Comet stuff with regular connection processing
HttpServletRequest request = event.getHttpServletRequest();
HttpServletResponse response = event.getHttpServletResponse();

if ("POST".equals(request.getMethod())) { // 送信されたメッセージ
String user = request.getParameter("user");
String message = request.getParameter("message");
// 全てのクライアントへ送信
messageSender.send(user, message);
//response.sendRedirect("/jsp/CometChat.jsp");
event.close();
return;
}

if (event.getEventType() == CometEvent.EventType.BEGIN) { // コネクション確率
begin(event, request, response);
} else if (event.getEventType() == CometEvent.EventType.ERROR) {
error(event, request, response);
} else if (event.getEventType() == CometEvent.EventType.END) {
end(event, request, response);
} else if (event.getEventType() == CometEvent.EventType.READ) {
read(event, request, response);
}
}

protected void begin(CometEvent event, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
log("Begin for session: " + request.getSession(true).getId());

// タイムアウトの設定
event.setTimeout(60 * 1000 * 30);

response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.println("<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">");
writer.println("<html><head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">");
writer.flush();

addResponce(response);
}

protected void end(CometEvent event, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
log("End for session: " + request.getSession(true).getId());
removeResponce(response);

PrintWriter writer = response.getWriter();
writer.println("</body></html>");

event.close();

}

protected void error(CometEvent event, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
log("Error for session: " + request.getSession(true).getId());
removeResponce(response);
event.close();
}

protected void read(CometEvent event, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
InputStream is = request.getInputStream();
byte[] buf = new byte[512];
while (is.available() > 0) {
log("Available: " + is.available());
int n = is.read(buf);
if (n > 0) {
log("Read " + n + " bytes: " + new String(buf, 0, n)
+ " for session: " + request.getSession(true).getId());
} else if (n < 0) {
log("End of file: " + n);
end(event, request, response);
return;
}
}
}

protected synchronized void addResponce(HttpServletResponse response) {
connections.add(response);
}

protected synchronized void removeResponce(HttpServletResponse response) {
connections.remove(response);
}

/**
* Poller class.
*/
public class MessageSender implements Runnable {

protected boolean running = true;
protected ArrayList<String> messages = new ArrayList<String>();

public MessageSender() {
}

public void stop() {
running = false;
}

/**
* Add specified socket and associated pool to the poller. The socket will
* be added to a temporary array, and polled first after a maximum amount
* of time equal to pollTime (in most cases, latency will be much lower,
* however).
*
* @param socket to add to the poller
*/
public void send(String user, String message) {
synchronized (messages) {
messages.add("[" + user + "]: " + message);
messages.notify();
System.out.println("[" + user + "]: " + message);
}
}

/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
public void run() {

// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused

if (messages.size() == 0) {
try {
synchronized (messages) {
messages.wait();
}
} catch (InterruptedException e) {
// Ignore
}
}

synchronized (connections) {
String[] pendingMessages = null;
synchronized (messages) {
pendingMessages = messages.toArray(new String[0]);
messages.clear();
}
for (int i = 0; i < connections.size(); i++) {
try {
PrintWriter writer = connections.get(i).getWriter();
for (int j = 0; j < pendingMessages.length; j++) {
// FIXME: Add HTML filtering
writer.println(pendingMessages[j] + "
");
System.out.println(pendingMessages[j] + "
");
}
writer.flush();
} catch (IOException e) {
log("IOExeption sending message", e);
}
}
}

}

}

}

}


6.これでCometChat.jspを起動すれば、OK

課題
チャットのメッセージに入力して、送信ボタンを
クリックしても直ぐには画面表示されない。
(何度か試すと表示されるようになる。)

(※1)@ITには以下のようにかかれていたのだけれど、
   設定するとサーバがタイムアウトになって起動しなかった。
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="10000" redirectPort="8443"
scheme="http" secure="false" />


secureの項目をはずしたら起動できた。
詳細など不明。(誰か教えて。)
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000" redirectPort="8443"
scheme="http" />


(※2)@ITにはon_loaded関数の記述が無かったので追加しました。

(※3)@ITのサーブレットは動かなかったので、
   Tomcat6.0のサンプルや、インターオフィスさんのWeb開発日記
   を参考に作成。


設定テーマ

関連テーマ 一覧

月別リンク

トラックバック(0件)

タイトル (本文) ブログ名/日時

トラックバック用URL help


自分のブログにトラックバック記事作成(会員用) help

タイトル
本 文

コメント(0件)

内 容 ニックネーム/日時

コメントする help

ニックネーム
本 文