網絡聊天室(Java)

摘要

本文闡述了基於Linux環境,Java語言實現的基本聊天室功能,涉及Linux下的Java 語言的Socket編程。以及Java語言的多線程編程。

 
關鍵字
Linux         Java                  Thread              Socket              Tcp
 
簡介
開發背景
操作系統》和《計算機網絡》的學習,使我能夠有機會選擇“基於Linux的網絡聊天室的實現”這個課題項目,使自己課堂所學的理論能夠聯繫實際,並且能夠學習自己沒有涉及過網絡方面以及面向對象的基本思想,而且在編寫聊天程序的過程中,也涉及到多線程的程序設計問題,這個概念在《操作系統》中已經學到,但是對於真正的語言應用卻是第一次。Java語言的多線程編程提供了很好的基礎類,可以很容易實現多線程的調用。以及Java語言的跨平臺,使我有機會在Linux下編寫程序,當然對於Linux下的Java編程和Windows下的Java編程,沒有多少的不同,這些都是Java語言沒有平臺特有的特徵帶來的。
 
系統開發環境
Linux正以自由的精神席捲全球網絡操作系統市場,而Java憑藉其開放、先進的架構正迅速佔領着高端軟件領域。將這二者結合,便可通過Linux低廉的成本實現Java高級應用,在自由、高效的環境下充分發揮出Java的優勢。因此,無論從成本還是性能上考慮,二者的結合都可謂是相得益彰。
例如,現在熱門的服務器端腳本JSP的推薦實現就是Linux上的Tomcat,而與Jboss結合更是極佳的EJB平臺。但是,Linux之所以未能在桌面應用等領域迅速普及,軟件安裝和設置複雜是一個重要原因。要在Linux下實現Java編程,其普通的環境設置可能令習慣了Windows的用戶望而卻步。其實,很多問題只需要簡單的設置就能解決。
而對於本次課程項目的開發也正是兩者的結合,儘管結合沒有發揮各自的精髓,但是也能體會和感受到Java+Linux 的魅力。
 
技術概要
網絡通信基本原理

Ø         TCP (Transmission Control Protocol)基礎

 
數據傳輸協議允許創建和維護與遠程計算機的連接。連接兩臺計算機就可彼此進行數據傳輸。如果創建客戶應用程序,就必須知道服務器計算機名或者 IP 地址(RemoteHost 屬性),還要知道進行偵聽的端口(RemotePort 屬性),然後調用 Connect 方法。如果創建服務器應用程序,就應設置一個收聽端口(LocalPort 屬性)並調用 Listen 方法。當客戶計算機需要連接時就會發生 ConnectionRequest 事件。爲了完成連接,可調用 ConnectionRequest 事件內的 Accept 方法。建立連接後,任何一方計算機都可以收發數據。爲了發送數據,可調用 SendData 方法。當接收數據時會發生 DataArrival 事件。調用 DataArrival 事件內的 GetData 方法就可獲取數據。
 

Ø         UDP(User Datagram Protocol) 基礎

 
用戶數據文報協議 (UDP) 是一個無連接協議。跟 TCP 的操作不同,計算機並不建立連接。另外 UDP 應用程序可以是客戶機,也可以是服務器。
爲了傳輸數據,首先要設置客戶計算機的 LocalPort 屬性。然後,服務器計算機只需將 
RemoteHost 設置爲客戶計算機的 Internet 地址,並將 RemotePort 屬性設置爲跟客戶計算機的 LocalPort 屬性相同的端口,並調用 SendData 方法來着手發送信息。於是,客戶計算機使用DataArrival 事件內的 GetData 方法來獲取已發送的信息。
 

Ø         Socket(Java)

 
套接字方式通信(socket-based communication) 通過指派套接字實現程序自己的通信。套接字(Socket) 是一種抽象,爲服務器和客戶之間的通信提供方便。Java處理套接字通信的方式很像處理I/O操作,這樣,程序對套接字進行讀寫就像讀寫文件一樣容易。
 

Java支持流套接字(steam socket)和數據報套接字(datagram socket)。流套接字使用TCP協議(Transmission Control Protocol, 傳輸控制協議)進行數據的傳輸,而數據報套接字使用UDP協議(User Datagram Protocol, 用戶數據報協議)。因爲TCP能夠探測丟失的數據傳輸並重新提交它們,因此傳輸的數據不會丟失,是可靠的。相比之下,UDP協議不能保證無損失傳輸。所以,採用TCP協議通信可以保證數據的正確傳輸。

 

Ø         客戶/服務器模式

 
網絡聊天室涉及的一個服務器端和N個客戶端。客戶向服務器發送請求,服務器對請求作出響應。客戶嘗試與服務器建立連接,服務器可以接受連接也可以拒絕連接。一旦連接建立起來,客戶和服務器就可以通過套節字進行通信。
   客戶開始工作時,服務器必須正在運行,等待客戶的連接請求。創建服務器和客戶所需要的語句如圖1-1所示。

    

 
圖1-1 服務器創建一個服務器套接字,與用戶的連接一旦建立,就用客戶套接字也客戶保持連接
 
要建立服務器,需要創建一個服務器套接字,並把它附加到一個端口上,服務器通過這個端口監聽連接請求。端口標識套接字上的TCP服務。編號在0到1023之間的端口用來爲特權進程服務。
 
下面的語句創建一個服務器套接字server:



 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

圖1-2 多個線程分享一個CPU
 
 
多線程可以使程序反應更快、交互性更強,並提高執行效率。Java對多線程程序設計提供更好的支持,包括內在地支持創建線程和鎖定資源以避免衝突,解決了資源的共享衝突問題。
當程序運行時,Java解釋器爲main方法開始一個線程。此時可以創建另外的線程。每一個線程都是一個對象,它的類實現Runnable接口或者擴展實現了Runnable接口的類。也可以通過擴展Thread類來實現Runnable接口來創建線程。Thread事實實現了Runnable接口。
 
 

Ø         線程有五種狀態:新建、就緒、運行、阻塞、結束。

如圖1-3所示:

圖1-3一個線程處於其中的一個狀態
 
 
à       新創建一個線程的時候,它進入“新建狀態”。調用start方法啓動線程後,它進入“就緒狀態”。就緒態可以通過調用run方法實現到“運行狀態”的轉移。
à       如果給定的CPU時間用完,或者調用yield()方法可以使線程處於“就緒狀態”
à       當線程執行結束,然後其自然應該進入“結束狀態”。
à       當線程因爲調用sleep()等方法,將進入“阻塞狀態”。此狀態還可以重新進入“就緒狀態”,接着重新得到運行。
 

Ø         Thread類包括以下的幾種控制方法:

 
public void run()方法,用來執行線程。用戶線程類中必須覆蓋該方法。
public void start()方法,它引起對run方法的調用。
public void stop()方法,結束一個線程
public void suspend()方法,掛起一個線程[2]
public void resume()方法,喚醒一個線程[3]

public static void sleep(long millis) throws InterruptedException 方法,可以將在運行的線程置爲休眠狀態,休眠時間爲指定的毫秒。

public void interrupt()方法,中斷正在運行的線程。
public static boolean isInterrupted()方法,測試線程是否被中斷。
public boolean isAlive()方法,檢查線程是不是處於運行態。
public void setPriority(int p)方法,設置方法的優先級。從1~10
public final void wait() throws InterruptedException 方法,將該線程置爲暫停狀態,等待另外一個線程的通知。
public final void notify()方法,喚醒一個等待的線程。
 
Linux下Java編程
本次課題項目採用的環境是:
 

¨         Linux Platform - J2SE(TM) and NetBeans(TM) IDE Bundle NB 4.1 / J2SE 5.0 Update 4[4]

¨         Rat Hat Linux 9.0

 

Linux採用J2SENetBeans可以很容易的開發面向Linux的應用程序,可以移植到windows平臺下運行。其中NetBean Sun公司開發的免費Java圖形用戶界面編輯器。(如圖1-4)可以很輕鬆的實現界面的設計,它將控件以Swing awt JavaBean分類放置。

集成Tomcat 5.0 加入對XML,Structs的支持。這其中感觸最深的地方是,不能修改自動生成的組件初始代碼。假如要用ButtonGroup 就得自己去另寫一個函數來初始化。感覺這樣做,因爲不能隨意修改代碼,能避免隨意修改所導致的錯誤。但是,很多時候,我們真的要修改那部分代碼反倒是件很麻煩的事了。
對於window平臺有個比較不當的地方就是內存消耗太大。硬盤頻繁訪問。在Linux環境可以明顯感覺到速度快了不少!
 

 
 
圖1-4 windows 平臺下的NetBeans運行界面[5]
基於Linux的網絡聊天室的具體實現
服務器端和客戶端體系結構
根據通信的基本原理,不難分析服務器端與客戶端的通信實現,以下是客戶端和服務器端的交互流程,如圖1-5

 
圖1-5 客戶端和服務器端的基本流程
 
流程圖的簡要描述
ServerClient端通信主要是服務器端創建多個線程,生成多個Socket對不同的用戶進行通信。服務器端和客戶端通過消息命令字的方式進行消息確認。方式是在消息頭加入“命令字”。
自定義命令字含義如下:
 
[MESSAGE]:表示接下來的一句話是消息
 
[NAME]:   表示接下來的一句話是名字
[FIRSTNAME]:用於程序邏輯控制,表示第一個
[SYSTEM]: 系統消息                                                
[Server exit!] 服務器退出
[WHISPERMESSAGE]:私聊控制字
 
[QUIT]     表示客戶端退出聊天室
 
客戶端:
客戶端由兩個類實現,一個是主類ClientJFrame另外一個是用於播放聲音的PlaySound類。一下是客戶端的類關係圖,圖1-6

圖1-6 Client端兩個類,ClientJFrame中創建PlaySound實例來播放聲音
 

Ø        

圖1-7 Server端的類視圖
 

Ø             Server相對與客戶端更加的複雜,要主動監聽客戶端發送的連接請求,創建不同的線程,來應答客戶的請求。創建的線程,接受客戶發送的數據的處理。

 
Server創建了連接線程後,還必須創建廣播線程,將每一個客戶發送的消息廣播出去,到每一個客戶端。對於廣播線程和數據接受和處理線程之間的資源共享問題,我採用了,有序運行的方法來消除死鎖。即在用戶發送來數據時,開啓廣播線程,對剛纔的數據進行廣播,在信息廣播結束後,關閉廣播線程。這樣一前一後,就可以保證數據廣播的正確性。
 
在server還需要處理的一件事,就是如何將私聊信息發送給指定的客戶端。我採用的方式是,“用戶名查找發送法”,可以比較快而準確的發送數據[6],爲了和羣聊消息區分,我採用WhisperThread類單獨給予處理。這樣可以清晰的區分。
 
在客戶端,還需要處理的一件事情就是,管理當前的在線用戶。我使用一個堆棧[7]來管理用戶,當用戶來到時,就將用戶名壓入堆棧。當用戶退出時,將用戶的名字從中去除[8]
      

Ø             服務端使用serverListen()函數開始監聽端口,其函數原型如下:

 
圖1-8是windows下的運行效果,充分說明在Linux下開發的應用程序的可移植性,在Windows下運行無阻
 

Ø         程序可以實現公聊和私聊[10],公聊在服務器端將加入聊天記錄,私聊則只是發給指定用戶,服務器端不保留聊天信息。

Ø         收到系統消息,和用戶變化都會有聲音提示。

Ø         完全可以單機來調試信息,也試過在Linux下運行服務器端,在Windows下使用客戶端進行訪問,訪問方式沒有區別,通信也沒有故障。

Ø         當服務器退出時,或者說用戶端失去服務器連接時,用戶將需要重新連接,當然也可以實現超時退出的方式,這樣可以實現重新連接。

Ø         可擴展功能:系統可以選擇需要發送的系統消息的對象,這樣可以使系統消息發送更加靈活。

Ø         用戶可以通過右邊的list得到當前的在線用戶的狀況

Ø         用戶可以通過左邊的textArea得到當前羣中用戶所發送的消息的記錄[11]

Ø         當用戶連接失敗,可以選擇重新登陸,重新登陸就不需要重新輸入用戶名。

Ø         假如用戶登陸時,沒有指定連接地址,將默認爲localhost地址[12]

Ø         用戶可以通過直接按Enter鍵發送消息[13]

總結
經過一個星期的編碼,基本完成了課題任務。從中也學到了不少的東西,鍛鍊了自己的獨立開發能力。其中,對Java語言也有了一定的瞭解,也被Java語言的強大類庫所折服,以及Java環境提供的規範語言所欣喜。正因爲有這樣優秀的語言,和優秀的類庫使得這次的任務能順利的完成。
從中讓我深有體會的是,Java的多線程編程。讓我真正有機會接觸多線程的編程,而Java語言的強大也使得這樣的一個過程,不是非常的艱難。Java多線程編程,一般採用繼承Thread類或者採用Runnable接口來實現。
Windows平臺和Linux平臺對於Java語言,不同的只是虛擬機,對於程序,對於編碼沒有區別,這也是能讓我順利完成Linux平臺的應用程序的一個保證。
通過書寫這篇文檔,我也從中琢磨了許多的東西,如RoseUML等面向對象實現概念,通過嘗試也學習了其中工具帶來的方便。
 
參考資料
以下是開發過程中參考過的資料,其中有網頁模式,其中有課本,以及有用的信息。
 

m        Ineroduction to Java Programming Third Editon :     Y.Daniel Liang

 

m        Linux Platform - J2SE(TM) and NetBeans(TM) IDE : http://java.sun.com

 

m        Java sockets 101:             http://www.ibm.com/developerWorks

 

m        Building a Java chat server:     http://www.ibm.com/developerWorks

 

m        Beej網絡socket編程指南:    http://www.ecst.csuchico.edu/%7Ebeej/guide/net/

 

m        UML參考手冊 :             James Rumbaugh

 
 
 
 


[1] 如果試圖在被佔用的端口上創建一個服務器套接字,將引起java.net.BindException 實時錯誤

[2] 該方法可以引起死鎖。
[3] 喚醒線程一般不使用該方法,而是採用notify()方法加布爾變量來指明線程是否喚醒。

[4] Website: https://jsecom15d.sun.com/ECom/EComActionServlet;jsessionid=6596D10F28E751B8FD7981BCCB5E02DA#http://192.18.97.252/ECom/EComTicketServlet/BEGIN6596D10F28E751B8FD7981BCCB5E02DA/-2147483648/957453423/1/626894/626858/957453423/2ts+/westCoastFSEND/jdk-1.5.0_04-nb-4.1-oth-JPR/jdk-1.5.0_04-nb-4.1-oth-JPR:2/jdk-1_5_0_04-nb-4_1-linux.bin

[5] 圖中所示的界面是Windows平臺的界面效果,Linux(Rad hat Linux 9.0)下的執行界面的佈局方式是大致相同的。

[6] 這裏有個約定,就是用戶名應該是唯一的。
[7] 其實是Java的向量類(Vector),可以動態的調整大小,使用Java提供的函數可以很好實現數據的輸入保存和輸出得到

[8] 在實現用,我並沒有這樣做,而是將其賦離線常量(String), 這樣的目的,在後面將敘述到

[9] 這樣做的目的,是爲了處理簡單,當然在某些時候也是需要這樣處理的
[10] 可以實現多人私聊,可以將你的信息,發給你要想讓看到的人。而不用發給全部
[11] 若功能再擴展,可以實現將聊天的歷史記錄實時保存
[12] 本地的調試地址
[13] 這是通過調用textArea的鍵盤按鍵事件來實現的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章