天天看点

仿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.