聊天室系统
聊天室对于众多网民来说并不陌生,它是网络上大家讨论交流的有效平台。由于Java强
大的Internet网络程序设计功能,很多聊天室服务器端都是采用Java语言进行实现。本章所
要介绍的开发案例就是利用JAVA实现一个简单的聊天室系统的服务器端。
7.1聊天室系统介绍
网络聊天室是典型的网络应用程序,其一般采用客户/服务器结构来完成整个功能的设
计,下面将概要讨论网络聊天室系统的设计。
7.1.1客户/服务器模式
一般聊天室采用的是客户/服务器模式,我们熟悉的QQ聊天室就是采用这种结构模式。
它包含了两个完整的应用程序,即客户端程序和服务器端程序,如图4-1所示。其中,多个
客户端程序可以同时与一个服务器进行通信,然后通过服务器的统一处理而完成聊天室各种
所需要的功能,如各客户端之间的信息发送和接收。
图7-1ICQ的客户与服务器模式
我们从上可以知道,要实现该通信模式,首先应在服务器和客户端之间定义一套通信协
议,并通过创建套接字来建立连接,然后客户与服务器端再在该连接上进行可靠的传输和接
收数据。客户端发出请求,服务器端监听各种请求并对其提供响应服务。这也即典型的“请
求—应答”模式。
7.1.2聊天室功能介绍
聊天室用户功能
用户注册
新用户输入(用户登陆名,真实姓名,呢称,密码与确认密码),如果登陆名没有与系
统中已注册的用户登陆名相同,且密码与确认密码一致,则系统报告该用户注册成功,否则
提示错误消息。
用户登陆
注册用户输入登陆名与密码,如果与系统中已注册的用户登陆名及密码匹配,则用户登
陆成功,否则提示用户不存在或者密码不匹配。用户登陆成功后,可以选择房间来发言聊天
参数设置(设置服务器IP,端口)
在客户端设置聊天室服务器的参数,主要有IP地址和端口,把参数保存在一个XML
文件中,以便客户端重启时能读入设定的参数
进入房间
用户登陆成功后,可以从房间列表中选择某一房间进入,如果该房间的当前聊天室成员
数没有超过该房间的限定人数,则系统允许该用户进入,否则提示该房间已满。如果用
户是房间的第1个聊天成员,则该用户是该房间管理员,直到该用户退出该房间,房间
管理员有踢人的权力。在用户进入到聊天房间后,该房间的所有聊天成员都可以看到该
用户。
离开房间
用户可随时离开已进入的房间,当用户离开房间后,返回到房间选择窗口。如果房间管
理员离开房间,则把管理权移交给第二个登陆到该房间的用户。
房间管理员踢人
如果聊天房间中某用户破坏房间的聊天气氛,房间管理员可以把该用户剔出该房间,当
用户被剔出某房间后,他在10分钟之内(根据服务器参数设置)不能登陆到该房间。
发言
用户在房间中有两种发言方式:对房间的所有成员发言(所有成员都会收到该发言消
息),对某个成员发言(只有指定成员才能收到该发言消息)
刷新房间列表
在房间选择窗口中,系统自动刷新房间信息,房间信息包含(房间名称,当前成员数量)
用户退出系统
Web管理功能
管理员登陆
修改管理员密码
管理员退出
增加聊天房间
聊天房间属性:房间名称,最大聊天成员数。在客户端出现的房间都是用此功能配置出
来的
删除聊天房间
注册用户浏览
系统参数设置
参数有:端口,房间刷新时间间隔,被踢用户的重新进入房间的时间间隔。
7.1.3聊天室系统实现
本聊天室的实现参照了QQ聊天室系统的设计方法,为了更好的测试Java语言编写的网
络程序功能的正确性和通用性,我们用如下方法开发了整个聊天室系统。
1.利用MicrosoftvisualC++6.0实现聊天室的客户端。
2.利用Java实现聊天室的服务器端。
3.利用JSP设计并实现一个聊天室系统管理页面。
可以看出本章案例是一个集成各种相关技术的综合实例,我们也可以从中得出一些关于
综合技术开发的经验。本章将专注于介绍利用Java实现聊天室的服务器端以及利用JSP的
相关技术开发出简单实用的聊天室Web管理系统,而关于VC++6.0实现的客户端,本书将
提供源代码,具体实现细节可以参照其它相关书籍,在此不再讲述。
在开始聊天室系统设计实现之前,我们有必要对JAVA的网络编程方法有个大致的了解。
7.2节将比较详细的介绍Java所提供用于访问网络资源的类,从而读者入手聊天室将会更加
容易。
7.2JAVA网络编程基础
Java语言非常适合用于分布计算环境,这主要体现在它强大的Internet网络程序设计功
能。下面主要讨论java语言所提供的包,其包含了大部分用于访问网络资源的类。
7.2.1Socket类
利用Java来编写网络应用程序,其中最核心的就是Socket类。它是构造网络程序模块
的基础,利用它我们能够很方便的实现程序间双向的面向连接的通信。但是同一个套接字不
能与两台以上的计算机通讯,当需要多个客户端进行通信的时候,我们必须建立多个Socket
套接字来完成通讯。
Socket类的构造函数一般有四种方式。
表7-1Socket类的构造函数
构造函数抛出异常说明
Socket(Stringhost,intport)
UnknownHostException,
IOException
建立连接到特定主机和端
口的套接字
Socket(InetAddressaddress,intport)IOException
建立连接到指定的IP地址
和端口的套接字
Socket(Stringhost,intport,
InetAddresslocalAddr,intlocalPort)
IOException
建立连接到特定主机和端
口的套接字,并它绑定到特
定的本地地址和本地端口
Socket(InetAddressaddress,intport,
InetAddresslocalAddr,intlocalPort)
IOException
建立连接到指定的IP地址
和端口的套接字,并把它绑
定到特定的本地地址和本
地端口
其中,各构造函数的参数解释如下:
Stringhost——要进行连接的主机名
intport——主机的监听端口
InetAddressaddress——主机的IP地址
InetAddresslocalAddr——套接字所绑定的本地地址
intlocalPort——套接字所绑定的本地端口
下面介绍Socket类一些常用的方法:
InetAddressgetInetAddress()
//返回连接到套接字的远程主机的InetAddress对象。
InetAddressgetLocalAddress()
//返回与套接字关联的本地InetAddress对象。
intgetPort()
//返回套接字连接到的远程服务的端口号。
intgetLocalPort()
//返回该套接字绑定在本地计算机上的端口号。
treamgetInputStream()产生ption异常
//返回一个输入流,它从该套接字连接到的应用程序读取信息。
StreamgetOutputStream()产生ption异常
//返回一个输出流,它向套接字连接到的应用程序写入信息。
o()产生ption异常
//关闭套接字连接。
下面讨论如何通过socket类来编写客户端程序。其基本流程为:
1.通过上述的Socket构造函数,用服务器所在机器的ip以及服务器的端口作为
参数创建一个Socket对象
2.利用Socket类提供的getInputStream和getOutputStream方法来创建输入输
出流。
3.使用输入输出流对象相应的方法来完成客户端与服务器端的开发
7.2.2DatagramSocket和DatagramPacket类
DatagramSocket是一种面向无连接的数据报socket。由于不能建立连接,数据报服务不
能保证所有的数据都能准确有序的到达目的地,但它的速度比有连接协议快,而且像聊天室
这种网络应用程序对可靠性要求不是很高,故其也用的非常普遍。本聊天室就是利用该类来
发送和接收数据。
接收数据报
同Socket连接类似,数据报的接收也需要一个DatagramSocket类用来监听本地主机上
的指定端口,当有数据接收时创建一个DatagramPacket对象实例,调用DatagramSocket的
receive方法来接收数据,这一过程可以重复进行。在其它编程语言中也有类似的过程。
下面给出Java接收数据报典型的程序片断。
//为接收数据报分配缓冲区
Byte[]buffer=newbyte[500];
//为接收数据报实例化DatagramPacket和DatagramSocket
DatagramPacketdatagramPacket=newDatagramPacket(buffer,);
DatagramSocketdatagramSocket=newDatagramSocket(8000);
//接收数据处理
While(true)//无限循环
{
e(datagramPacket);
Strings=newString(buffer,0,0,gth());
}
发送数据报
发送数据报首先创建DatagramPacket实例,指定其所发送的数据缓冲区、数据长度、目
的地主机名和端口号,然后再使用DatagramSocket的nd()方法来发送。
下面给出Java发送数据报典型的程序片断。
//为发送数据报分配缓冲区
Byte[]buffer=newbyte[500];
//为发送数据报实例化DatagramPacket和DatagramSocket
DatagramPacketdatagramPacket=newDatagramPacket(buffer,,address,8000);
DatagramSocketdatagramSocket=newDatagramSocket();
//发送
(datagramPacket)
7.3聊天室系统设计
7.3.1系统建模
对聊天室的分析我们可以明确系统主要有两类角色(Actor)及其相关的用例:
系统管理员:系统管理员维护聊天室系统的所有信息,包括用户管理、聊天室房间
管理、配置服务器参数等。
普通用户:普通可以使用系统进行各种聊天室服务,包括:注册用户名、登陆服务
器、聊天室发言、房间管理员踢人、退出聊天室系统等。
根据前面的用例分析,我们得到了聊天室系统的用例(UCa)图。首先我们看到
的是整个系统的用例图,如图8-2所示。
离开
登陆
进入房间
注册
Ur
发言
踢人
房间管理员
房间管理
用户管理
管理员
系统参数维护
图7-2系统的总体用例图
从用例图中我们可以看到:系统的两类角色的功能分布比较平均。普通用户是整个系统
的被动使用者,他们只能使用聊天室系统的正常功能,而不参与系统的管理和维护。管理员
则负责整个系统的管理和维护。为了增加系统的灵活性,管理员还可以也可以参与到聊天室
聊天,维护聊天室的管理。另外,管理员还需要维护系统的数据库文件,例如进行备份、加
密等方面的工作。
下面是对本系统中涉及到的主要用例的一些简单介绍:
1.登陆/退出系统:本用例描述了管理员和普通用户如何登陆和退出本系统,登陆时要
注意的事项,本系统所有用户都应启用各自的登陆/退出用例。
2.管理员帐户管理:本用例描述了系统管理员如何进行管理员的帐户管理。本用例可
以进一步细化为:密码的维护与管理,管理员的增加和删除等。
3.聊天室房间管理:本用例描述系统管理员如何维护聊天室系统的房间管理。本用例
可以进一步细化为:添加删除聊天室,修改聊天室属性如房间名称,最大聊天成员
室。
4.系统参数管理:本用例主要是描述系统管理员如何管理和配置各种系统参数。本用
例可以进一步细分为:服务器服务端口、房间刷新时间、被踢用户重新进入房间的
时间间隔。
5.用户注册:本用例是描述教师如何在该聊天室系统中进行用户注册。本用例可以进
一步细分为填写注册信息、打包注册信息并发送注册请求、结果显示。
6.进入/离开房间:本用例描述如何向系统提交用户进入或者离开房间请求以及系统如
何响应该请求。需要指出的是进入房间请求需要用户满足一定的条件:聊天室现有
成员没有超过限定数量而且用户如果被房间管理员踢出的间隔时间要大于预定值。
而退出房间则比较自由。
7.发言:本用例描述了用户如何使用聊天室系统进行各种信息的发送,它是系统中最
核心的用例。本用例可以进一步细分为公共发言和私聊发言。
8.房间管理员踢人:本用例描述了房间管理员如何使用本系统完成踢人的功能。需要
指出的是,该功能只有房间管理员才具备,其定为第一个进入该聊天室房间的成员。
7.3.2聊天室系统的系统设计
为了更加详细地介绍本系统中各个用例的工作过程,接下来我们给出本系统中关键用例
的UML时序图。时序图反映了各用例的工作流程,以及工作过程中系统各层次间的协作关
系,这对编码实现非常重要。
1.普通用户登陆/退出系统
用户发送登陆请求,客户端获取用户登陆信息发送到服务器端,服务器启动用户登陆
管理线程,获取用户名和密码进行登陆验证,将验证结果返回给客户端显示。
:Ur
程序客户端程序服务端用户数据
发送登陆请求
获取用户信息
建立连接
连接成功
发送登陆信息
获取登陆信息
查询用户信息
用户信息
验证用户信息
返回验证结果
显示验证结果
图7-4普通用户登陆/退出时序图
说明:
系统的所有用户都必须经过登陆之后才能使用系统所提供的功能,不登陆则不能使
用。若没有用户名,则必须通过注册获取用户帐户。
2.管理员帐户管理
管理员登陆后可以发送帐户管理请求,进入帐户管理页面,获取帐户维护信息,验证帐
户的合法性(包括唯一性,字符数不超过限定数量等),操作数据库进行相应的更新,最后
将结果返回给管理员用户。
:管理员
用户管理页面用户管理用户数据
发送请求
获取管理员登陆信息
发送管理员登陆信息
接收信息
查询管理员验证信息
管理员验证信息
验证登陆信息
[if验证成功]获取用户信息
返回用户信息
发送用户信息
显示用户信息
发送管理请求
发送新信息
获取新的用户信息
获取信息
更新信息
更新结果
发送结果
显示结果
图7-5管理员帐户管理时序图
说明:
管理员帐户管理在进行密码修改时为了增加系统的安全性,需要管理员再次输入密
码,若不能验证则将不会修改密码。
3.聊天室房间管理
聊天室房间管理是整个系统管理维护的核心内容之一。管理员发送请求,聊天室管理系
统首先调用数据库获取房间信息列表,管理员可以选择添加或者删除房间,并设置其各种参
数,系统首先进行合法性检测,合法以后再更新数据库文件,否则报错,所有的反馈信息将
在结果也没进行显示。
:管理员
房间管理页面房间管理房间信息列表
发送请求
传递请求
获取房间信息
返回房间信息
发送房间信息
显示房间信息
管理房间信息
获取更新后的房间信息
发送新房间信息
接收信息
验证参数的合法性
更新数据
验证成功
报告参数非法
验证失败
返回更新结果
发送更新结果
显示操作结果
图7-6聊天室房间管理时序图
4.系统参数管理
管理员登陆后可以系统参数管理请求,进入系统参数配置页面,系统首先读取数据库显
示现有配置方案,管理员可以其各种参数进行修改提交,系统获取新的配置信息,操作数据
库进行相应的更新,最后将结果返回给管理员用户。
:管理员
系统参数配置
页面
系统参数管理系统参数
发送请求
传递请求
获取系统参数
返回系统参数
发送系统参数
显示系统参数
修改系统参数
获取系统参数
发送新参数
更新系统参数
返回操作结果
发送操作结果
显示操作结果
图7-7系统参数管理时序图
说明:
由于本系统没有采用实时读取系统参数的方式,所有更新完系统参数后需要重新启
动聊天室系统服务器程序后才能生效。
5.用户注册
用户头次进入聊天室时需要进行用户注册才能登陆聊天室聊天。注册流程如下:用户提
出注册请求,客户端响应弹出注册信息对话框,用户输入相关信息并提交,客户端首先进行
合法检查,确定无误后将数据信息发送服务器端。服务器接受到请求,启动单独的线程进行
处理,首先进行合法性检查,确认登陆名没有与系统中已注册的用户登陆名相同后更新数据
库,并返回信息,客户端进行显示。
:Ur
客户端服务器用户数据
发送请求
获取用户注册信息
验证注册信息
发送注册信息
初步验证
成功
获取注册信息
查询用户信息
检测是否
重复
返回查询结果
验证注册信息的合法性
创建新的用户
验证成功
返回用户ID
发送ID
显示结果
图7-8用户注册时序图
6.进入房间
用户可以选择不同的房间进入。进入房间流程如下:用户客户端提出进入房间请求,将
进入房间的信息发送到服务器端,服务器端首先检查房间是否已满,若满则拒绝,否则再检
查用户的合法性,若确定无误,则更新房间信息,并将结果返回给用户。
:Ur
客户端服务器房间信息用户信息
发送请求
获取房间编号
发送房间编号
查询房间信息
返回房间信息
验证房间信息
查询用户信息
房间未满
返回用户信息
验证用户信息
更新房间信息
返回操作结果
发送操作结果
显示操作结果
图7-9进入房间时序图
7.发言
发言是聊天室的核心功能。用户提出发言请求,客户端接受发言的内容和其它各种
设置(公聊或私聊),打包发给服务器端。服务器接收请求后确定聊天方式,若为公聊
则将消息发送到每个用户,若为私聊,则将消息发送到指定的用户。服务结果最后显
示在客户端。
:Ur
客户端本地服务器远程客户端
发送请求
获取发言信息和
相关设置信息
信息打包
发送信息包
获取信息包
解包
获取设置信息
发送聊天信息
根据不同的
设置,发送
到不同的客
户端
返回操作结果
发送操作结果
显示操作结果
图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用户信息表(ur_info)
字段名称类型可否为空长度说明
ur_name
varchar
否
20用户名(主键)
ur_nicknamevarchar
否
20
用户昵称
ur_pwdvarchar
否
20
密码
ur_xvarchar
否
2
性别
ur_problem
varchar
是
20
提问
ur_answer
varchar
是
20
回答
ur_email
varchar
是
20
用户邮箱地址
ur_map
varchar
否
10
用户头像
ur_edu
varchar
是
10
用户教育程度
表7-2房间信息表(room_info)
字段名称类型可否为空长度说明
room_name
varchar
否
20房间名(主键)
room_max_memberint
否自动最大容纳成员数
Room_idlong
否自动房间编号
表7-3系统参数配置表(chat_attribute)
字段名称类型可否为空长度说明
port
varchar
否
20端口号
refreshtimevarchar
否
20
刷新时间
reintimevarchar
否
10
被踢重新进入房间限定时间
表7-4管理员表(admin_info)
字段名称类型可否为空长度说明
admin_name
varchar
否
15管理员帐号
admin_pwdvarchar
否
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-20Java服务器端界面
7.5.3聊天室Web管理界面
聊天室的Web管理主要是提供通过网络对数据库进行修改的功能,故管理员进行聊天
室维护时首先必须进行登陆,其界面如图7-21所示。
图7-21Web管理登陆页面
管理员登陆通过验证以后,即可进行房间信息管理,用户信息管理,系统参数配置等。
房间管理页面见图7-22,而用户的管理页面见图7-23。
图7-22Web房间管理页面
图7-23Web用户管理页面
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@x
回答注册包服务器端注册成功:22@1
注册失败:22@0
获取房间信息包客户端
3@
回答房间信息包服务器端
23@room_name1@room_num1@room_na
me2@room_num2@...
进入房间包客户端
4@urname@roomname
进入房间响应包服务器端进入房间成功:24@1
退出房间失败:24@0
发送消息包客户端
5@receiver@nder@expression@cret
@guolv@str@color
发送消息响应包服务器端
27@0@receiver@expression@nder@str
@color
踢人包客户端
9@name@tickname@cour
踢人响应包服务器端踢人成功:30@1
踢人失败:30@0
系统退出包客户端
11@name
7.6.2系统类模块设计
聊天室服务器的开发主要集中于事务管理。其总体框架为创建服务总线程,所有的信息
由此接收,并创建子线程进行具体处理。该种设计思路可以使服务器的吞吐量得到大大的提
高。在综合前述的设计,构建服务器端所需要的类列表如下。
表7-6服务器端数据类
类名简要说明
WholeChatRoomInfo
描述整个聊天室各种信息的类。
包括房间信息,以及服务器的各种属性
DataBean
负责提供外界对数据库进行处理的接口
Packet
进行包的处理
RoomInfo
房间信息类,包括用户列表、房间的各种属性
UrTempInfo
用户临时信息类,包括姓名、所使用的IP地址、端口号等
UrRegisterInfo
用户注册信息类,包括姓名、密码、昵称、头像、电子邮件等
TickInfo
用户被踢时所保存的时间信息
表7-7服务器端处理事务类
类名简要说明
ServerThread
负责接收和分发信息的线程类
CusLoginThread
用来处理与登陆有关的事务的线程类
ExitChatRoom
用来处理当用户退出整个聊天室
PatchUrInfoThread
定时发送用户列表信息
RegisterThread
用来处理当用户注册时的类
TicktimeRefresh
用来定时更新被踢时间
整个类图关系如图7-24所示。
图7-24服务器端类图
可以看出,ServerThread类是服务器端的主线程,其负责整个服务器数据的接收并启动
其它服务进程来完成具体的服务。通过数据类和处理事务类的区分使得整个程序框架结构清
晰,便于编程实现,下节将对几个重点的类进行代码分析讲解。
7.6.3重点代码分析
首先我们分析,该类声明了用于声明发送和接收数据报的
DatagramSocket类和DatagramPacket类实例,然后无限循环监听系统设置所绑定的端口,当
有服务请求后,分析该请求包所属类型,然后启动相应的服务线程进行具体的响应。其具体
代码如下:
//
packagechat;
.*;
.*;
.*;
/**
*类名:rverThread
*描述:整个服务器端的服务线程,接收客户端请求并且启动相应进程进行处理
*/
publicclassrverThreadextendsThread{
//声明发送和接收数据包
DatagramPacketndpacket,receivepacket;
//声明发送和接收DatagramSocket
DatagramSocketndsocket,receivesocket;
//构造函数
publicrverThread()
{
try
{
//初始化所有聊天室房间信息
WholeChatRoomInfowholeInfo=newWholeChatRoomInfo();
lRoom();
//初始化接收和发送数据报socket
ndsocket=newDatagramSocket();
receivesocket=newDatagramSocket(port);
}
catch(SocketException)
{
//异常的报错机制
tackTrace();
(0);
}
}
//线程执行体
publicvoidrun()
{
//由于用户信息需要随时更新,这里创建一个线程负责整个用户信息在客户端显示更
新
PatchUrInfoThreadpatchurInfo=newPatchUrInfoThread();
//启动用户信息更新线程
();
//对被踢成员的时间限制的刷新
TicktimeRefreshticktimeThread=newTicktimeRefresh();
//启动该线程
();
//死循环
while(true){
try{
//缓冲区申请
byte[]array=newbyte[100];
receivepacket=newDatagramPacket(array,);
e(receivepacket);//接收
("nfrom"+ress()+":");
Stringreceived=newString(a());
(received);
//处理整个接收消息的过程
charmark='@';
inttype=0;
intindex=f(mark);
type=nt(ing(0,index));
received=ing(index+1);
switch(type)
{
ca1://登陆请求
CusLoginThreadloginthread=newCusLoginThread(receivepacket);
();
//(ng(type));
break;
ca2://注册信息
RegisterThreadregister=newRegisterThread(received,receivepacket);
();
break;
ca3://获取房间信息
ca4://进入房间
ca5://发送消息
ca6://刷新包
ca7://退出房间
ca8://查询用户信息
ca9://踢人包
ca11://整个退出
RoomAdmThreadadmthread=new
RoomAdmThread(receivepacket,type);
();
break;
ca10://留言
break;
}
}
catch(IOException){
(ng()+"n");
tackTrace();
}
}
}
}
主要完成对房间的各种信息的管理,包括用户信息的接收与转发
等,该类的各种属性和方法见图7-25所示
图7-24RoomAdmThread类
//
packagechat;
importchat.*;
.*;
.*;
.*;
publicclassRoomAdmThreadextendsThread{
InetAddressclientIP;//客户IP地址
intclientport;//客户端口
inttype;//服务类型
DatagramPacketndpacket;//声明发送和接收数据包
DatagramPacketreceivepacket;//声明发送和接收数据包
DatagramSocketndsocket;//声明发送和接收DatagramSocket
publicRoomAdmThread(){
}
//构造函数(传入参数:接收的数据报和对应的数据报类型)
publicRoomAdmThread(DatagramPacketpacket,intty){
receivepacket=packet;
clientport=t();
clientIP=ress();
type=ty;
}
//向客户端发送所有房间信息
publicvoidndRoomInfo()
{
Stringpac=newString();
pac="23";
for(inti=0;i<();i++)
{
RoomInforInfo=newRoomInfo();
rInfo=(RoomInfo)tAt(i);
pac+=Netpac();
}
ndUdp(pac);
}
//发送的所给num的房间消息
publicvoidndRoomMessage(intnum)
{
Stringpac;
RoomInforInfo=(RoomInfo)tAt(num);
for(inti=0;i<();i++)
{
ndUdp((String)tAt(i));
}
}
//处理发送用户进入某房间信息文本
publicvoidpatchgotoInfo(Stringname,intnum)
{
//发送着+方式+接收者+内容+颜色
//Stringcolor="12132";
Stringpac="27@-1@"+name;
RoomInforInfo=(RoomInfo)tAt(num);
for(inti=0;i<();i++)
{
UrTempInfotemp=newUrTempInfo();
temp=(UrTempInfo)tAt(i);
MessagePatchThreadmesPatch=newMessagePatchThread(temp,pac);
();
}
}
//处理用户退出某房间信息文本
publicvoidpatchgooutInfo(Stringname,intnum,inttype)
{
Stringpac="27@"+ng(type)+"@"+name;
RoomInforInfo=(RoomInfo)tAt(num);
if(type!=-3)
ndUdp(pac);
for(inti=0;i<();i++)
{
(pac);
UrTempInfotemp=newUrTempInfo();
temp=(UrTempInfo)tAt(i);
MessagePatchThreadmesPatch=newMessagePatchThread(temp,pac);
();
}
}
//用户进入房间处理函数
publicbooleangointoRoom(Stringname,intnum)
{
inttype=1;
Stringpac1="24@";
if(ckname(name,num))//被踢
{
pac1+="0@1@"+ng(me);
ndUdp(pac1);
returnfal;
}
for(inti=0;i<();i++)
{
UrTempInfoinfo=(UrTempInfo)tAt(i);
if(eTo(e())==0)
{
Stringpac2="24@";
RoomInforInfo=(RoomInfo)tAt(num);
if(_num<())//人员满
{
pac1+="0@4";//人员满
ndUdp(pac1);
returnfal;
}
intn=xSeq()+1;
pac2+="1@";
intisAdm=();
if(isAdm==0)
{
_name=name;
pac2+="1";//第一个为管理员
}
el
{
pac2+="0";
}
omnum=num;
cy=n;
//发送消息
(pac2);
if(ker(info))
{
if(ndUdp(pac2))
{
mentAt(rInfo,num);
(i);
returntrue;
}
}
el
{
pac1+="0@3";//已经登陆
ndUdp(pac1);
returnfal;
}
}
}
pac1+="0@2";//还未登陆
ndUdp(pac1);
returnfal;
}
//退出房间处理函数
publicbooleangetoutofRoom(Stringname,intnum)
{
inttype=1;
RoomInforInfo=(RoomInfo)tAt(num);
if(lker(name))
{
returntrue;
}
el
returnfal;
}
//UDP发送函数
publicbooleanndUdp(Stringpac)
{
byteArray[]=newbyte[255];
Array=es();
ndpacket=newDatagramPacket(Array,,
clientIP,
clientport);
try{
ndsocket=newDatagramSocket();
(ndpacket);
returntrue;
}
catch(Exceptione)
{
returnfal;
}
}
//发送消息处理函数
publicvoidpatchmessageInfo(Stringmes)
{
try{
//接收客户端来的数据
//接收者发送者姓名方式是否私聊是否过滤发送内容颜色
Stringreceiver=newString();
Stringnder=newString();
Stringexpression=newString();
Stringcret=newString();
Stringguolv=newString();
Stringstr=newString();
Stringcolor=newString();
charmark='@';
intindex=f(mark);
receiver=ing(0,index);
mes=ing(index+1);
index=f(mark);
nder=ing(0,index);
mes=ing(index+1);
index=f(mark);
expression=ing(0,index);
mes=ing(index+1);
index=f(mark);
cret=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);
//发送着+标志+方式+接收者+内容+颜色
//根据要求发送到指定的用户
Stringpac="27@"+"0@"+receiver+"@"+expression+"@"+nder+"@"+str+"@"+color;
RoomInforInfo=newRoomInfo();
rInfo=(RoomInfo)tAt(erRoom(nder));
//私聊
if(eTo("大家")!=0&&eTo("1")==0)
{
for(inti=0;i<();i++)
{
UrTempInfotempinfo=(UrTempInfo)tAt(i);
if(eTo()==0)
//nd
{
MessagePatchThreadmesPatch=newMessagePatchThread(tempinfo,pac);
();
return;
}
}
}
//公聊
el
{
rInfo=(RoomInfo)tAt(erRoom(nder));
sage(pac);
mentAt(rInfo,erRoom(nder));
for(inti=0;i<();i++){
UrTempInfotempinfo=(UrTempInfo)tAt(i);
//nd
MessagePatchThreadmesPatch=newMessagePatchThread(tempinfo,pac);
();
}
return;
}
}
catch(Exceptione)
{
}
}
//从房间将某用户踢出处理函数
publicbooleantickur(Stringreceived)
{
intindex=0;
charmark='@';
index=f(mark,0);
Stringname=ing(0,index);
received=ing(index+1);
//获取被踢人的姓名
index=f(mark,0);
Stringtickname=ing(0,index);
received=ing(index+1);
//获取被踢原因
index=f(mark,0);
Stringcour=ing(0,index);
Stringpac="30@";
if(eTo(tickname)==0)
{
pac+="0";
ndUdp(pac);
returnfal;
}
UrTempInfouInfo=newUrTempInfo();
uInfo=obyName(name);
intnum=omnum;
if(num>=0)
{
RoomInforInfo=(RoomInfo)tAt(num);
//是否为管理员
if(eTo(_name)==0)
{
ment(obyName(
tickname));
patchgooutInfo(tickname,num,-3);
//设置tick后的情况
try{
Datecurrent=newDate();
TickInfotick=newTickInfo(tickname,_name,current);
(tick);
}catch(Exceptione)
{
}
lker(tickname);
mentAt(rInfo,num);
pac+="1";
ndUdp(pac);
returntrue;
}
pac+="0";
ndUdp(pac);
returnfal;
}
el
{
pac+="0";
ndUdp(pac);
returnfal;
}
}
//退出房间
publicvoidexitroom(Stringname)
{
intnum=erRoom(name);
if(num>=0)
{
RoomInforInfo=(RoomInfo)tAt(num);
ment(obyName(name));
if(lker(name))
{
UrTempInfouInfo=newUrTempInfo();
uInfo=obyName(_name);
Stringpac="32@1";
MessagePatchThreadmesPatch=newMessagePatchThread(uInfo,pac);
();
}
mentAt(rInfo,num);
//发送消息
patchgooutInfo(name,num,-2);
}
}
//类执行体
publicvoidrun()
{
try{
//接收的数据报字符串
Stringreceived=newString(a());
charmark='@';
inttype=0;
intindex=f(mark);
type=nt(ing(0,index));
received=ing(index+1);
//分析数据报是何种类型
switch(type){
ca3:
//发送房间消息
ndRoomInfo();
break;
ca4:
//进入房间
index=f(mark,0);
Stringname=ing(0,index);
//(name);
introomnum=0;
received=ing(index+1);
index=f(mark,0);
roomnum=nt(ing(0,index));
//(ing(0,index));
if(gointoRoom(name,roomnum)){
patchgotoInfo(name,roomnum);
//UrTempInfourinfo=newUrTempInfo();
//geisuoyou人都要发
//=clientIP;
//=clientport;
PatchUrInfoThreadrInfothread=newPatchUrInfoThread();
rInfo();
ndRoomMessage(roomnum);
}
break;
ca5://发送消息
patchmessageInfo(received);
break;
ca6://发送文本列表
break;
ca7://退出请求
index=f(mark,0);
name=ing(0,index);
exitroom(name);
PatchUrInfoThreadrInfothread1=newPatchUrInfoThread();
rInfo();
break;
ca9://踢人包
tickur(received);
PatchUrInfoThreadrInfothread2=newPatchUrInfoThread();
rInfo();
break;
ca11://整个退出
index=f(mark);
name=ing(0,index);
if(me(name))
{
exitroom(name);
PatchUrInfoThreadrInfothread3=newPatchUrInfoThread();
rInfo();
}
ExitChatRoomexit=newExitChatRoom(name);
();
break;
}
}//出错机制
catch(Exceptione){
("Wrong!");
tackTrace();
}
}
}
另外一个很重要的类就是,它提供支持最底层数据库操作的方法,包括数
据库的连接,关闭,SQL语言的查询,执行等,其类的属性与方法如图7-25所示,具体代
码见下所示。
图7-25DataBean类
packagechat;
.*;
.*;
/**
*类名:DataBean
*描述:完成底层的数据库操作
*/
publicclassDataBean{
//驱动
staticfinalStringDbDriver="bcDriver";
//连接数据
staticfinalStringstrConn="jdbc:odbc:chatroom";
privateConnectiondbCon;
privateStatementstl;
publicDataBean()
{
}
//数据库连接操作
publicbooleanconnect()
{
try{
erDriver((Driver)(e(DbDriver).newInstance()));
dbCon=nection(strConn);
stl=Statement();
}
catch(Exceptione)
{
returnfal;
}
returntrue;
}
//数据库的SQL查询
publicResultSetopenRs(Stringsql)eption{
ResultSetrs=eQuery(sql);
return(rs);
}
//执行SQL语句
publicbooleanexecuteSql(Stringsql){
try{
e(sql);
}catch(Exceptione){
returnfal;
}
returntrue;
}
//提供对管理员帐户和密码的合法性检查
publicbooleancheckLogin(Stringur,Stringpassword){
try{
Stringsql="lect*fromadmin_infowhereadmin_name='"
+ur+"'andadmin_pwd='"+password+"'";
ResultSetrs=eQuery(sql);
if(())
returntrue;
el
returnfal;
}catch(Exceptione){
returnfal;
}
}
//利用SQL语句进行数据库更新
publicvoidupdate(StringstrSQL)
{
try
{
eUpdate(strSQL);
}catch(Exceptione)
{
tackTrace();
}
}
//关闭数据库
publicvoidclo(){
try{
();
stl=null;
dbCon=null;
}
catch(Exceptione){
}
}
}
7.7Web管理端设计实现
7.7.1Web管理端模块设计
Web管理端允许管理通过网络对聊天室的各种信息和参数进行维护。包括房间管理、
参数配置、用户管理、帐户维护等功能。故我们很容易得出其系统所需要的各个模块,如图
7-26所示。
管理员登陆页面
管理主界面
房间管理参数配置用户管理帐户管理
退出管理
数据库
图8-26Web管理端模块设计
根据该Web管理的模块设计,我们得到模块所对应的jsp文件分别为:主框架,
房间管理,参数配置,用户管理,帐
户管理。
7.7.2Web管理端代码分析
由于该Web管理较为简单,我们只列举其中的两个文件和
do_。其中为房间管理的前台页面,负责信息输入和显示,
具体代码如下:
!
<%@pagecontentType="text/html;chart=GBK"%>
<%@pageimport=".*"%>
<%@pageimport="chatroomweb.*"%>
SetRoomJsp
房间管理
<%
introom_count=0;
ResultSetrs;
inti=0;
try
{
t();
Stringsql="lect*fromroom_info";
rs=(sql);
while(())
{
room_count++;
}
();
}//检查房间数
catch(Exceptione)
{
}
finally
{
}
//显示
%>
现有房间<%=room_count%>间
<%
Stringroomname;
Stringroomnum;
Stringsql="lect*fromroom_info";
rs=(sql);
while(())
{
i++;
roomname=ing("room_name");
roomnum=ing("room_max_member");
%>
_GB2312><%=roomname%>
_GB2312><%=roomnum%>
name=<%="room"+ng(i)%>value="yes">
<%
}
%>