程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 推技術聊天室的實現(上)

推技術聊天室的實現(上)

編輯:關於JAVA

基於推技術的聊天室在國內現在已經比較常見。這種聊天室最大的特點是不使用浏覽器每格一段時間就刷新的方式,而讓服務器不定時往客戶端寫聊天的內容。當有人發言時,屏幕上才會出現新聊天內容,而且聊天內容是不斷向上滾動的,如果浏覽器狀態欄在的話,可以看到進度條始終處於下載頁面狀態。即使這種聊天室容納上百人,性能不會明顯的降低。而以往的CGI或活動服務器端腳本做的聊天室性能明顯就不行了。

推技術的聊天室聊天室基本原理是,不使用HTTPD服務器程序,由自己的Socket程序監聽服務器的80端口,根據html規范,在接收到浏覽器的請求以後,模仿www服務器的響應,將聊天內容發回浏覽器。在浏覽器看來就象浏覽一個巨大的頁面一樣始終處於頁面接收狀態。也就是說,我們不再使用CGI等方式來處理聊天的內容,而采用我們自己的程序來處理所有的事務。實際上它就是一個專門的聊天服務器,即一個簡化了的專門用於聊天的WWW服務器。

在具體討論程序的實現之前,我們先來解析一下相關的技術。

◆http請求和應答過程

http協議是浏覽器與WWW服務器之間通信的標准,Socket聊天服務器應當遵守這個協議。實際上,我們只需要使用其中的一小部分就可以了。

http使用了C/S(客戶/服務器)模式,其中浏覽器是http客戶,浏覽某個頁面實際上就是打開一個Socket連接,發送一個請求到WWW服務器,服務器根據所請求的資源發送應答給浏覽器,然後關閉連接。客戶和服務器之間的請求和應答有一定的格式要求,只要按照這個格式接收請求發送應答,浏覽器就會正常的顯示你所需要的的內容。

請求和應答具有類似的結構,包括:

· 一個初始行

· 0個或多個header lines

 · 一個空行

· 可選的信息

我們看看一個浏覽器發出的請求:

當我們浏覽網頁http://www.somehost.com/path/file.html的時候,浏覽器首先打開一個到主機www.somehost.com的80端口的socket,然後發送以下請求:

GET /path/file.html HTTP/1.0

User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0; DigExt)

[空行]

第一行GET /path/file.html HTTP/1.0是我們需要處理的核心。由以空格分隔的三部分組成,方法(method):GET,請求資源:/path/file.html,http版本:HTTP/1.0。

服務器將會通過同一個socket用以下信息回應:

HTTP/1.0 200 OK
Date: Fri, 31 Dec 1999 23:59:59 GMT
Content-Type: text/html
Content-Length: 1354
<html>
<body>
<h1>Hello world!</h1>
(其他內容)...
</body>
</html>

第一行同樣也包括三部分:http版本,狀態碼,與狀態碼相關的描述。狀態碼200表示請求成功。

發送完應答信息以後,服務器就會關閉socket。

◆服務器模型

一般網絡服務器主要分為兩種:

(1)循環服務器(iterative server):它是一個時刻只能處理一個請求的服務器,多個請求同時到來將會放在請求隊列裡。TCP套接字服務器一般很少采用循環方式,因為假如某個客戶和服務器的連接出了問題,會導致整個服務器掛掉。它常為UDP套接字服務器所采用。

(2)並發服務器(concurrent server):在每個請求到來以後分別產生一個新進程來處理這個請求所產生的連接。TCP的Socket服務器大多采用並發方式提供服務。

並發服務器有多種實現方法:

i 服務器和每個接收到的客戶機進行連接,創建一個新的子進程處理這個客戶機請求。

ii 服務器預先創建多個子進程,由這個子進程處理客戶機請求。這種方式被稱為“預創建(prefork)”服務器。

iii 服務器用函數select實現對多個客戶機連接的多路復用。

iv 超級服務器(inet)激活的服務器。

並發服務器由於其算法而具有與生俱來的快速響應優勢,而且當某一個用戶與服務器通信死鎖不會影響其他進程,但由於多個進程之間需要通過進程間通信實現信息交換,而且fork新進程所帶來的開銷隨著用戶數量的增加越來越大,因此原始的並發服務器並不一定是最好的選擇。JAVA語言給我們帶來的方便的線程機制,使我們可以用多線程來代替多進程,實現並發服務器,為我們進行快速的商業版本的聊天室的開發提供了優勢。

值得注意的是,在linux下,JAVA並沒有實現真正的多線程,本質上仍然是多進程。

◆POST與GET

提交form表單信息一般常用的有兩種:POST或者GET。POST由於長度不受限制,而作為大多數form提交時使用的方法。GET方法通過URL來發送提交信息,由於URL被WWW服務器限制了長度,一般最長只能為1024字節,所以如果發送信息很長的話,就不能使用這種方法。

由於我們對聊天內容有長度限制,不會太長,而且普通浏覽頁面使用GET方法,使用GET方法提交form表單可以簡化處理過程,所以我們可以使用這種方法來提交聊天內容。

我們感到美中不足的是GET方法將提交的內容簡單的附在連接後邊,我們如果能夠將提交的內容進行HTML編碼的話,就可以讓客戶舒服點了。

◆用JAVA實現並發SOCKET通信

如果以前做過C的SOCKET編程,那麼這一段對你來說將不是什麼難事。利用JAVA的多線程機制我們可以非常方便的實現並發服務。

每當我們知道服務器主程序創建一個新的套接字連接(即成功地調用了accept()方法)的時候,就啟動一個新的線程來負責本服務器和該客戶之間的連接,主程序將返回並等待下一個連接。為了實現這個方案,本服務器主循環應該采用如下形式:

while(true)
{ Socket newjoin=s.accept();
Tread t=new ThreadedChatHandle(newjoin);
t.start();
}

ThreadedChatHandle類是從Thread類衍生出的處理聊天過程的子類,它的run()方法包括了服務器和客戶的通信循環——判斷客戶的請求(例如登錄、發言、刷新在線列表),處理發言數據,發送聊天信息等等。下面是一個服務器程序的例子,可以幫助初學者盡快理解。

import java.io.*;
import java.net.*;
public class ChatServer
{ public static void main(String[] args)
{ int I=1;
try
{ServerSocket s=new ServerSocket(8080);
/*創建一個監視8080端口的服務器套接字,如果需要,你可以改成80端口*/
for(;;)
{ Socket newjoin=s.accept();
/*等待一個連接。如果這個連接沒有被創建,本方法阻塞當前線程。返回值是一個
Socket對象,服務器程序利用這個對象可以與連接的客戶通信。*/
System.out.println(“新連接”+i);
new ThreadedChatHandle(newjoin,i).start();
/* ThreadedChatHandle(Socket theS,int c)是我們自己定義的聊天服務類,這個
類在後邊我們有進一步描述*/
i++;
}
}
catch(Exception e)
{ System.out.println(e);
}
}
……
}

多進程(線程)並發服務的一個關鍵問題是,如何實現進程(線程)間通信。每個客戶的發言(包括表情和動作等選項)都需要放在一個公共的地方,讓所有的輸出線程都能夠獲得它。解決的方法有很多,比如說放在數據庫裡,放在大家都有權限的dat文件裡,或直接用管道實現進程間通信。其中,對一個聊天室服務器來說,第一種方法是最傻的,太消耗系統資源,而且使程序執行效率變慢,可能出錯環節增多。而使用管道通信的方式,把所有發言數據都保存在內存裡,不但可以獲得最高的執行效率,安全的執行過程,也不用考慮線程同步的問題。不要以為所有的發言數據會很多,其實服務器端只要保存最後100句就已經很了不起了,不是嗎?

JAVA裡關於管道的API有:

●Java.io.PipedInputStream

PipldInputStream():

創建新的管道輸入流,且它沒有關聯一個管道輸出流。

PipldInputStream(PipldOutputStream out):

創建新的管道輸入流,且從管道輸出流out中讀取數據。

connect(PipldOutputStream out):

關聯一個管道輸出流,且這個流讀取數據。

●Java.io.PipedOutputStream

PipldOutputStream():

創建新的管道輸出流,且它沒有關聯一個管道輸入流。

PipldOutputStream(PipldInputStream in):

創建新的管道輸出流,並輸出數據到in。

connect(PipldInputStream in):

關聯一個管道輸入流,並輸入數據到in。

◆Daemon的實現

實際上,我還沒有找到直接在JAVA中實現後台守護進程的方法。實現一個後台進程需要完成一系列的工作,包括:關閉所有的文件描述字;改變當前工作目錄;重設文件存取屏蔽碼(umask) ;在後台執行;脫離進程組;忽略終端I/O信號;脫離控制終端。

JAVA中有一個叫Daemon Thread的東西,我沒有使用過。據介紹,這種叫服務線程的東東唯一的目的就是為其它線程提供服務。而一個程序裡如果只剩下服務線程的話,這個程序就會停止(和我們的初衷簡直就是南轅北轍)。有興趣的朋友可以看看相關的內容,在java.lang.Thread.setDaemon()。

雖然我們不能用JAVA實現後台服務守護進程,不過我們還有JAVA的C接口,問題總有解決的辦法。

◆異常處理

在Socket通信過程中很容易出現一些意外情況,如果不加處理直接發送數據,就可能導致程序意外退出。例如,客戶關閉了socket後,服務器繼續發送數據,這就會導致異常。為避免這一情況的發生,我們必須對它進行處理,一般情況下,只需要簡單地忽略這個信號就可以了。幸好,JAVA的異常處理機制還比較強壯。

◆用戶斷線判斷和處理

許多情況下,用戶不是通過提交“離開”按鈕離開聊天室,這時候就需要判斷用戶是否斷線了。一般用戶斷線可能包括以下幾種情況:方法是:當用戶關閉浏覽器,或者點擊了浏覽器stop按鈕,或者跳轉到其他網頁的時候(如果用JAVASCRIPT彈出一個聊天窗口的話,那麼這兩種情況我們是能夠避免的——大不了再禁止右鍵),相對應的socket將會變成可讀狀態,而此時讀出的數據卻是空字符串。

利用這個原理,只要在某個可讀的socket讀取數據時,讀到的卻是空數據,那麼我們就可以斷定,與這個socket相對應的用戶斷線了。

◆防止連接超時斷線

如果浏覽器在一段時間內沒有接到任何數據,那麼就會出現超時錯誤。要避免這一錯誤,必須在一定間隔內發送一些數據,在我們這個應用系統裡,可以發送一些html注釋。發送注釋的工作可以直接插入聊天內容之間來完成。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved