1. 程式人生 > >Java通訊-仿QQ聊天專案

Java通訊-仿QQ聊天專案

前後歷時一個多月的Java實現聊天通訊專案-仿QQ聊天室基本告一段落,期間面對了很多問題,也有不同的解決方案,重寫了幾次核心程式碼,等等問題。現在在專案的結束之時,給自己做一個總結,算是一個回顧,算是一次提升,也是一次新的開始。

Github連結

圖形化介面

本次通訊專案是基於圖形化介面的聊天室,使用了基本的JFrame窗體,加上了各種JTextField、JButton、JLabel、JList元件,完成了註冊登陸窗體、好友列表窗體、聊天窗體等,溫習了前面學習的java中的swing元件的使用。

事件監聽

想要圖形化介面對做出的選擇進行響應,事件監聽是不可缺少的,對於一些基本的按鈕、列表選擇、滑鼠雙擊等事件可以進行監聽,然後程式做出不同的處理。可以說圖形化介面的實際應用是和事件監聽緊密相連的。

類的封裝

之所以提到使用自己封裝的類,是因為java三大特性之一的封裝可以帶給我們使用時很多便利,在本專案中,重點提一個MySocket類和一個ReceiveMessage類。

MySocket類:

MySocket類是包含了伺服器端使用的socket物件的類,使用這個MySocket類可以使伺服器在處理註冊登陸、聊天、事件申請處理等不同要求時提供完備詳細的資訊,以及便捷的操作,比如一個socket肯定對應一個id,一個密碼,一個暱稱,一個自己的好友列表等資訊。

ReceiveMessage類:

該類是針對每一個客戶端處理來自伺服器發來的各種資訊的封裝類,這裡使用了多執行緒,阻塞式接收伺服器的資訊,根據通訊協議處理不同的資訊,有的是聊天資訊,有的是申請服務的返回資訊。在這個封裝類中,都需要做出不同的處理結果。具體的內容在通訊協議中會提到。

多執行緒

java通訊若想實現一對多或者是多多的聊天,多執行緒是必不可少的,無論是伺服器連線多個客戶端,還是處理多個客戶端傳送來的資訊,不同客戶端接收伺服器發來的資訊等,都需要一個或多個執行緒來進行阻塞式接收資訊。像以前總結的一樣,多執行緒就是提供一個程式可以同時處理多個任務的功能,在聊天時,我們就需要一邊傳送資訊,一邊還可以接收資訊。同樣,伺服器端更是需要不斷的接收、處理、傳送資訊。
Java 執行緒

I/O操作

本專案使用I/O操作都是基本的InputStream和OutputStream流處理操作,使用了基本的read和write方法,都是讀寫位元組陣列。在每次寫前,都會先發送一個位元組陣列的長度,接收端就根據這個長度申請一個位元組陣列,來接收接下來的位元組陣列。本人在專案早期的時候,使用的是readUTF和writeUTF方法,因為不能處理後面更多的傳輸內容(檔案等),導致了一次大的程式重寫,這是十分令人沮喪的。

Java I/O

通訊協議

關於通訊協議,這是在通訊專案中最最最重要的一個點,可以說這是整個專案的骨架,所有功能的實現都離不開通訊協議的制定,無論是註冊、登陸、群聊私聊、傳送檔案等操作,都需要依託通訊協議,來將客戶端與伺服器與客戶端串聯起來,實現不同的功能。
通訊過程

註冊

使用者註冊需要先連線伺服器,這時會使用暫時的socket物件,然後客戶端把使用者id、使用者暱稱、使用者密碼,按照“id|暱稱(retsiger)”的形式,轉化成位元組陣列傳送到 伺服器,伺服器會讀取位元組陣列轉換成的字串,按照“”將字串拆分,一旦發現了“(retsiger)”即可判斷出使用者申請註冊,接下來呼叫方法,確定使用者是否已經註冊,允許註冊後會將記錄儲存到一個txt檔案中,方便登陸,然後返回一個合法值以供客戶端確認。

登陸

使用者需要使用已經註冊的id和密碼進行登陸,首先仍舊是先連線伺服器,然後向伺服器傳送一條“id*密碼”的內容,伺服器會查詢HashMap以確定是否已經註冊,或者是否已經登陸,然後會返回一個值給客戶端,由客戶端的ReceiveMessage物件接收後,做出正確的處理,允許登陸或者是拒絕登陸。這裡要提一下,本專案有自動重新整理id列表的功能,每當有新使用者登陸伺服器後,都會由伺服器傳送一條“id*id_list*DISDNEIRFHSULF”給所有客戶端,由ReceiveMessage接收後,根據後面的“DISDNEIRFHSULF”來將id_list中所有的好友id拆分,重新填充到JList中,並重新整理JList,實現了自動重新整理id列表的功能。

重新整理id

使用者點選“重新整理id列表”,客戶端會發送一條“id*DISDNEIRFHSULF*DISDNEIRFHSULF”,伺服器接收後將字串進行拆分,讀取到“DISDNEIRFHSULF”,便知道了客戶端申請重新整理id列表,伺服器呼叫方法,將該id對應的MySocket中儲存的id列表,按照“id1|id2|id3…”規則組成字串,再按照“id*id_list*DISDNEIRFHSULF”傳送到客戶端,由客戶端的ReceiveMessage進行處理,重新整理好友列表。

公聊私聊

針對公聊會發送“id*聊天內容 * TNEILCLLA”到伺服器,私聊則會發送“id*聊天內容TargetID” 到伺服器,伺服器會根據“”拆分後的第三部分內容來進行處理,如果是群聊,則向所有客戶端傳送群聊資訊,如果是私聊,則會遍歷所有的MySocket物件,根據id查找出目標targetID,然後傳送聊天內容。

新增好友

客戶端點選新增好友,會向伺服器傳送“id*請求新增的id*DNEIRFDDALLAC”,伺服器接收會便會給目標客戶端傳送申請資訊,目標客戶端可以選擇接收或者拒絕,如果接收,則重新整理兩者的好友列表,如果拒絕則提示申請者。

檔案傳輸

同新增好友類似,客戶端點擊發送檔案,選中檔案後,向伺服器傳送“id* 目標物件id*ELIFDNESYLPPA”,伺服器向目標客戶端傳送請求,目標客戶端可以選擇拒絕或者接收,如果接收,則將檔案讀取成位元組陣列開始傳送到伺服器,伺服器將檔案再傳送到目標客戶端,傳送的內容包含了檔名。

通訊協議總結:

縱然看起來似乎可以完成很多功能,但還是十分遺憾,這是一個失敗的通訊協議,原因就是,這個通訊協議的結構是“id*內容/特殊協議內容*協議內容”,結構分為三個部分,客戶端id,用來區分發起申請的客戶端;內容部分是一些聊天的內容或者是一些重複的特殊協議;協議內容是採用了大寫倒序的方式組成的協議關鍵詞。
這種1+1+1結構是建立在字串的基礎上,將各個部分進行拆分,絕大部分功能的實現是沒有問題的,個人也是覺得比較簡便,便一直在使用,但是在傳輸檔案時發生了致命的缺陷,我們知道,檔案的傳輸需要傳輸位元組陣列,而我們在將位元組陣列轉換成字串,再由字串轉換回來時,位元組陣列中的內容已經發生了不可逆的變化!!!。本人經過很多次實驗,無論是強制字元編碼還是其他的轉換方式,只要是位元組陣列轉換成字串,再由字串轉換成位元組陣列時,都會因為檔案中某些編碼問題或者是未知的問題,導致檔案的傳輸失敗(使用二進位制方式開啟可以看出某些內容已更改)。
字元編碼
當然了,一般的文字檔案或者是程式檔案不回發生問題,但是圖片、視訊等檔案就無法避免失敗了。所以本人認為,正確的通訊協議應該是建立在n+1+n的基礎上,使用位元組陣列實現,第一個n是要傳送位元組陣列的數量,第二個1是協議關鍵詞,第三個n是要傳送的n個位元組陣列,無論是伺服器還是客戶端,都會根據第二個1來進行不同的處理,根據第一個n來接收發送的n個位元組陣列。

TCP/UDP

本專案使用的是TCP/IP通訊協議,使用了Socket/ServerSocket。我們都知道TCP是可靠傳輸,UDP是不可靠傳輸,兩者各有優缺點,以傳輸內容準確為重使用TCP通訊,以傳輸速度為重使用UDP通訊。在java中若使用UDP通訊則需要使用DatagramSocket和DategramPacket。

版本迭代

本人認為,在一個版本的基礎上修改進化程式是一個良好的習慣,儲存了上一個版本的副本,在出現重大失誤時,起碼有了一個還原的工作點,無論是做總結,記錄日誌還是工作還原,都是十分具有幫助的,本專案重寫了兩次,如果不是依託於版本迭代,花費的工作量將是十分巨大的。

總結

總的來說,雖然專案不夠完美,但是也鍛鍊鞏固了自己的JavaSE的基礎,雖然出現了很多問題,導致程式的重寫,但是通過自己鑽研解決問題,也獲取了很多知識,讓自己印象更加深刻。一句話,紙上得來終覺淺,絕知此事要躬行。