http://homelink.javaeye.com/blog/293328#comments
參考文檔
http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
comet是HTTP長連接,就是在HTTP發(fā)送請求時(shí),服務(wù)器不立刻發(fā)送響應(yīng)信息給客戶端,
而是保持著連接,等待一定情況發(fā)生后才把數(shù)據(jù)發(fā)送回去給客戶端。所以用comet可以實(shí)現(xiàn)服務(wù)器端的數(shù)據(jù)實(shí)時(shí)地發(fā)送給客戶端。
本文主要是用java和js來簡單地實(shí)現(xiàn)comet,最后附上源碼和使用例子。
在客戶端用XMLRequest發(fā)送請求到服務(wù)器,在服務(wù)器端用一個(gè)servlet來接收XMLRequest的請求,當(dāng)接收到請
求時(shí),并不立刻響應(yīng)客戶端,而是把該servlet線程阻塞,等到一定事件發(fā)生后,再響應(yīng)客戶端。當(dāng)客戶端接收到服務(wù)端的響應(yīng)后,調(diào)用自定義的回調(diào)函數(shù)來
處理服務(wù)器發(fā)送回來的數(shù)據(jù),處理完成后,再發(fā)送一個(gè)XMLRequest請求到服務(wù)端,這樣循環(huán)下去,就可以實(shí)現(xiàn)數(shù)據(jù)的實(shí)時(shí)更新,又不必要在客戶端不斷地
輪循(polling)。
利用該comet的實(shí)現(xiàn)(以后簡稱為keeper)時(shí),只要在客戶端注冊事件和寫一個(gè)處理返回?cái)?shù)據(jù)的回調(diào)函數(shù),然后在服務(wù)端實(shí)現(xiàn)
keeper中的EventListener接口,調(diào)用Controller.action(eventListener,eventType)就可以
了。
keeper分成兩大部分,第一部分為客戶端的javascript,第二部分是服務(wù)端的servlet和事件處理。
一.客戶端
建立一個(gè)XMLRequest對象池,每發(fā)送一次請求,從對象池中取一個(gè)XMLRequest對象,如果沒有可用的對象,則創(chuàng)建一
個(gè),把它加入到對象池中。這部分的代碼來自于網(wǎng)絡(luò)。
為了使用方便,再添加一些方法,用來注冊事件。這樣只要調(diào)用注冊函數(shù)來注冊事件,并且把回調(diào)函數(shù)傳給注冊事件函數(shù)就行了,處理數(shù)據(jù)
的事情,交給回調(diào)函數(shù),并由用戶來實(shí)現(xiàn)。
keeper為了方便使用,把客戶端的javascript代碼集成在servlet中,當(dāng)配置好keeper的servlet,
啟動HTTP服務(wù)器時(shí),keeper會根據(jù)用戶的配置,在相應(yīng)的目錄下生成客戶端的javascript代碼。
二.服務(wù)端
服務(wù)端的servlet初始化時(shí),根據(jù)配置來生成相應(yīng)的客戶端javascript代碼。
servlet的入口由keeper.servlet.Keeper.java中的doGet進(jìn)入。在Keeper的doGet
中,從請求中獲取用戶注冊事件的名稱(字符串類型),然后根據(jù)事件的名稱,構(gòu)造一個(gè)事件(Event類型),再把它注冊到NameRegister中,注
冊完成后,該servlet線程調(diào)用wait(),把自已停止。等待該servlet線程被喚醒后,從Event中調(diào)用事件的EventListener
接口的process(request,response)來處理客戶端的請求。
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- String eventName = request.getParameter("event");
- NameRegister reg = NameRegister.getInstance();
- Event event = null;
- try {
- event = reg.getEvent(eventName);
- if(event == null) {
- event = new Event(eventName,this);
- reg.registeEvent(eventName, event);
- }
- if(event.getServlet() == null) {
- event.setServlet(this);
- }
-
- } catch (RegistException e1) {
- e1.printStackTrace();
- }
- synchronized(this) {
- while(!event.isProcess()) {
- try {
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- EventListener listener = event.getListener();
- if(listener != null) {
- listener.process(request,response);
- }
- }
在服務(wù)端處理事件時(shí),調(diào)用了keeper.control.Controller中的靜態(tài)方法
action(EventListener listener,String eventName)來處理。如下所示。
- public static boolean action(EventListener listener,String eventName){
- NameRegister reg = NameRegister.getInstance();
- HttpServlet servlet = null;
- Event e = null;
- try {
- e = reg.getEvent(eventName,true);
- if(e == null) {
- return false;
- }
- e.setListener(listener);
- servlet = e.getServlet();
- e.setProcess(true);
- synchronized(servlet) {
- servlet.notifyAll();
- }
- } catch (RegistException ex) {
- ex.printStackTrace();
- }
- if(servlet != null && e != null) {
- e = null;
- return true;
- } else {
- return false;
- }
- }
下面開始用keeper來寫一個(gè)簡單的網(wǎng)頁聊天程序和基于服務(wù)端的時(shí)間。
1.客戶端設(shè)置
注冊兩個(gè)事件,一個(gè)用于是時(shí)間事件,一個(gè)是消息事件。同時(shí)還要寫兩個(gè)回調(diào)函數(shù),用于處理服務(wù)
端返回的時(shí)間和聊天消息。如下所于:
- <script type="text/javascript">
- Keeper.addListener('timer',showTime);//注冊時(shí)間事件
- function showTime(obj){ //時(shí)間處理回調(diào)函數(shù)
- var sp = document.getElementById("dateTime");
- if(sp){
- sp.innerHTML = obj.responseText;
- }
- }
- function startOrStop(obj){
- var btn = document.getElementById("controlBtn")
- btn.value=obj.responseText;
- }
- Keeper.addListener('msg',showMsg,"GBK");//注冊消息事
件,最后一個(gè)參數(shù)是
- //字符串編碼
- function showMsg(obj){//處理消息的回調(diào)函數(shù)
- var msg = document.getElementById("msg");
- if(msg){
-
- msg.value = obj.responseText+""n"+msg.value;
-
- }
- }
- function sendMsg() {
- var msg = document.getElementById("sendMsg");
- if(msg){
- var d = "msg="+msg.value;
- sendReq('POST','./demo',d,startOrStop);
- msg.value = "";
- }
- }
-
- </script>
2.配置服務(wù)端
服務(wù)端的配置在
web.xml文件中,如下所示
- <servlet>
- <servlet-name>keeper</servlet-name>
- <servlet-class>keeper.servlet.Keeper</servlet-class>
- <init-param>
- <!--可選項(xiàng),設(shè)置生成客戶端的JavaScript路徑和名字,默認(rèn)置為
/keeper.js-->
- <param-name>ScriptName</param-name>
- <param-value>/keeperScript.js</param-value>
- </init-param>
- <!--這個(gè)一定要設(shè)置,否則不能生成客戶端代碼-->
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>keeper</servlet-name>
- <url-pattern>/keeper</url-pattern>
- </servlet-mapping>
用<script type="text/javascript"
src="./keeperScript.js"></script>在頁面包含JavaScript時(shí),這里的src一定要和上面配
置的一至。上面的設(shè)置除了<init-param></init-param>為可選的設(shè)置外,其他的都是必要的,而且不能改
變。
3.編寫事件處理代碼,消息的處理代碼如下:
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- System.out.println("Post..");
- String msg = request.getParameter("msg");
- Controller.action(new SendMsg(msg),"msg");
- }
- class SendMsg implements EventListener{
- private String msg;
- public SendMsg(String msg) {
- this.msg = msg;
- }
- @Override
- public void process(HttpServletRequest request, HttpServletResponse response) {
- response.setCharacterEncoding("UTF-8");
- PrintWriter out = null;
- try {
- out = response.getWriter();
- if(msg!=null){
- out.write(msg);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- finally{
- if(out != null) {
- out.close();
- }
- }
- }
- }
到這時(shí),一個(gè)基本的keeper應(yīng)用就完成了。其它部分請參考附件中的例子源碼。