2024年2月6日发(作者:)

聊天室系统

聊天室对于众多网民来说并不陌生,它是网络上大家讨论交流的有效平台。由于Java强大的Internet网络程序设计功能,很多聊天室服务器端都是采用Java语言进行实现。本章所要介绍的开发案例就是利用JAVA实现一个简单的聊天室系统的服务器端。

7.1 聊天室系统介绍

网络聊天室是典型的网络应用程序,其一般采用客户/服务器结构来完成整个功能的设计,下面将概要讨论网络聊天室系统的设计。

7.1.1客户/服务器模式

一般聊天室采用的是客户/服务器模式,我们熟悉的QQ聊天室就是采用这种结构模式。它包含了两个完整的应用程序,即客户端程序和服务器端程序,如图4-1所示。其中,多个客户端程序可以同时与一个服务器进行通信,然后通过服务器的统一处理而完成聊天室各种所需要的功能,如各客户端之间的信息发送和接收。

图7-1 ICQ的客户与服务器模式

我们从上可以知道,要实现该通信模式,首先应在服务器和客户端之间定义一套通信协议,并通过创建套接字来建立连接,然后客户与服务器端再在该连接上进行可靠的传输和接收数据。客户端发出请求,服务器端监听各种请求并对其提供响应服务。这也即典型的“请求—应答”模式。

7.1.2聊天室功能介绍

聊天室用户功能

 用户注册

新用户输入(用户登陆名,真实姓名,呢称,密码与确认密码),如果登陆名没有与系统中已注册的用户登陆名相同,且密码与确认密码一致,则系统报告该用户注册成功,否则提示错误消息。

 用户登陆

注册用户输入登陆名与密码,如果与系统中已注册的用户登陆名及密码匹配,则用户登陆成功,否则提示用户不存在或者密码不匹配。用户登陆成功后,可以选择房间来发言聊天

 参数设置(设置服务器IP,端口)

在客户端设置聊天室服务器的参数,主要有IP地址和端口,把参数保存在一个XML文件中,以便客户端重启时能读入设定的参数

 进入房间

用户登陆成功后,可以从房间列表中选择某一房间进入,如果该房间的当前聊天室成员数没有超过该房间的限定人数,则系统允许该用户进入,否则提示该房间已满。如果用户是房间的第1个聊天成员,则该用户是该房间管理员,直到该用户退出该房间,房间管理员有踢人的权力。在用户进入到聊天房间后,该房间的所有聊天成员都可以看到该用户。

 离开房间

用户可随时离开已进入的房间,当用户离开房间后,返回到房间选择窗口。如果房间管理员离开房间,则把管理权移交给第二个登陆到该房间的用户。

 房间管理员踢人

如果聊天房间中某用户破坏房间的聊天气氛,房间管理员可以把该用户剔出该房间,当用户被剔出某房间后,他在10分钟之内(根据服务器参数设置)不能登陆到该房间。

 发言

用户在房间中有两种发言方式:对房间的所有成员发言(所有成员都会收到该发言消息),对某个成员发言(只有指定成员才能收到该发言消息)

 刷新房间列表

在房间选择窗口中,系统自动刷新房间信息,房间信息包含(房间名称,当前成员数量)

 用户退出系统

Web管理功能

 管理员登陆

 修改管理员密码

 管理员退出

 增加聊天房间

聊天房间属性:房间名称,最大聊天成员数。在客户端出现的房间都是用此功能配置出来的

 删除聊天房间

 注册用户浏览

 系统参数设置

参数有:端口,房间刷新时间间隔,被踢用户的重新进入房间的时间间隔。

7.1.3聊天室系统实现

本聊天室的实现参照了QQ聊天室系统的设计方法,为了更好的测试Java语言编写的网络程序功能的正确性和通用性,我们用如下方法开发了整个聊天室系统。

1. 利用Microsoft visual C++6.0实现聊天室的客户端。

2. 利用Java实现聊天室的服务器端。

3. 利用JSP设计并实现一个聊天室系统管理页面。

可以看出本章案例是一个集成各种相关技术的综合实例,我们也可以从中得出一些关于综合技术开发的经验。本章将专注于介绍利用Java实现聊天室的服务器端以及利用JSP的相关技术开发出简单实用的聊天室Web管理系统,而关于VC++ 6.0实现的客户端,本书将提供源代码,具体实现细节可以参照其它相关书籍,在此不再讲述。

在开始聊天室系统设计实现之前,我们有必要对JAVA的网络编程方法有个大致的了解。7.2节将比较详细的介绍Java所提供用于访问网络资源的类,从而读者入手聊天室将会更加容易。

7.2 JAVA网络编程基础

Java语言非常适合用于分布计算环境,这主要体现在它强大的Internet网络程序设计功能。下面主要讨论java语言所提供的包,其包含了大部分用于访问网络资源的类。

7.2.1 Socket类

利用Java来编写网络应用程序,其中最核心的就是Socket类。它是构造网络程序模块的基础,利用它我们能够很方便的实现程序间双向的面向连接的通信。但是同一个套接字不能与两台以上的计算机通讯,当需要多个客户端进行通信的时候,我们必须建立多个Socket套接字来完成通讯。

Socket类的构造函数一般有四种方式。

表7-1 Socket类的构造函数

构造函数

Socket(String host, int port)

抛出异常

UnknownHostException,

IOException

说明

建立连接到特定主机和端口的套接字

建立连接到指定的IP地址和端口的套接字

建立连接到特定主机和端IOException

口的套接字,并它绑定到特定的本地地址和本地端口

建立连接到指定的IP地址和端口的套接字,并把它绑定到特定的本地地址和本地端口

Socket(InetAddress address, int port) IOException

Socket(String host, int port,

InetAddress localAddr, int localPort)

Socket(InetAddress address, int port,

InetAddress localAddr, int localPort)

IOException

其中,各构造函数的参数解释如下:

String host —— 要进行连接的主机名

int port —— 主机的监听端口

InetAddress address —— 主机的IP地址

InetAddress localAddr —— 套接字所绑定的本地地址

int localPort —— 套接字所绑定的本地端口

下面介绍Socket类一些常用的方法:

1. Public InetAddress getInetAddress()

//返回连接到套接字的远程主机的InetAddress对象。

2. Public InetAddress getLocalAddress()

//返回与套接字关联的本地InetAddress对象。

3. Public int getPort()

//返回套接字连接到的远程服务的端口号。

4. Public int getLocalPort()

//返回该套接字绑定在本地计算机上的端口号。

5. InputStream getInputStream()产生ption异常

//返回一个输入流,它从该套接字连接到的应用程序读取信息。

6. OutputStream getOutputStream()产生ption异常

//返回一个输出流,它向套接字连接到的应用程序写入信息。

7. void close()产生ption异常

//关闭套接字连接。

下面讨论如何通过socket类来编写客户端程序。其基本流程为:

1.通过上述的Socket构造函数,用服务器所在机器的ip以及服务器的端口作为参数创建一个Socket对象

2.利用Socket类提供的getInputStream和getOutputStream方法来创建输入输出流。

3.使用输入输出流对象相应的方法来完成客户端与服务器端的开发

7.2.2 DatagramSocket和DatagramPacket类

DatagramSocket是一种面向无连接的数据报socket。由于不能建立连接,数据报服务不能保证所有的数据都能准确有序的到达目的地,但它的速度比有连接协议快,而且像聊天室这种网络应用程序对可靠性要求不是很高,故其也用的非常普遍。本聊天室就是利用该类来发送和接收数据。

 接收数据报

同Socket连接类似,数据报的接收也需要一个DatagramSocket类用来监听本地主机上的指定端口,当有数据接收时创建一个DatagramPacket对象实例,调用DatagramSocket的receive方法来接收数据,这一过程可以重复进行。在其它编程语言中也有类似的过程。

下面给出Java接收数据报典型的程序片断。

//为接收数据报分配缓冲区

Byte[] buffer = new byte[500];

//为接收数据报实例化DatagramPacket和DatagramSocket

DatagramPacket datagramPacket = new DatagramPacket(buffer,);

DatagramSocket datagramSocket = new DatagramSocket(8000);

//接收数据处理

While(true) //无限循环

{

}

 发送数据报

e(datagramPacket);

String s = new String(buffer,0,0,gth());

发送数据报首先创建DatagramPacket实例,指定其所发送的数据缓冲区、数据长度、目的地主机名和端口号,然后再使用DatagramSocket的send()方法来发送。

下面给出Java发送数据报典型的程序片断。

//为发送数据报分配缓冲区

Byte[] buffer = new byte[500];

//为发送数据报实例化DatagramPacket和DatagramSocket

DatagramPacket datagramPacket = new DatagramPacket(buffer,,address,8000);

DatagramSocket datagramSocket = new DatagramSocket();

//发送

(datagramPacket)

7.3 聊天室系统设计

7.3.1 系统建模

对聊天室的分析我们可以明确系统主要有两类角色(Actor)及其相关的用例:

 系统管理员:系统管理员维护聊天室系统的所有信息,包括用户管理、聊天室房间管理、配置服务器参数等。

 普通用户:普通可以使用系统进行各种聊天室服务,包括:注册用户名、登陆服务器、聊天室发言、房间管理员踢人、退出聊天室系统等。

根据前面的用例分析,我们得到了聊天室系统的用例(UseCase)图。首先我们看到的是整个系统的用例图,如图8-2所示。

离开登陆User房间管理员踢人进入房间发言注册房间管理管理员用户管理系统参数维护

图7-2 系统的总体用例图

从用例图中我们可以看到:系统的两类角色的功能分布比较平均。普通用户是整个系统的被动使用者,他们只能使用聊天室系统的正常功能,而不参与系统的管理和维护。管理员则负责整个系统的管理和维护。为了增加系统的灵活性,管理员还可以也可以参与到聊天室聊天,维护聊天室的管理。另外,管理员还需要维护系统的数据库文件,例如进行备份、加密等方面的工作。

下面是对本系统中涉及到的主要用例的一些简单介绍:

1. 登陆/退出系统:本用例描述了管理员和普通用户如何登陆和退出本系统,登陆时要注意的事项,本系统所有用户都应启用各自的登陆/退出用例。

2. 管理员帐户管理:本用例描述了系统管理员如何进行管理员的帐户管理。本用例可以进一步细化为:密码的维护与管理,管理员的增加和删除等。

3. 聊天室房间管理:本用例描述系统管理员如何维护聊天室系统的房间管理。本用例可以进一步细化为:添加删除聊天室,修改聊天室属性如房间名称,最大聊天成员室。

4. 系统参数管理:本用例主要是描述系统管理员如何管理和配置各种系统参数。本用例可以进一步细分为:服务器服务端口、房间刷新时间、被踢用户重新进入房间的时间间隔。

5. 用户注册:本用例是描述教师如何在该聊天室系统中进行用户注册。本用例可以进一步细分为填写注册信息、打包注册信息并发送注册请求、结果显示。

6. 进入/离开房间:本用例描述如何向系统提交用户进入或者离开房间请求以及系统如何响应该请求。需要指出的是进入房间请求需要用户满足一定的条件:聊天室现有成员没有超过限定数量而且用户如果被房间管理员踢出的间隔时间要大于预定值。而退出房间则比较自由。

7. 发言:本用例描述了用户如何使用聊天室系统进行各种信息的发送,它是系统中最核心的用例。本用例可以进一步细分为公共发言和私聊发言。

8. 房间管理员踢人:本用例描述了房间管理员如何使用本系统完成踢人的功能。需要指出的是,该功能只有房间管理员才具备,其定为第一个进入该聊天室房间的成员。

7.3.2 聊天室系统的系统设计

为了更加详细地介绍本系统中各个用例的工作过程,接下来我们给出本系统中关键用例的UML时序图。时序图反映了各用例的工作流程,以及工作过程中系统各层次间的协作关系,这对编码实现非常重要。

1. 普通用户登陆/退出系统

用户发送登陆请求 ,客户端获取用户登陆信息发送到服务器端,服务器启动用户登陆管理线程,获取用户名和密码进行登陆验证,将验证结果返回给客户端显示。

: User程序客户端程序服务端用户数据发送登陆请求获取用户信息建立连接连接成功发送登陆信息获取登陆信息查询用户信息用户信息验证用户信息返回验证结果显示验证结果

图7-4 普通用户登陆/退出时序图

说明:

系统的所有用户都必须经过登陆之后才能使用系统所提供的功能,不登陆则不能使用。若没有用户名,则必须通过注册获取用户帐户。

2. 管理员帐户管理

管理员登陆后可以发送帐户管理请求,进入帐户管理页面,获取帐户维护信息,验证帐户的合法性(包括唯一性,字符数不超过限定数量等),操作数据库进行相应的更新,最后将结果返回给管理员用户。

: 管理员用户管理页面用户管理用户数据发送请求获取管理员登陆信息发送管理员登陆信息接收信息查询管理员验证信息管理员验证信息验证登陆信息[if 验证成功]获取用户信息返回用户信息发送用户信息显示用户信息发送管理请求获取新的用户信息发送新信息获取信息更新信息更新结果发送结果显示结果

图7-5管理员帐户管理时序图

说明:

管理员帐户管理在进行密码修改时为了增加系统的安全性,需要管理员再次输入密码,若不能验证则将不会修改密码。

3. 聊天室房间管理

聊天室房间管理是整个系统管理维护的核心内容之一。管理员发送请求,聊天室管理系统首先调用数据库获取房间信息列表,管理员可以选择添加或者删除房间,并设置其各种参数,系统首先进行合法性检测,合法以后再更新数据库文件,否则报错,所有的反馈信息将在结果也没进行显示。

: 管理员发送请求房间管理页面房间管理房间信息列表传递请求获取房间信息返回房间信息发送房间信息显示房间信息管理房间信息获取更新后的房间信息发送新房间信息接收信息验证参数的合法性更新数据报告参数非法验证成功验证失败发送更新结果显示操作结果返回更新结果

图7-6聊天室房间管理时序图

4. 系统参数管理

管理员登陆后可以系统参数管理请求,进入系统参数配置页面,系统首先读取数据库显示现有配置方案,管理员可以其各种参数进行修改提交,系统获取新的配置信息,操作数据库进行相应的更新,最后将结果返回给管理员用户。

: 管理员系统参数配置页面发送请求传递请求系统参数管理系统参数获取系统参数返回系统参数发送系统参数显示系统参数修改系统参数获取系统参数发送新参数更新系统参数返回操作结果发送操作结果显示操作结果

图7-7系统参数管理时序图

说明:

由于本系统没有采用实时读取系统参数的方式,所有更新完系统参数后需要重新启动聊天室系统服务器程序后才能生效。

5. 用户注册

用户头次进入聊天室时需要进行用户注册才能登陆聊天室聊天。注册流程如下:用户提出注册请求,客户端响应弹出注册信息对话框,用户输入相关信息并提交,客户端首先进行合法检查,确定无误后将数据信息发送服务器端。服务器接受到请求,启动单独的线程进行处理,首先进行合法性检查,确认登陆名没有与系统中已注册的用户登陆名相同后更新数据库,并返回信息,客户端进行显示。

: User发送请求客户端服务器用户数据获取用户注册信息验证注册信息发送注册信息获取注册信息初步验证成功查询用户信息检测是否重复返回查询结果验证注册信息的合法性创建新的用户验证成功发送ID显示结果返回用户ID

图7-8用户注册时序图

6. 进入房间

用户可以选择不同的房间进入。进入房间流程如下:用户客户端提出进入房间请求,将进入房间的信息发送到服务器端,服务器端首先检查房间是否已满,若满则拒绝,否则再检查用户的合法性,若确定无误,则更新房间信息,并将结果返回给用户。

: User发送请求客户端服务器房间信息用户信息获取房间编号发送房间编号查询房间信息返回房间信息验证房间信息查询用户信息房间未满返回用户信息验证用户信息更新房间信息返回操作结果发送操作结果显示操作结果

图7-9进入房间时序图

7. 发言

发言是聊天室的核心功能。用户提出发言请求,客户端接受发言的内容和其它各种设置(公聊或私聊),打包发给服务器端。服务器接收请求后确定聊天方式,若为公聊则将消息发送到每个用户,若为私聊,则将消息发送到指定的用户。服务结果最后显示在客户端。

: User发送请求客户端本地服务器远程客户端获取发言信息和相关设置信息信息打包发送信息包获取信息包解包获取设置信息发送聊天信息根据不同的设置,发送到不同的客户端返回操作结果发送操作结果显示操作结果

图7-10发言时序图

8. 房间管理员踢人

只有房间管理员才具有踢人的功能。流程如下:房间管理员提出踢人要求,客户端将用户名和踢人请求打包发送给服务器,服务器获取求情首先验证管理员的身份,确定具有权限则执行踢人动作,将所踢用户退出该房间,并更新房间信息,将结果返回给客户端。

: 房间管理员客户端服务器用户信息房间信息发送踢人请求接收请求打包数据发送数据包接收包解包查询用户权限返回权限信息验证用户权限更改房间信息返回操作结果发送操作结果显示结果

图7-11房间管理员踢人时序图

上面即为整个聊天室系统的概要设计,进一步的细化、详细接口描述限于篇幅请读者自行完成。

7.4 设计数据库

数据库在信息管理系统中占有非常重要的地位,其设计的好坏直接影响到整个系统的效率和性能。设计数据库系统时,首先要完成系统的需求分析,包括现有的以及将来可能添加的需求,从而使整个系统具有很好的可扩展性。本小节数据库设计将按照以下几个步骤进行:

数据库需求分析

数据库概念设计

数据库逻辑结构设计

7.4.1 数据库需求分析

用户的需求具体体现在各种信息的提供、保存、更新和查询。这就要求数据库结构能够充分的满足各种信息的输入和输出。收集基本数据、数据结构以及数据处理流程,组成一份详细的数据字典,为下一步的具体设计做好充分的准备。

仔细分析聊天室系统的需求,可以得到如图8-12所示的系统要处理的数据流程图。

用户管理员注册信息录入使用聊天室系统维护管理员帐户管理用户信息管理系统参数设置房间信息管理

图7-12聊天室系统数据流程图

值得注意的是,考虑到数据库的规模,聊天室的发言内容将不存储在数据库中。通过对聊天室系统工作过程的内容和数据流程分析,设以下的数据项和数据结构:

用户信息,包括的数据项有:用户名、昵称、性别、用户提示问题、回答答案、用户邮箱、用户头像、教育水平等。

房间信息,包括的数据项有:房间名、房间所能容纳的最大成员数、房间编号

系统参数配置信息,包括的数据项有:端口号、刷新时间、被踢重新进入房间的限制时间。

管理员信息,包括的数据项有:管理员帐号,密码等。

有了上面的数据字典和数据流程,我们就可以对其进行进一步的概念结构设计。

7.4.2 数据库概念分析

得到上面的数据项和数据结构以后,就可以设计出能够满足用户需求的各种实体以及它们之间的关系,为以后的逻辑结构设计打下基础。这些实体包括各种信息,通过相互之间的作用形成数据的流动。

我们根据上面的需求分析设计的实体有:用户信息实体、房间信息实体、系统参数实体、管理员实体。各个实体具体的E-R图如下文。

用户信息实体E-R图如图8-13所示。

用户信息实体用户名教育水平昵称用户头像„图7-13用户信息实体E-R图

房间信息实体E-R图如图7-14所示。

房间信息实体

房间名所能容纳最大成员数房间编号

图7-14房间信息实体E-R图

系统参数配置实体E-R图如图8-15所示。

系统参数配置实体端口号被踢后重新进入房间的限制时间刷新时间

图7-15系统参数配置实体E-R图

管理员实体E-R图如图7-16所示。

管理员实体管理员帐号密码

图8-16管理员实体E-R图

7.4.3 数据库逻辑结构设计

现在需要将上面的数据库概念设计E-R图转化为能被实际数据库系统所支持的实际数据模型,这就是数据库的逻辑设计。

聊天室系统各个表格的设计结果如下面的表格所示。其中每个表格即表示数据库中的一个表。本系统采用Access数据库作为服务器。

表7-1

字段名称

user_name

user_nickname

user_pwd

user_sex

user_problem

user_answer

user_email

user_map

user_edu

类型

varchar

varchar

varchar

varchar

varchar

varchar

varchar

varchar

varchar

用户信息表(user_info)

可否为空

长度

20

20

20

2

20

20

20

10

10

说明

用户名(主键)

用户昵称

密码

性别

提问

回答

用户邮箱地址

用户头像

用户教育程度

表7-2

字段名称

room_name

room_max_member

Room_id

类型

varchar

int

long

房间信息表(room_info)

可否为空

长度

20

自动

自动

说明

房间名(主键)

最大容纳成员数

房间编号

表7-3

字段名称

port

refreshtime

reintime

系统参数配置表(chat_attribute)

可否为空

长度

20

20

10

说明

端口号

刷新时间

被踢重新进入房间限定时间

类型

varchar

varchar

varchar

表7-4

字段名称

admin_name

admin_pwd

类型

varchar

varchar

管理员表(admin_info)

可否为空

长度

15

15

说明

管理员帐号

密码

7.5 设计用户界面

界面是人机交互的窗口。用户界面接收用户的输入信息,并将程序执行的结果向用户输出。良好的用户界面能够提升用户的工作效率,使系统获得更好的肯定。

根据用例可以制定出用户界面,包括:用户界面的功能、与用户交互的信息,以及用户界面之间的切换关系等。

对于本聊天室,界面主要由三大块组成:

客户端界面

服务器界面

Web端管理界面

下面分别进行介绍。

7.5.1 客户端界面

客户端主要完成与服务器的连接和各种数据的传送,为聊天室用户提供良好的人机界面。界面设计如下:

用户要进入聊天室必须首先经过登陆。聊天室的登陆界面如图7-17所示。用户必须指定用户名,输入密码,以及服务器的地址以及端口。

图7-17客户端登陆界面

若用户通过服务器验证,登陆成功,将出现选择房间界面,如图7-18所示。

图7-18客户端选择房间界面

点选自己感兴趣的房间,将进入主聊天界面,如图7-19所示。

图7-19客户端主聊天界面

7.5.2 服务器界面

Java服务器主要提供各种聊天室服务,包括数据转发,用户信息维护,房间信息维护,其界面主要是提供启动服务的功能,而其中各种管理则有Web管理页面进行承担,如图7-20所示。

图7-20 Java服务器端界面

7.5.3 聊天室Web管理界面

聊天室的Web管理主要是提供通过网络对数据库进行修改的功能,故管理员进行聊天室维护时首先必须进行登陆,其界面如图7-21所示。

图7-21 Web管理登陆页面

管理员登陆通过验证以后,即可进行房间信息管理,用户信息管理,系统参数配置等。房间管理页面见图7-22,而用户的管理页面见图7-23。

图7-22 Web房间管理页面

图7-23 Web用户管理页面

7.6 服务器端设计实现

7.6.1 系统通讯包设计

通讯协议的设计是聊天室程序的一个重要内容,好的通讯协议将使程序有很好的可扩展行和可维护性。本聊天室的通讯包设计如表8-5所示。

表7-5

通讯包名称

登陆包

回答登陆包

注册包

回答注册包

获取房间信息包

回答房间信息包

聊天室通讯包设计表

格式

1@name@pwd

登陆成功:21@1

登陆失败:21@0

2@name@nickname@pwd@email@map@edu@ques@ans@sex

注册成功:22@1

注册失败:22@0

3@

23@room_name1@room_num1@room_na端属性

客户端

服务器端

客户端

服务器端

客户端

服务器端

me2@room_num2@...

进入房间包

进入房间响应包

发送消息包

发送消息响应包

踢人包

踢人响应包

系统退出包

客户端

服务器端

客户端

服务器端

客户端

服务器端

客户端

4@username@roomname

进入房间成功:24@1

退出房间失败:24@0

5@receiver@sender@expression@secret@guolv@str@color

27@0@receiver@expression@sender@str@color

9@name@tickname@course

踢人成功:30@1

踢人失败:30@0

11@name

7.6.2 系统类模块设计

聊天室服务器的开发主要集中于事务管理。其总体框架为创建服务总线程,所有的信息由此接收,并创建子线程进行具体处理。该种设计思路可以使服务器的吞吐量得到大大的提高。在综合前述的设计,构建服务器端所需要的类列表如下。

表7-6

类名

服务器端数据类

简要说明

描述整个聊天室各种信息的类。

WholeChatRoomInfo

包括房间信息,以及服务器的各种属性

DataBean

Packet

RoomInfo

UserTempInfo

UserRegisterInfo

TickInfo

负责提供外界对数据库进行处理的接口

进行包的处理

房间信息类,包括用户列表、房间的各种属性

用户临时信息类,包括姓名、所使用的IP地址、端口号等

用户注册信息类,包括姓名、密码、昵称、头像、电子邮件等

用户被踢时所保存的时间信息

表7-7

类名

ServerThread

CusLoginThread

ExitChatRoom

PatchUserInfoThread

RegisterThread

TicktimeRefresh

服务器端处理事务类

简要说明

负责接收和分发信息的线程类

用来处理与登陆有关的事务的线程类

用来处理当用户退出整个聊天室

定时发送用户列表信息

用来处理当用户注册时的类

用来定时更新被踢时间

整个类图关系如图7-24所示。

图7-24 服务器端类图

可以看出,ServerThread类是服务器端的主线程,其负责整个服务器数据的接收并启动其它服务进程来完成具体的服务。通过数据类和处理事务类的区分使得整个程序框架结构清

晰,便于编程实现,下节将对几个重点的类进行代码分析讲解。

7.6.3 重点代码分析

首先我们分析,该类声明了用于声明发送和接收数据报的DatagramSocket类和DatagramPacket类实例,然后无限循环监听系统设置所绑定的端口,当有服务请求后,分析该请求包所属类型,然后启动相应的服务线程进行具体的响应。其具体代码如下:

//

package chat;

import .*;

import .*;

import .*;

/**

* 类名:serverThread

* 描述:整个服务器端的服务线程,接收客户端请求并且启动相应进程进行处理

*/

public class serverThread extends Thread {

//声明发送和接收数据包

DatagramPacket sendpacket,receivepacket;

//声明发送和接收DatagramSocket

DatagramSocket sendsocket,receivesocket;

//构造函数

public serverThread()

{

try

{

//初始化所有聊天室房间信息

WholeChatRoomInfo wholeInfo=new WholeChatRoomInfo();

lRoom();

//初始化接收和发送数据报socket

sendsocket=new DatagramSocket();

receivesocket=new DatagramSocket(port);

}

catch(SocketException se)

{

//异常的报错机制

tackTrace() ;

(0);

}

}

//线程执行体

public void run()

{

//由于用户信息需要随时更新,这里创建一个线程负责整个用户信息在客户端显示更新

PatchUserInfoThread patchuserInfo=new PatchUserInfoThread();

//启动用户信息更新线程

();

//对被踢成员的时间限制的刷新

TicktimeRefresh ticktimeThread = new TicktimeRefresh();

//启动该线程

();

//死循环

while(true){

try{

//缓冲区申请

byte[]array=new byte[100];

receivepacket=new DatagramPacket(array, );

e(receivepacket);//接收

("nfrom "+ress() +" : ");

String received=new String(a());

(received);

//处理整个接收消息的过程

char mark='@';

int type=0;

int index=f(mark);

type=nt(ing(0,index));

received=ing(index+1);

switch(type)

{

case 1: //登陆请求

CusLoginThread loginthread=new CusLoginThread(receivepacket);

();

//(ng(type));

break;

case 2: //注册信息

RegisterThread register=new RegisterThread(received,receivepacket);

();

break;

case 3: //获取房间信息

case 4: //进入房间

case 5: //发送消息

case 6: //刷新包

case 7: //退出房间

case 8: //查询用户信息

case 9: //踢人包

case 11: //整个退出

RoomAdmThread

RoomAdmThread(receivepacket,type);

();

break;

case 10: //留言

break;

}

}

catch (IOException se){

(ng() +"n");

tackTrace() ;

}

}

}

}

主要完成对房间的各种信息的管理,包括用户信息的接收与转发等,该类的各种属性和方法见图7-25所示

admthread=new

图7-24 RoomAdmThread类

//

package chat;

import chat.*;

import .*;

import .*;

import .*;

public class RoomAdmThread extends Thread {

InetAddress clientIP; //客户IP地址

int clientport; //客户端口

int type; //服务类型

DatagramPacket sendpacket;//声明发送和接收数据包

DatagramPacket receivepacket;//声明发送和接收数据包

DatagramSocket sendsocket; //声明发送和接收DatagramSocket

public RoomAdmThread() {

}

//构造函数(传入参数:接收的数据报和对应的数据报类型)

public RoomAdmThread(DatagramPacket packet,int ty) {

receivepacket=packet;

clientport=t();

clientIP=ress();

type=ty;

}

//向客户端发送所有房间信息

public void sendRoomInfo()

{

String pac=new String();

pac="23";

for(int i=0;i<();i++)

{

RoomInfo rInfo=new RoomInfo();

rInfo=(RoomInfo)tAt(i);

pac+=Netpac();

}

sendUdp(pac);

}

//发送的所给num的房间消息

public void sendRoomMessage(int num)

{

String pac;

RoomInfo rInfo=(RoomInfo)tAt(num);

for(int i=0;i<();i++)

{

sendUdp((String)tAt(i));

}

}

//处理发送用户进入某房间信息文本

public void patchgotoInfo(String name,int num)

{

//发送着+方式+接收者+内容+颜色

//String color="12132";

String pac="27@-1@"+name;

RoomInfo rInfo = (RoomInfo)tAt(num);

for(int i=0;i<();i++)

{

UserTempInfo temp=new UserTempInfo();

temp=(UserTempInfo)tAt(i);

MessagePatchThread mesPatch=new MessagePatchThread(temp,pac);

();

}

}

//处理用户退出某房间信息文本

public void patchgooutInfo(String name,int num,int type)

{

String pac="27@"+ng(type)+"@"+name;

RoomInfo rInfo = (RoomInfo)tAt(num);

if(type!=-3)

sendUdp(pac);

for(int i=0;i<();i++)

{

(pac);

UserTempInfo temp=new UserTempInfo();

temp=(UserTempInfo)tAt(i);

MessagePatchThread mesPatch=new MessagePatchThread(temp,pac);

();

}

}

//用户进入房间处理函数

public boolean gointoRoom(String name,int num)

{

int type=1;

String pac1="24@";

if(ckname(name,num)) //被踢

{

pac1+="0@1@"+ng(me);

sendUdp(pac1);

return false;

}

for(int i=0;i<();i++)

{

UserTempInfo info=(UserTempInfo)tAt(i);

if(eTo(e())==0)

{

String pac2="24@";

RoomInfo rInfo = (RoomInfo)tAt(num);

if(_num<()) //人员满

{

pac1+="0@4"; //人员满

sendUdp(pac1);

return false;

}

int n =xSeq()+1;

pac2+="1@";

int isAdm=();

if(isAdm==0)

{

_name = name;

pac2 += "1"; //第一个为管理员

}

else

{

pac2+="0";

}

omnum=num;

cy=n;

//发送消息

(pac2);

if(ker(info))

{

if(sendUdp(pac2))

{

mentAt(rInfo, num);

(i);

return true;

}

}

else

{

pac1+="0@3"; //已经登陆

sendUdp(pac1);

return false;

}

}

}

pac1+="0@2"; //还未登陆

sendUdp(pac1);

return false;

}

//退出房间处理函数

public boolean getoutofRoom(String name,int num)

{

int type=1;

RoomInfo rInfo=(RoomInfo)tAt(num);

if(lker(name))

{

return true;

}

else

return false;

}

//UDP发送函数

public boolean sendUdp(String pac)

{

byte Array[]=new byte[255];

Array=es();

sendpacket=new DatagramPacket(Array,,

clientIP,

clientport);

try{

sendsocket=new DatagramSocket();

(sendpacket);

return true;

}

catch(Exception e)

{

return false;

}

}

//发送消息处理函数

public void patchmessageInfo(String mes)

{

try{

//接收客户端来的数据

//接收者 发送者姓名 方式 是否私聊 是否过滤 发送内容 颜色

String receiver=new String();

String sender=new String();

String expression=new String();

String secret=new String();

String guolv=new String();

String str=new String();

String color=new String();

char mark = '@';

int index = f(mark);

receiver=ing(0,index);

mes=ing(index+1);

index = f(mark);

sender=ing(0,index);

mes=ing(index+1);

index = f(mark);

expression=ing(0,index);

mes=ing(index+1);

index = f(mark);

secret=ing(0,index);

mes=ing(index+1);

index = f(mark);

guolv=ing(0,index);

mes=ing(index+1);

index = f(mark);

str=ing(0,index);

mes=ing(index+1,());

color=mes;

//mes=ing(index+1);

//发送着+标志+方式+接收者+内容+颜色

//根据要求发送到指定的用户

String pac="27@"+"0@"+receiver+"@"+expression+"@"+sender+"@"+str+"@"+color;

RoomInfo rInfo=new RoomInfo();

rInfo=(RoomInfo)tAt(erRoom(sender));

//私聊

if(eTo("大家")!=0 && eTo("1")==0)

{

for(int i=0;i<();i++)

{

UserTempInfo tempinfo = (UserTempInfo) tAt(i);

if(eTo()==0)

//send

{

MessagePatchThread mesPatch=new MessagePatchThread(tempinfo,pac);

();

return ;

}

}

}

//公聊

else

{

rInfo=(RoomInfo)tAt(erRoom(sender));

sage(pac);

mentAt(rInfo,erRoom(sender));

for (int i = 0; i < (); i++) {

UserTempInfo tempinfo = (UserTempInfo) tAt(i);

//send

MessagePatchThread mesPatch=new MessagePatchThread(tempinfo,pac);

();

}

return ;

}

}

catch(Exception e)

{

}

}

//从房间将某用户踢出处理函数

public boolean tickuser(String received)

{

int index=0;

char mark='@';

index = f(mark, 0);

String name = ing(0, index );

received = ing(index+1);

//获取被踢人的姓名

index = f(mark, 0);

String tickname = ing(0, index );

received = ing(index+1);

//获取被踢原因

index = f(mark, 0);

String course = ing(0, index );

String pac="30@";

if(eTo(tickname)==0)

{

pac+="0";

sendUdp(pac);

return false;

}

UserTempInfo uInfo=new UserTempInfo();

uInfo=obyName(name);

int num=omnum;

if(num>=0 )

{

RoomInfo rInfo = (RoomInfo) tAt(num);

//是否为管理员

if(eTo(_name)==0)

{

ment(obyName(

tickname));

patchgooutInfo(tickname, num,-3);

//设置tick后的情况

try{

Date current = new Date();

TickInfo tick= new TickInfo(tickname,_name,current);

(tick);

}catch(Exception e)

{

}

lker(tickname);

mentAt(rInfo, num);

pac+="1";

sendUdp(pac);

return true;

}

pac+="0";

sendUdp(pac);

return false;

}

else

{

pac+="0";

sendUdp(pac);

return false;

}

}

//退出房间

public void exitroom(String name)

{

int num=erRoom(name);

if(num>=0)

{

RoomInfo rInfo = (RoomInfo) tAt(num);

ment(obyName(name));

if(lker(name))

{

UserTempInfo uInfo = new UserTempInfo();

uInfo = obyName(_name);

String pac = "32@1";

MessagePatchThread mesPatch = new MessagePatchThread(uInfo, pac);

();

}

}

//类执行体

public void run()

{

try {

//接收的数据报字符串

String received = new String(a());

char mark = '@';

int type = 0;

}

mentAt(rInfo, num);

//发送消息

patchgooutInfo(name, num,-2);

int index = f(mark);

type = nt(ing(0, index));

received = ing(index + 1);

//分析数据报是何种类型

switch (type) {

case 3:

//发送房间消息

sendRoomInfo();

break;

case 4:

//进入房间

index = f(mark, 0);

String name = ing(0, index);

//(name);

int roomnum = 0;

received = ing(index + 1);

index=f(mark,0);

roomnum = nt(ing(0, index));

//(ing(0,index));

if (gointoRoom(name, roomnum)) {

patchgotoInfo(name, roomnum);

//UserTempInfo userinfo = new UserTempInfo();

//gei suo you 人都要发

// = clientIP;

// = clientport;

PatchUserInfoThread rInfothread = new PatchUserInfoThread();

serInfo();

sendRoomMessage(roomnum);

}

break;

case 5: //发送消息

patchmessageInfo(received);

break;

case 6: //发送文本列表

break;

case 7: //退出请求

index = f(mark, 0);

name = ing(0, index);

exitroom(name);

PatchUserInfoThread rInfothread1 = new PatchUserInfoThread();

serInfo();

break;

case 9: //踢人包

tickuser(received);

PatchUserInfoThread rInfothread2 = new PatchUserInfoThread();

serInfo();

break;

case 11: //整个退出

index=f(mark);

name=ing(0,index);

if(me(name))

{

exitroom(name);

PatchUserInfoThread rInfothread3 = new PatchUserInfoThread();

serInfo();

}

ExitChatRoom exit=new ExitChatRoom(name);

();

break;

}

}//出错机制

catch (Exception e) {

("Wrong!");

tackTrace() ;

}

}

}

另外一个很重要的类就是,它提供支持最底层数据库操作的方法,包括数据库的连接,关闭,SQL语言的查询,执行等,其类的属性与方法如图7-25所示,具体代码见下所示。

图7-25 DataBean类

package chat;

import .*;

import .*;

/**

* 类名:DataBean

* 描述:完成底层的数据库操作

*/

public class DataBean {

//驱动

static final String DbDriver = "bcDriver";

//连接数据

static final String strConn="jdbc:odbc:chatroom";

private Connection dbCon;

private Statement stl;

public DataBean()

{

}

//数据库连接操作

public boolean connect()

{

try{

erDriver((Driver)(e(DbDriver).newInstance()));

dbCon=nection(strConn);

stl=Statement();

}

catch(Exception e)

{

return false;

}

return true;

}

//数据库的SQL查询

public ResultSet openRs(String sql) throws eption {

ResultSet rs = eQuery(sql);

return (rs);

}

//执行SQL语句

public boolean executeSql(String sql){

try{

e(sql);

}catch (Exception e) {

return false;

}

return true ;

}

//提供对管理员帐户和密码的合法性检查

public boolean checkLogin(String user,String password) {

try{

String sql="select * from admin_info where admin_name='"

+user+"'and admin_pwd='"+password+"'";

ResultSet rs=eQuery(sql);

if(() )

return true;

else

return false;

}catch (Exception e) {

return false;

}

}

//利用SQL语句进行数据库更新

public void update(String strSQL)

{

try

{

eUpdate(strSQL);

}catch(Exception e)

{

tackTrace();

}

}

//关闭数据库

public void close(){

try {

();

stl=null;

dbCon=null;

}

catch (Exception e) {

}

}

}

7.7 Web管理端设计实现

7.7.1

Web管理端模块设计

Web管理端允许管理通过网络对聊天室的各种信息和参数进行维护。包括房间管理、参数配置、用户管理、帐户维护等功能。故我们很容易得出其系统所需要的各个模块,如图7-26所示。

管理员登陆页面管理主界面退出管理房间管理参数配置用户管理帐户管理数据库图8-26 Web管理端模块设计

根据该Web管理的模块设计,我们得到模块所对应的jsp文件分别为:主框架,房间管理,参数配置,用户管理,帐户管理。

7.7.2

Web管理端代码分析

由于该Web管理较为简单,我们只列举其中的两个文件和do_。其中为房间管理的前台页面,负责信息输入和显示,具体代码如下:

!

<%@ page contentType="text/html; charset=GBK" %>

<%@ page import=".*" %>

<%@ page import="chatroomweb.*"%>

</p><p style="text-indent: 2em;";>SetRoomJsp </p><p style="text-indent: 2em;";>

房间管理

<%

int room_count=0;

ResultSet rs;

int i=0;

try

{

t();

String sql="select * from room_info";

rs=(sql);

while(())

{

room_count++;

}

();

} //检查房间数

catch(Exception e)

{

}

finally

{

}

// 显示

%>

现有房间<%=room_count%>间

>

<%

String roomname;

String roomnum;

String sql="select * from room_info";

rs=(sql);

while(())

{

i++;

roomname=ing("room_name");

roomnum=ing("room_max_member");

%>

<%

}

%>

房间名称 容纳最大人数 点选
<%=roomname%>

_GB2312><%=roomnum%>

name=<%="room"+ng(i)%> value="yes">

type="checkbox"

   

>

输入房间参数
房名
最大成员数

%>

-->

do_执行真正的数据库操作,代码如下所示。

!do_

<%@ page contentType="text/html; charset=GBK" %>

<%@ page import=".*" %>

<%@ page import="chatroomweb.*"%>

</p><p style="text-indent: 2em;";>do_UpdateRoomJsp </p><p style="text-indent: 2em;";>

房间管理

<%

String type=ameter("type");

String getCount=ameter("count");

t();

String sql="select * from room_info";

ResultSet rs=(sql);

chDirection(_REVERSE);

if(eTo("1")==0)

{

for(int i=1;i<=nt(getCount);i++)

{

String check;

check=ameter("room"+ng(i));

if(check!=null)

{

if(eTo("yes")==0)

{

te(i);

Row();

("删除房间"+ng(i)+"成功!");

}

}

}

}

else if(eTo("2")==0)

{

String roomname=new String();

String roomnum=new String();

roomname=ameter("upname");

roomnum=ameter("upmembernum");

sql="insert into room_info

values('"+roomname+"','"+roomnum+"')";

if(eSql(sql))

("增加");

}

%>

Web管理其它页面的代码与上两个类似,这里不在赘述,读者自行分析。

(room_name,room_max_member)

7.8 小结

本章讲述了一个通用聊天室系统的开发设计过程,详细的讲述了以下五个方面的内容:

1. 聊天室系统简要介绍:该部分主要讲述了聊天室系统的客户端/服务器设计模式,并且对聊天室的功能做了简要的分析。

2. 网络编程基础:Java通过提供提供低级和高级的网络功能而很好的支持Internet上的各种应用。本小节对其中经常用到的类进行了简要的介绍。

3. 概要设计:概要设计的工作就是设计系统的大体框架,包括对系统的用例分析,领域分析和关键用例的流程分析。用例是系统中需要实现的功能模块,不同的用例可能归属于不同的角色。在本章中,我们主要介绍了用户注册、聊天流程以及管理员进行Web管理的登陆和维护流程。

4. 数据库设计:在数据库设计部分,我们简单讲述了本系统中使用Access为系统所建立的数据表。

5. 用户界面设计:本小节简单的介绍了各个界面的设计以及它们之间的关联。

6. 设计实现:对聊天室的设计和源代码进行了较为深入的分析,读者可以在本实例的基础上进一步完善和扩展功能。