天天看點

仿qq聊天程式設計之借鑒

import java.net.DatagramPacket;

import java.io.Serializable;

import java.net.InetAddress;

public class QDatagramPacket implementsSerializable

{

    //定義操作的類型,type用于傳遞時,識别操作類型

    //各種操作定義為類屬性,在網絡環境下傳遞

    privateint type;

    publicDatagramPacket dPacket=null;  //由于資料報包類不能被繼承,是以定義一個資料報包類的引用

    //publicstatic final int MESSAGESHOW_PIC_PERSONAL = 1;

   // publicstatic final int MESSAGESHOW_PHOTO = 2;

    publicstatic final int MESSAGESHOW_WORD_PERSONAL = 3;    //私人聊天操作

    publicstatic final int MESSAGESHOW_WORD_GROUP = 4;         //群裡聊天操作

   // publicstatic final int MESSAGESHOW_PIC_GROUP = 5;

    //publicstatic final int DATABASEHANDLE_FIND = 11;

    //publicstatic final int DATABASEHANDLE_DEAL = 12;

    //publicstatic final int DATABASEHANDLE_RESULT = 13;

    //publicstatic final int DATABASEHANDLE_FLAG = 14;

    publicstatic final int USER_INFO = 21;        //使用者資訊操作

    publicstatic final int USER_LOGIN = 22;       //使用者登入操作

    publicstatic final int USER_REGISTER = 23;     //使用者注冊操作

    public static final int FIND_USER=31;            //查找好友操作

    publicstatic final int RETURN_USERINFO=32;      //傳回使用者資訊操作

    publicstatic final int FIND_GROUP=33;          //查找群号操作

    publicstatic final int RETURN_GROUPINFO=34;         //傳回群消息操作

    publicstatic final int JOIN_GROUP=35;               //加入一個群

    publicstatic final int RETURN_GROUPMEMBERS=36;    //傳回群組中成員資訊

    publicstatic final int MAKE_FRIENDS=37;             //加好友操作

    publicstatic final int SYSTEM_INFO = 91;         //系統消息

    publicstatic final int SYSTEM_ON_OR_OFFLINE = 92;   // 判斷是否線上

    //兩個構造函數

    publicQDatagramPacket(byte[] b,int length)

    {

       dPacket=new DatagramPacket(b,b.length);

    }

    publicQDatagramPacket(byte[] b,int length,InetAddress add,int port)

    {

       dPacket=new DatagramPacket(b,b.length,add,port);

    }

    //獲得操作類型

    publicint getType()

    {

       return this.type;

    }

    //設定操作類型

    publicvoid setType(int type)

    {

       this.type=type;

    }

}

2.2.1功能分析

本系統要實作的功能如下:

1)注冊

伺服器收到使用者的注冊請求,便開始接受客戶傳遞的資訊,諸如客戶的賬号(必須為6-10個字元),呢稱,性别,籍貫,個人資料等,接受完畢後,便通過Jdbc-Odbc與背景資料庫連接配接,然後向資料庫添加記錄。客戶收到伺服器傳回的資訊後,便打開主登陸視窗。

2)登陸

在用戶端,使用者輸入其号碼和密碼,然後建立與伺服器的連接配接,告訴伺服器我要登入,伺服器收到後,開始通過JdbcOdbc讀取資料庫,然後與使用者輸入的資訊比較,如果成功,便打開主程式視窗。然後客戶向伺服器請求讀取好友名單,伺服器收到該請求,開始讀取資料庫中的表,得到好友的号碼後,再在icq表中讀取好友資料,然後向用戶端發送這些資訊,客戶收到後就在主視窗顯示好友,并且建立幾個矢量(Vector)用以存儲好友的呢稱,号碼。

3)私聊

私聊就是兩個聊天。用戶端首先發送消息到伺服器端,伺服器端根據發送人資訊和接收者資訊來轉發。例如伺服器接收到一個A發給B的消息,先判斷B是否已經線上,如果線上就将資訊發送過去。B接收到A發來的資訊,如果是未建立對話視窗,就詢問是否建立對話,否則就丢棄資訊。

4)群聊

群聊,就是多人一起聊天。過程與私聊差不多。唯一的不同就是伺服器會将資訊轉發給指定群的所有成員。

5)建立分組

使用者注冊以後預設的好友分組隻有一個。使用者在執行添加分組操作以後,用戶端分将一個這個動作的資訊發送給伺服器端(主要是FromClientlisten)。伺服器端的fromClientListen接收到這個請求,會進行資料庫操作,将要添加的分組資訊插入到資料庫中。如果成功,則傳回這個分組的資訊給用戶端,否則傳回錯誤提示資訊。

6)建立群

使用者注冊以後群個數為0。使用者在執行添加群操作以後,用戶端分将一個這個動作的資訊發送給伺服器端(主要是FromClientlisten)。伺服器端的fromClientListen接收到這個請求,會進行資料庫操作,将要添加的群資訊插入到資料庫中。如果成功,則傳回這個群的資訊給用戶端,否則傳回錯誤提示資訊。

7)查找好友并加為好友

輸入要查找的使用者ID号,用戶端發送一個查詢資訊給伺服器端,如果找到就傳回使用者服務資訊。打開一個使用者資訊顯示界面,該界面會提供一個“添加為好友”按鈕,點選後可将此人加為好友。如果未找到,彈出一個資訊提示框。

8)查找群并加入群

輸入要查找的群ID号,用戶端發送一個查詢資訊給伺服器端,如果找到就傳回使用者服務資訊。打開一個群資訊顯示界面,該界面會提供一個“加入此群”按鈕,點選後可将此人加為好友。如果未找到,則彈出一個資訊提示框。

9)儲存聊天記錄

聊天記錄可以儲存于用戶端,在某一個用戶端發送一個消息的時候可以先再臨時檔案中儲存消息内容,再發送給伺服器,再讓伺服器轉發,臨時檔案可以定期更新。         

圖2-7删除好友活動圖

3.總體設計

3.1功能子產品圖

本系統主要由由兩個子系統組成:;聊天子系統。

聊天子系統的功能有:1)注冊;2)登陸;3)添加群;4)查找群5)私聊;6)群聊;7)檢視好友資料;8)查找好友;9)添加好友;10)建立好友分組。

3.2資料庫設計

3.2.1E-R圖

使用者表(QQUser):

屬性:id,account(登陸賬号),pwd,nickname,sign(個性簽名),sex,email,head(頭像)

關系:

               一個使用者可以有多個好友分組。

               一個使用者可以有多個群。

               一個使用者可以有多條留言。

好友分組表(Qqperson_group):

       屬性:id,userid(建立者ID),name(分組名)

   關系:一條記錄隻對應一個使用者,但是可以擁有多個組成員。

群表(Qqpublic_group):

       屬性:id,userid(建立者ID号),name(群名),sign(群公告)

       關系:一個群隻有一個建立者,但是可以擁有多個群成員。

圖3-4系統各表之間的聯系圖

3.2.2表結構設計

表3-1  客戶資訊表(qquser)

1 自動id号 Id 整數
2 使用者賬号 Account 整數
3 使用者密碼 Pwd 字元
4 使用者昵稱 Nickname 字元
5 使用者簽名 sign 字元
6 使用者性别 Sex 字元
7 使用者郵箱 email 字元
8 使用者頭像 Head 字元

表3-2  Qq消息表(QQMessage)

1 自動ID ID 整數
2 發信人 Account_from 整數
3 收信人 Account_to 整數
4 發送内容 Content 字元

表3-3  使用者分組表(qqpersongroup)

1 自動ID ID 整數
2 擁有者Id userid 整數
3 分組名稱 name 字元

表3-4  公共群表(qqpublic_group)

1 自動ID ID 整數
2 建立人 userid 整數
3 群名稱 name 整數
4 群的公共消息 sign 字元

表3-5  使用者與群的中間表(qquser_publicgroup)

1 自動ID ID 整數
2 加入的群ID groupid 整數
3 加入的使用者的ID userid 整數

表3-6  使用者與分組的中間表(QQuser_persongroup)

1 自動ID ID 整數
2 加入的分組的id groupid 整數
3 加入分組的使用者的id userid 整數

以上各表建立的代碼如下:

1)建立使用者表

create table qquser(

number primary key,

account number unique,

nickname varchar(100),

sign varchar(500),

sex varchar(10),

email varchar(100),

head varchar(200));

2)建立分組表

create table qqperson_Group(

id number primary key,

userid references qquser(id),

name varchar(100));

3)建立群表

create table qqpublic_group(

id number primary key,

userid number references qquser(id),

name varchar(100),

sign varchar(800));

4)建立消息表

create table qqmessage(

id number primary key,

account_from number,

account_to number,

content varchar(1000));

5)建立使用者與群的中間表

create qquser_publicgroup (

id number primary key,

groupid number referencesqqpublic_group(id) ,

userid references qquser(id););

6)建立使用者與分組的中間表

create qquser_persongroup(

id number primary key,

groupid number referencesqqpublic_group(id) ,

userid references qquser(id));

7)為使用者表插入資料

insert intoqquser values(1,1,'long','I'm long','male','[email protected]','48.gif');

insert into qquser values(2,2,'a','aa','male','[email protected]','48.gif');

insert into qquser values(3,3,'b','bb','male','[email protected]','48.gif');

insert into qquser values(4,4,'c','cc','male','[email protected]','48.gif');

insert into qquser values(5,5,'d','dd','male','[email protected]','48.gif');

       8)為分組表和群表插入資料

insert into qqperson_group values(1,1,'myfriend');

into qqpublic_group values(1,1,'ha ha','my new group');

9)為使用者與群中間表插入資料

insert into qqperson_group(1,1,1);

insert into qqperson_group(1,1,2);

insert into qqperson_group(1,1,3);

insert into qqperson_group(1,1,4);

insert into qqperson_group(1,1,5);

10)為使用者與分組中間表加入資料

insert into qqpublic_group(1,1,1);

insert into qqpublic_group(1,1,2);

insert into qqpublic_group(1,1,3);

intoqqpublic_group(1,1,4);

insert into qqpublic_group(1,1,5);

3.類的定義和實作自己定義!

4. 詳細設計及實作

4.1界面設計

1)登陸界面

       本界面需要填寫的有兩個,一個是使用者賬号,另一個是使用者密碼。在用戶端,使用者輸入其号碼和密碼,然後建立與伺服器的連接配接,告訴伺服器我要登入,伺服器收到後,開始通過Jdbc方式讀取資料庫,然後與使用者輸入的資訊比較,否則傳回錯誤,如果客戶收到成功資訊就打開主視窗,否則提示出錯。如果成功,便打開主程式視窗。然後客戶向伺服器請求讀取好友名單,伺服器收到該請求,開始讀取資料庫中的friend表,得到好友的号碼後,再在好友表中讀取好友資料,然後向用戶端發送這些資訊,客戶收到後就在主視窗顯示好友,并且建立幾個Vector用以存儲好友的資訊。

圖4-1 登陸界面圖

2)注冊界面

本界面要填寫的項目包括:使用者昵稱,密碼,郵件位址,個性簽名。

需要選擇的項目包括:性别,頭像。

本界面主要負責将使用者的資訊包裝成一個QQUserClass對象。注冊按鈕一旦被點選,就會試圖建立一個與伺服器端的連接配接。連接配接建立成功就會将填寫的資訊發送到伺服器端。如果伺服器寫入資料庫成功,就回傳回一個賬号。使用這個賬号,使用者可以登陸到系統中。

圖4-2  注冊界面圖

3)主界面

主界面是系統的核心部分。它可以列出所有好友和客戶所有加入的群,也可以提供共享檔案和檔案下載下傳功能。如果使用者服務輕按兩下好友分組或群,好友清單或群成員清單将會顯示出來。輕按兩下一個好友,可以與他進行私聊。輕按兩下一個群成員,可以與群裡的所有成員對話。本界面還提供了以下右鍵菜單:添加好友分組、查找好友并加為好友、查找群并加為群、建立群、移動好友到其它分組、檢視好友資料、檢視群資料。

圖4-3  系統主要界面圖

4)個人資料界面

該界面主要顯示某個使用者資訊,包括使用者的賬号,昵稱,性别,個性簽名,使用者等級等。

本界面還提供了一個“加為好友”按鈕,如果點選它,此人将會被加為使用者的好友。預設的是加到第一個好友清單,不過也可以移動其它好友分組。

圖4-4 檢視好友資料圖

圖4-6 檔案傳送用戶端

7)私聊界面

   本界面主要用來顯示兩個人的對話資訊以及發送資訊。發送的資訊可以設定字型,大小,字型顔色。也可以發送一個QQ表情。

圖4-7私聊界面圖

8)群聊界面

本界面主要用來顯示多人的對話資訊以及發送資訊。發送的資訊可以設定字型,大小,字型顔色。也可以發送一個QQ表情。右邊顯示的是群公告,可以用來公布一些必要的資訊。

圖4-8群聊天界面

4.2資料輸入輸出設計

4.2.1資料輸入

4.2.1.1登陸界面資料的輸入要求

Number(賬号)輸入的必須是整型,它是一個使用者登陸的賬号。

Password(密碼)可以是你設定好的字元串,字元串内容可以随便。

如果使用者設定不正确,将會導緻登陸不成功,系統會彈出相應的對話框。

4.2.1.2注冊界面的輸入

Nickname(昵稱),password(密碼),sign(個性簽名)可以是任意的字元串。

        Email必須是Email位址格式,也就是中間必須含有“@”,否則提示輸入不正确。

4.3.1.2 伺服器端監聽線程的設計

伺服器在監聽到一個客戶以後,它就會建立一個線程去管理這個客戶。至于如何去管理這個客戶,伺服器端不做任何幹涉,完全由監聽線程決定。    監聽線程主要對用戶端的請求進行響應,做到有求必應。接收到資訊後,首先判斷該資訊是哪種類型,如果是要通路資料庫伺服器,則直接根據發送資訊内容操作資料庫。如果是聊天内容,伺服器會将該資訊再次轉發到目的地。這些判斷将會全放部在一個線程的run方法中。

4.3.2傳遞包的設計

由于請求的服務不同,包的類型應該所差別。是以在設計的時候,應該在包中加一個存儲包類型的屬性并提供設定和擷取這個屬性的方法。以下就是對包抽象出來的應該有的方法,所有要傳輸的方法必須實作以下接口中的方法:

package javaqq.datagram;

importjava.io.Serializable;

public interface DatagramPacket extends Serializable

{

    // define class number for the packet which need to send to others

    public static final int MESSAGESHOW_PIC_PERSONAL = 1;

    public static final int MESSAGESHOW_PHOTO = 2;

    public static final int MESSAGESHOW_WORD_PERSONAL = 3;

    public static final int MESSAGESHOW_WORD_GROUP = 4;

    public static final int MESSAGESHOW_PIC_GROUP = 5;

    // define class number for the packet which not need to send to others

    public static final int DATABASEHANDLE_FIND = 11;

    public static final int DATABASEHANDLE_DEAL = 12;

    public static final int DATABASEHANDLE_RESULT = 13;

    public static final int DATABASEHANDLE_FLAG = 14;

    public static final int USER_INFO = 21;

    public static final int USER_LOGIN = 22;

    public static final int USER_REGISTER = 23;

    public static final int USER_REGISTERINFO = 24;

    //find infomation

    public static final int FIND_USER=31;

    public static final int RETURN_USERINFO=32;

    public static final int FIND_GROUP=33;

    public static final int RETURN_GROUPINFO=34;

    public static final int JOIN_GROUP=35;

    public static final int RETURN_GROUPMEMBERS=36;

    // system information

    public static final int SYSTEM_INFO = 91;

    public static final int SYSTEM_ON_OR_OFFLINE = 92;

    public int getType();

    public Object getData();

    public long getFromuserid();

    // if (getTouerid()==0),then the datapacket will be sended to server

    public long getTouserid();

    public String getTalkroomid();

}

       伺服器在接收包的時候,就調用它的getType()獲得type的值,并采取相應的措施。

4.3.3聊天用戶端設計

聊天視窗發送檔案文字與圖檔的代碼如下:

//發送文字

public voidinsertString(String s, SimpleAttributeSet attributset)

{

              Try

{

                     doc.insertString(doc.getLength(), s,attributset);

                     doc.insertString(doc.getLength(),"\n", null);

                     showScroll.getVerticalScrollBar().setValue(

                                   showScroll.getVerticalScrollBar().getMaximum()+20);

                     showText.setCaretPosition(showText.getDocument().getLength());

              }

catch(BadLocationException e)

{

                     e.printStackTrace();

              }

}

6總結    

用java開發GUI系統,比較麻煩的一點在于界面的設計。主要包括兩個方面:元件不能随意設定背景圖檔、布局比較困難。目前有的工具(如netbeans,JBuilder)在這這布局方面可能比較好,但是手動更改代碼非常不友善。關于背景的設定就更不好解決,設定顔色還可以辦到,要想設定背景圖檔,那就得自己去重寫某個類了。本系統重寫的類有JPanel、DefaultTreeCender、DefaultMutableTreeNode等。其中JPanel重寫了一個方法後可以設定背景圖檔,不過圖檔對象隻能在構造器中傳入。DefaultTreeCende和DefaultMutableTreeNode的重寫是為了可以建構一個帶有自定義圖示的數元件。

對于檔案傳輸子產品,我本來想做成具備多檔案下載下傳和檔案夾下載下傳的功能的程式。但是有一點使我感到很困惑的是DataOutputStream對象一旦傳輸了byte數組,如果再傳輸字元串就會出現異常。為了避開這個問題,我以為把所有字元串轉換成byte數組,然後再轉換成字元串就可以解決問題。可出乎我的意料之外的是,這樣解決會更加困難。這樣做的困難是,假如有兩個位元組數組A和C,另外有一個字元串(假如這個字元串表示一個檔案的開始,如“fileTransferStart”)轉換成的位元組數組B。當伺服器把A、B、C三個資料依次發送出去後,用戶端接收的B不再是B,它有可能與A混在一起,也有可能與C混在一起,也有可能A、B、C同時混在一起。當B代表着一個檔案的結束與另一個檔案的開始的時候,用戶端程式就無法區分了,它會将多個檔案的資料全部寫入到一個檔案。這樣話的,這個檔案就報廢了。我對于這個問題的了解是,字元數組在網絡傳輸的時候可能會進行重組,把多個非常短的數組合并成一個長度适中的數組,這樣可能會節省資源開銷吧。對于什麼時候合并成一個數組我就不知道了。還有一種思路就是,把所有的位元組數組類型的資料換成字元串,再通過字元串傳輸到用戶端,用戶端接收以後,首先判斷這個是不是正常的字元串,如果不是就把它轉換成byte數組寫入到檔案中。因為時間的關系沒來得及驗證這種想法。就算這種想法可以,它也會在效率上大大折扣的,畢竟把byte數組轉換成字元串和把字元串轉換成位元組數組是需要時間的。

參考文獻

[1] 王昆,張力生.Java Swing中的渲染器機制.重慶工學學院報,2008,22(10):175~178.

[2] 呂校春,李玲莉.基于Swing的Java GUI元件開發[J].機械工程師,2008(5):129~131.

[3] 陸維廳,邵燕.基于JAVA的SOCKET實作網上交談.河海常州分校學報,2005,3(14):31~35.

[4] 戴歆.JAVA Swing程式開發.軟體導刊,2007,2(6):22~29.

[5] 莫足琴.基于Java Socket多用戶端并發通信聊天程式的設計與實作.十堰職業技術學博士論文,2008:253~255. 

       [6]   王靜,曲鳳娟.基于Socket的多使用者并發通信的設計[J].福建電腦,2007(3):164.

[7] 趙文清,姜波.基于Socket的Java語言網絡通訊機制和程式設計.資訊技術,2006,8(7):66~67.

[8] 溫濤.工程概念.軟體工程師,2006(1):16~18

[10] 朗波.JAVA語言程式設計.清華大學出版社,2005:189~190.

[11] 曹健.計算機軟體發展.IT時代周刊,2005(2):12~15

[12] 周燕飛,左敦穩,李亮.資料庫原理與應用.北京:機械工業出版社,2003:78~89

[13] 張景中.應用計算機.計算機應用,2008(1):23~28

[14] Ian Somerville. Software engineering.北京:機械工業出版社,2004:73~84.

[15] Paul C.Jorgensen. 軟體測試.西安:機械工業出版社,2003:134~139

[16] 趙池龍,姜義平,張建. 軟體工程實踐教程. 北京:電子工業出版社,2006:160~185.

[17] 薩世煊,王珊. 資料庫系統概論(第三版). 北京:高等教育出版社,2006,23-100.