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

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

From: someuser@somehost.com

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注釋。發送注釋的工作可以直接插入聊天內容之間來完成。


作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗