课程设计说明书
设计题目: 基于java的聊天系统设计与实现
学院、系: 计算机系
专业班级: 计算机2011-1班
学生姓名:
指导教师:
成 绩:
2013年10月27日
基于JAVA的聊天系统的设计与实现
摘 要
网络聊天工具已经作为一种重要的信息交流工具,受到越来越多的网民的青睐。目前,出现了很多非常不错的聊天工具,其中应用比较广泛的有Netmeeting、腾讯QQ、MSN-Messager等等。该系统开发主要包括一个网络聊天服务器程序和一个网络聊天客户程序两个方面。前者通过Socket套接字建立服务器,服务器能读取、转发客户端发来信息,并能刷新用户列表。后者通过与服务器建立连接,来进行客户端与客户端的信息交流。其中用到了局域网通信机制的原理,通过直接继承Thread类来建立多线程。开发中利用了计算机网络编程的基本理论知识,如TCP/IP协议、客户端/服务器端模式(Client/Server模式)、网络编程的设计方法等。在网络编程中对信息的读取、发送,是利用流来实现信息的交换,其中介绍了对实现一个系统的信息流的分析,包含了一些基本的软件工程的方法。经过分析这些情况,该聊天工具采用Eclipse为基本开发环境和java语言进行编写,首先可在短时间内建立系统应用原型,然后,对初始原型系统进行不断修正和改进,直到形成可行系统
关键词:即时通讯系统 B/S C/S MySQL Socket Swing
4.3.1 Message类的设计 14
4.2.2 截图类的设计 15
4.2.3 聊天记录类的设计 16
4.2.4 服务器线程类设计 17
第1章 引言
1.1 开发背景
随着互联网的快速发展,网络聊天工具已经作为一种重要的信息交流工具,受到越来越多的网民的青睐。目前,出现了很多非常不错的聊天工具,其中应用比较广泛的有Netmeeting、腾讯QQ、MSN-Messager等等。无论是个人还是企业等组织机构,对沟通的需求也在不断发展,传统的电话、传真、邮件等沟通方式显然已无法满足当今人们工作和生活沟通的需要。随着软件、网络和通讯三大现代信息技术的发展,在沟通、协作方面有着更多方便、快捷、实时等优势的即时通讯,成为继电话、E-Mail之后又一个完全融入每个人生活的互联网工具。
Java是一个由Sun公司开发而成的新一代编程语言。使用它可在各式各样不同种机器、不同种操作平台的网络环境中开发软件。不论你使用的是哪一种WWW浏览器,哪一种计算机,哪一种操作系统,只要WWW浏览器上面注明了“支持Java”,你就可以看到生动的主页。Java正在逐步成为Internet应用的主要开发语言。它彻底改变了应用软件的开发模式,带来了自PC机以来又一次技术,为迅速发展的信息世界增添了新的活力。
1.2 开发目的和意义
网络通信在当今信息社会中起着不可或缺的作用,人们可以利用网络通信技术进行即时的信息 交流。比如说QQ聊天工具,它就是利用网络通信技术开发的一款众所周知的网络聊天工具。通讯工具最初虽为聊天而诞生,但其作用早已超出了聊天的范畴,随着企业即时通讯工具的出现,即时信息在网络营销中将发挥更大的作用。
目前,信息交流是互联网提供的主要内容,网络通信系统有多种实现方 式,类似ICQ属于一种点对点的聊天系统;还有一种是基于Socket的集中式聊天系统,这种聊天系统需要登录统一的聊天服务器。考虑到要可以在局域网中应用,本系统使用的是第二种方法,经过设置,在局域网或internet上都可以使用。
1.3 研究内容
1、即时通讯原理
首先验证登陆,如果成功,则建立与服务端的socket连接,服务端新开启一个线程专门为它服务,将打包好的Message发送给服务器端,服务器端根据Message里面的信息,再将信息转发给其他用户。一个标准的C/S模式。
2、Swing技术
Swing是一个用于开发Java应用程序用户界面的开发工具包。它以抽象窗口工具包(AWT)为基础使跨平台应用程序可以使用任何可插拔的外观风格。用来实现客服端的界面设计。
3、Java web和struts2技术
采用B/S的结构实现服务器端,对用户和在线用户进行增删改查,和踢用户下线,以及开启服务器和关闭服务器。
4、系统的构建
客户端采用C/S结构,管理端采用B/S的结构,用Tomcat 作为服务器,MySQL作为数据库,还使用到了WindowBuilder开源框架进行界面开发。
第2章 即时通讯系统的相关研究
2.1 C/S开发模式
C/S结构的优点是能充分发挥客户端PC的处理能力,很多工作可以在客户端处理后再提交给服务器。对应的优点就是客户端响应速度快。缺点主要有以下几个:
而随着互联网的飞速发展,移动办公和分布式办公越来越普及,这需要我们的系统具有扩展性。这种方式远程访问需要专门的技术,同时要对系统进行专门的设计来处理分布式的数据。
客户端需要安装专用的客户端软件。首先涉及到安装的工作量,其次任何一台电脑出问题,如病毒、硬件损坏,都需要进行安装或维护。还有,系统软件升级时,每一台客户机需要重新安装,其维护和升级成本非常高。(大多数没法自动升级而需要人工升级)
对客户端的操作系统一般也会有。可能适应于Windows 98,但不能用于Windows 2000或Windows XP。或者不适用于微软新的操作系统等等,更不用说Linux、Unix等。
2.2 B/S开发模式
B/S结构(Browser/Server结构)结构即浏览器和服务器结构。它是随着Internet技术的兴起,对C/S结构的一种变化或者改进的结构。
在这种结构下,用户工作界面是通过WWW浏览器来实现,极少部分事务逻辑在前端(Browser)实现,但是主要事务逻辑在服务器端(Server)实现,形成所谓三层3-tier结构。这样就大大简化了客户端电脑载荷,减轻了系统维护与升级的成本和工作量,降低了用户的总体成本(TCO)。
以目前的技术看,局域网建立B/S结构的网络应用,并通过Internet/Intranet模式下数据库应用,相对易于把握、成本也是较低的。它是一次性到位的开发,能实现不同的人员,从不同的地点,以不同的接入方式(比如LAN, WAN, Internet/Intranet等)访问和操作共同的数据库;它能有效地保护数据平台和管理访问权限,服务器数据库也很安全 。
特别是在JAVA这样的跨平台语言出现之后,B/S架构管理软件更是方便、速度快、效果优。
2.3即时通讯原理
登陆进入聊天软件后,即显示出好友列表,在线的显示亮头像,不在线的显示灰色头像。双击好友头像即会显示出聊天界面。
在聊天页面上,有发送文字,发送截图,和发送文件的功能按钮。
点击“发送”按钮之后,程序就会把输入的信息的种类,信息内容、本人的ID号、对方的ID号以及当前的时间等内容打包成一个Message对象通过Socket发送到服务器端。
服务器接收到Message之后,按照协议进行解析和转发。这样,双方的即时通讯就实现了。
2.4 Java Web 、struts2、Ajax、javascript应用技术
通过这些技术,实现服务器的后台管理端,对用户和在线用户进行增删改查,和踢用户下线,以及开启服务器和关闭服务器。以及用户的注册,前台验证等等
2.5 MySQL数据库应用技术
使用MySQL存储用户信息,采用JDBC技术对其进行增删改查。
2.6 Socket通信技术
Socket程序的工作过程:
1、建立Socket连接:在通信开始之前由通信双方确认身份,建立一条专用的虚拟连接通道。
2、数据通信:利用虚拟连接通道传送数据信息进行通信。
3、关闭:通信结束时,再将所建的虚拟连接拆除。
具体如下:
服务器
图 2-1 socket通信
2.7 开发环境的搭建
客户端采用C/S结构,管理端采用B/S的结构,用Tomcat 作为服务器,MySQL作为数据库,还使用到了WindowBuilder开源框架进行界面开发。
第3章 系统分析
3.1 系统基本功能描述
客户端可以实现注册,即时聊天,相互之间收发文件,发送截图,查看历史聊天记录等功能。收发消息时,可以实现离线接收。
服务器端应当建立一个ServerSocket,并且不断进行侦听是否有客户端连接或者断开连接(包括判断没有响应的连接超时)。服务器端应当是一个信息发送中心,所有客户端的信息都传到服务器端,由服务器端根据要求分发信息。
在后台管理系统,可以到对用户进行增删改查,查看在线用户,和踢用户下线
3.2 可行性分析
本系统的可行性分析包括以下几个方面的内容:
(1) 技术可行性
使用Swing 和socket技术,可以很轻松地开发出实用、简便、高效的基于网络的即时通讯系统。
因此技术上是可以实现的。
(2) 经济可行性
计算机网络已经普及,因此在网络设备上不需要进行大的投入。本系统需要一个MySQL数据库服务器,由于并发使用人数比较少,tomcat可以作为web服务器,所以成本很低。
(3) 操作可行性
只要一台以上计算机连接在同一个局域网内,本系统就可以安装使用,所以操作上完全不存在问题。如果要实现internet上通讯,只需要将服务器端运行在一个有固定IP的公网上就可以。
综上所述,即时Java即时通讯系统是可行的。
3.3 系统需求分析
3.3.1功能分析
本系统要实现的功能如下:
1)注册
服务器收到用户的注册请求,便开始接受客户传递的信息,诸如客户的呢称,性别,籍贯,个人资料等,接受完毕后,便通过JDBC与后台数据库连接,然后向数据库添加记录,如果成功,便向客户返回其号码。客户收到服务器返回的信息后,便打开窗口登陆。
2)登陆
在客户端,用户输入其号码和密码,然后建立与服务器的连接,告诉服务器我要登录,服务器收到后,开始通过JDBC读取数据库,然后与用户输入的信息进行比对,如果成功,便打开主程序窗口。然后客户向服务器请求读取好友名单,服务器收到该请求,从数据库中取出好友列表,然后向客户端发送这些信息,客户收到后就在主窗口显示好友。
3)聊天
客户端首先发送消息到服务器端,服务器端根据发送人信息和接收者信息来转发。例如服务器接收到一个A发给B的消息,先判断B是否已经在线,如果在线就将信息发送过去。如果B不在线则把信息储存在服务器,等B上线了在发送给他。B接收到A发来的信息,如果是未建立对话窗口,则自动打开显示。
4)发送截图
客户端A点击截图按钮,将屏幕锁定,截取图片,将图片储存在byte[]数组中发送到服务器端。服务器端根据发送人信息和接收者信息来转发。B接收到A发来的截图信息,会根据byte[]构造图片,将图片显示出来。同样,如果是未建立对话窗口,则自动打开显示。
5)发送文件
客户端A点击发送文件按键,从本地选择文件,将文件在byte[]数组中分批次发送到服务器端。服务器端根据发送人信息和接收者信息来转发。B接收到A发来的文件信息,会根据文件发送的状态和byte[]构造文件。
6)日志
客户端发送和接收到的信息都会储存在文本文档中。默认储存在C盘下面,会以用户的登录账号为名。
7)对用户的操作
管理员通过B/S的系统对用户进行增加,修改,删除,查找等操作,对系统进行维护。
8)在线用户的操作
管理员通过B/S的系统对在线用户操作,可以踢用户下线。
第4章 系统设计
4.1 数据库设计
本系统中所涉及的主要实体及其属性有:
用户(用户账号,用户名,密码,个性签名,头像ID,年龄,性别)
E-R模型如图所示。
图 4-1系统实体及其属性
表4-1 User
字段名 | 数据类型 | 字段说明 | 键引用 | 备注 |
account | int(6) | ID | PK | 主键(自动增一) |
name | varchar(20) | 用户名称 | 非空 | |
password | varchar(20) | 用户密码 | 非空 | |
signature | Varchar(255) | 签名 | ||
profileID | int(2) | 头像ID | ||
age | int(3) | 年龄 | ||
sex | sex char(2) | 性别 |
4.2.1 聊天系统工作原理图
图 4-2 系统工作原理
4.2.2 系统功能模块图:
图 4-3 系统模块
4.2.3 系统用例图:
普通用户的用例图:
图 4-4 普通用户用例图
后台管理员的用例图:
图 4-5 后台用户用例图
4.2.4 活动图:
1)用户登陆活动图
首先填写登陆信息,提交以后服务器会对用户名和密码进行验证。如果不正确,返回一个提示信息。如果登陆成功,就更新在线状态。通知所有好友自己已经上线。服务器再检查是否有他的离线消息,如果有这立即发送给用户。
图 4-6 用户登录活动图
2)发送消息活动图
短消息发送以后,服务器端对应的监听线程会接收到一个数据包。此包中包含接收者的信息,如果接收者在线,数据包将会转发到接收者。如果不在线,存在服务器端,等它上线在发送。
图 4-7发送消息活动图
4.3 系统类设计
4.3.1 Message类的设计:
客户端和服务器端通信时,发送的都是Message类的对象,因此Message类的设计很重要。
表4-2 Message
属性名 | 作用 |
private Integer msgType; | 信息类型 |
private String content; | 信息的文本内容 |
private Integer senderAccount; | 发送者account |
private Integer receiverAccount; | 接收者account |
private String time; | 发送时间 |
private User myself; private List | 自己本身对象,用与好友列表头 好友列表 |
private transient Image img; | 图片对象(不传送) |
private byte[] imageByte; | 图片的内容 |
private String fileName; | 文件名称 |
private byte[] fileByte; | 文件的长度 |
private int length;//文件长度,以KB为单位 | 文件的内容 |
private int state; | 文件传输的状态: 0是不接受文件,1是接受文件,2请求发送文件,3文件传输中,4文件传送完成 |
private Integer msgType消息的类型有这么几种:
public interface MessageType {
Integer message_succeed=1;//表明是登陆成功
Integer message_login_fail=2;//表明登录失败
Integer message_comm_mes=3;//普通信息包
Integer message_get_onLineFriend=4;//要求在线好友的包
Integer message_ret_onLineFriend=5;//返回在线好友的包
Integer message_offline=6;//下线通知包
Integer message_img=7;//图片包
Integer message_file=8;//文件包
}
4.3.2 截图类的设计:
截图类的实现类为:
class Screenshot extends JFrame implements MouseListener,MouseMotionListener。
图 4-8 Screenshot类
截图类的核心是JDK中:Robot类中createScreenCapture(Rectangle screenRect) 创建包含从屏幕中读取的像素的图像。
先用截取全部屏幕的图片,然后显示出来,覆盖住整个屏幕。然后监听鼠标事件,再用createScreenCapture(Rectangle screenRect)方法截图。
双击表示截图成功,右键表示取消截图。
4.3.3 聊天记录类的设计:
记录日志的功能被封装在MessageRecord类中,所在包为com.im.client.record,在发送或接受到数据后,将其写入日志文件。
图 4-9 MessageRecord类
如果是图片则显示出,发送时间和图片大小。如果是文件则显示出请求发送时间,同意接收时间,接收完成时间,和文件大小。
日志文件格式如下:
图 4-10 记录文件
4.3.4 服务端线程的设计:
服务器端的核心是对应客户的线程,每个socket对应一个线程,也就是每一个客户端都在服务器端有一个相应的线程。它负责转发客户端发来的消息。
线程类为ClientThread:
表4-3 ClientThread类
属性名 | 作用 |
public void notifyOnline() | 通知其他线程,自己上线了,让客户方更新头像 |
public void run() | 线程的主题,转发消息 |
public void notifyOffline(){ | 通知其他线程,自己下线了,让客户方更新头像 |
public void send(Object o) | 发送消息 |
public Object receive() | 接收消息 |
5.1 实现概况
本系统可分为登录、聊天、文件、用户管理和注册五个功能模块。“登录”模块为用户提供登录界面,并在用户登录显示好友列表;“聊天”模块实现用户的即时传送信息,即多用户即时聊天,可以发送文字或者截图;“文件”模块主要实现一用户向另一用户发送文件,对方可以选择接受或者拒绝。聊天记录会以文本文档格式保存。“用户管理”模块即管理员对用户进行增删改查,和踢用户下线的操作。
下面详细介绍各个模块的具体实现。
5.2 注册模块
5.2.1 流程图
图 5-1 用户注册流程图
5.2.2 关键代码
注册的前台页面是showDetail.jsp,后台是UserAction类,UserAction类中有方法:
public String saveOrUpdate(){
us.saveOrUpdate(user);
return "refresh";
}
saveOrUpdate则是用到了Userservice里面的方法:
public void saveOrUpdate(User u){
System.out.println("u.getAccount()="+u.getAccount());
if(u.getAccount()==null)
ud.save(u);
else ud.update(u);
}
DAO类中的方法:
public void save(User u){
conn=getConnection();
try {
ps=conn.prepareStatement(SAVE);
ps.setString(1, u.getName());
ps.setString(2, u.getPassword());
ps.setString(3, u.getSignature());
ps.setInt(4, 1);
ps.setInt(5, u.getAge());
ps.setString(6, u.getSex());
ps.execute();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
close();
}
}
网页上显示用户的account
图 5-2 用户注册信息
5.3 登录模块
5.3.1 流程图
图 5-3 登录流程图
5.3.2 关键代码
1)客户端代码
登录事件是定义在class IMClientLogin extends JFrame 这个类的监听事件中。登录成功后,立即向服务器请求自己的好友列表。
//登录事件
login.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// cus=new IMClientUserServer();
// System.out.println(cus.getServer());
User u=new User();
u.setAccount(Integer.parseInt(account.getText().trim()));
u.setPassword(new String(passwordField.getPassword()));
// 登陆成功
if(cus.checkLogin(u)){
//向服务器发送数据包,请求在线人得信息
ClientConnToServerThread t=Manager.THREAD;
Message m=new Message();
m.setMsgType(MessageType.message_get_onLineFriend);
m.setSenderAccount(u.getAccount());
try {
t.send(m);
} catch (IOException e1) {
e1.printStackTrace();
}
dispose();
}
else
JOptionPane.showMessageDialog(null, "用户名或密码错误", "错误", JOptionPane.ERROR_MESSAGE);
}
});
登录逻辑是在public class IMClientUserServer 里面,认证成功者就会新建一个线程,专门负责接收服务器端发来的信息class ClientConnToServerThread extends Thread 。
public boolean checkLogin(Object o) {
boolean success=false;
Message msg=(Message) server.sendLoginInfo(o);
// System.out.println(msg.getMsgType());
// System.out.println(MessageType.message_succeed);
if(msg.getMsgType().intValue()==MessageType.message_succeed.intValue()){
success=true;
User u=(User) o;
//将好友列表放到管理器中,进行上下线操作
IMFriendList fl=new IMFriendList(msg.getMyself(),msg.getFriends());
Manager.FRIEND_LIST=fl;
//开启客户线程 ,接收服务器端的信息
connThread =new ClientConnToServerThread(server.getSocket());
connThread.setSocket(server.getSocket());
connThread.start();
connThread.setName(u.getAccount()+"");
Manager.THREAD=connThread;
}
return success;
}
客户端通过public class IMClientServer 类与服务器通信
public Object sendLoginInfo(Object o) {
try {
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(o);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message ms = (Message) ois.readObject();
return ms;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
这样按照MVC的模式设计,减小的系统耦合。
2)服务端代码
服务器端接收用户验证信息的代码被写在了一个线程public class ServerThread extends Thread中,这样服务器就可以不停的监听端口,接收来自不同客户端的请求,建立socket连接,也不会堵塞在主线程当中。
从数据库中搜索用户,看是否存在
public boolean checkLogin(User u){
User user=ud.findById(u.getAccount());
if(user!=null){
return user.getPassword().equals(u.getPassword());
}
return false;
}
如果验证成功,查找是否有他的离线消息,有就发送给他。然后在服务器上专门开启一个针对当前用户的线程,并且吧线程放入管理类中,便于日后的管理。
if (checkLogin(u)) {
// 返回一个成功登陆的信息报
User myself=ud.findById(u.getAccount());
m.setMsgType(MessageType.message_succeed);
m.setMyself(myself);
m.setFriends(ud.findAll());//好友列表
oos.writeObject(m);
//为用户单独创建线程
ClientThread t=new ClientThread(s);
//查看是否有离线消息
LinkedList if(list!=null){ for(Message msg:list){ t.send(msg); } list.clear(); } //开启线程 t.start(); t.setName(u.getAccount()+""); //把用户线程放入管理类中 ClientThreadManager.addClientThread(u.getAccount(), t); //通知其他的用户 t.notifyOnline(); System.out.println(" 成功"); } else { m.setMsgType(3); oos.writeObject(m); System.out.println(" 失败"); // 关闭Socket s.close(); } 5.4 聊天模块 5.4.1 流程图 图 5-4 聊天流程图 5.4.2 关键代码 1)客服端代码 发送文字信息: 客户端发送和显示信息的代码被封装在了IMChat extends JFrame implements ActionListener类中,其中发送文字信息的代码 //点击的如果是发送button if (e.getSource() == confirmButton) { Message m=(Message) makePackage(MessageType.message_comm_mes,null); //发送 sendMessage(m); //在自己的Ouput上显示 showMessage(m,Color.BLACK); //清空输入 input_textArea.setText(""); } 当用户点击发送按钮后,先把先把信息显示在自己的聊天窗口上,后方发送信息,自己发送的信息和接收到的信息用不同的颜色显示。 显示文字信息,显示在JtextPane控件中 SimpleAttributeSet attrset = new SimpleAttributeSet(); StyleConstants.setForeground(attrset,c); Document docs = output_pane.getDocument(); // 利用getDocument()方法取得JTextPane的Document //一般信息 if(m.getMsgType().intValue()==MessageType.message_comm_mes){ String str=m.getSenderAccount() + " " +m.getTime()+"\\n"+" "+ m.getContent()+ "\\n"; try { docs.insertString(docs.getLength(), str, attrset); //设置光标到末尾 output_pane.setCaretPosition(docs.getLength()); } catch (BadLocationException e) { e.printStackTrace(); } } 发送图片信息: 在发送图片信息的时候,先将图片转换成为byte[]数组,包装在信息包种发送出去,接收到图片信息时,从byte[]数组构建图片,显示出来。 图片和byte[]之间相互转换的关键代码如下: //将image转换成 byte[] public byte[] imageToBytes(Image img){ ByteArrayOutputStream bos=new ByteArrayOutputStream(); try { ImageIO.write((RenderedImage) img, "jpg", bos); } catch (IOException e) { e.printStackTrace(); } return bos.toByteArray(); } //将 byte[]转换成image public Image bytesToImage(byte[] b){ try { return ImageIO.read(new ByteArrayInputStream(b)); } catch (IOException e) { e.printStackTrace(); } return null; } 在JtextPane中显示图片的代码: if(m.getMsgType().intValue()==MessageType.message_img){ //图片信息 String str=m.getSenderAccount() + " " +m.getTime()+ "\\n"; try { //显示发送人 和时间 docs.insertString(docs.getLength(), str, attrset); //设置光标到末尾 output_pane.setCaretPosition(docs.getLength()); output_pane.insertIcon(new ImageIcon(m.getImg())); docs.insertString(docs.getLength(), "\\n", attrset); //设置光标到末尾 output_pane.setCaretPosition(docs.getLength()); } catch (BadLocationException e) { e.printStackTrace(); } } 在客户端显示截图示例: 图 5-5 聊天截图 2)服务端代码 服务端接收到客户端的信息后,只需要根据他的接收人,进行转发就可以。关键代码如下: Message m = (Message) receive(); // 如果是普通信息 if (m.getMsgType() == MessageType.message_comm_mes.intValue()) { System.out.println(m.getSenderAccount() + "send to"+ m.getReceiverAccount() + "at" + m.getTime()); Integer receiverAccount = m.getReceiverAccount(); ClientThread t = ClientThreadManager.getClientThread(receiverAccount); if (t == null) { //不在线就暂时存储在服务器上 OffLineMsgManager.putSingle(receiverAccount, m); } else t.send(m); } 5.5 文件模块 5.5.1 流程图 图 5-6 发送文件流程图 5.5.2 关键代码 1)客户端代码 在客户端采用JfileChooser来浏览本地文件,点击浏览按钮后,会弹出对话框,让用户选择文件,然后将文件名,大小等信息封装在数据包里,发送出去。关键代码如下: @Override public void mouseClicked(MouseEvent e) { fileDialog.setDialogTitle("选择文件"); fileDialog.showOpenDialog(IMChat.this); //只能选中文件 fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY); f=fileDialog.getSelectedFile(); //发送数据包,请求发送文件 Message m = new Message(); m.setMsgType(MessageType.message_file); m.setSenderAccount(ownerId); m.setReceiverAccount(Integer.parseInt(hideButton.getText())); SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//发送时间 m.setTime(s.format(new java.util.Date())); m.setFileName(f.getName());//文件名 m.setState(2);//请求发送文件 int length=(int) (f.length()/1000); m.setLength(length);//文件大小 sendMessage(m); } 对方会显示提示信息: 图 5-7 询问接收文件图 如果对方同意接收则开启线程。因为文件可能比较大,所以不能一次性发送,只能用线程分批发送。线程关键代码如下: 正常发送文件: 图 5-8 文件传输图 @Override public void run() { try { int num ; // bar.setStringPainted(true); JProgressBar bar=chat.getProgressBar(); m.setMsgType(MessageType.message_file); m.setFileName(f.getName()); BufferedInputStream bos=new BufferedInputStream(new FileInputStream(f)); while((num = bos.read(b)) != -1){ m.setFileByte(b); Manager.THREAD.send(m); int value=LENGTH/10000; bar.setValue(bar.getValue()+value); } bar.setVisible(false); // bar.setValue(0); //发送完毕 SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); m.setTime(s.format(new java.util.Date())); m.setState(4); Manager.THREAD.send(m); //在窗口上显示发送成功 chat.append(new Color(0,0,255), "文件"+f.getName()+"(" +f.length()/1000000+"M)"+"发送成功"+"\\n"); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 发送完成后,双方的界面上都会显示成功信息: 图 5-9 文件传输成功图 客户端接收文件的逻辑: 文件的传输状态被定义成了这几种: 0是不接受文件, 1是接受文件, 2请求发送文件, 3文件传输中, 4文件传送完成 客户端接收文件是可根据这些状态,进行操作,关键代码如下: //请求发送包 if(m.getState()==2){ //对话框 是否接收 int i=JOptionPane.showConfirmDialog(chat, "确定接收" + m.getFileName()+"(" +m.getLength()+"KB)", "接收文件",JOptionPane.YES_NO_OPTION); Integer sender=m.getReceiverAccount(); Integer getter=m.getSenderAccount(); m.setSenderAccount(sender); m.setReceiverAccount(getter); //同意就发送同意接收数据包 if(i==JOptionPane.YES_OPTION){ //打开保存文件对话框 JFileChooser jfc=new JFileChooser("c:/"); jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int mod=jfc.showSaveDialog(chat); if(mod==JFileChooser.APPROVE_OPTION){ //如果选择了路径 File f=jfc.getSelectedFile(); f=new File(f.getAbsolutePath()+"/"+m.getFileName()); bos=new BufferedOutputStream(new FileOutputStream(f)); m.setState(1); send(m); }else{ //选择了取消或关闭对话框 m.setState(0); send(m); } }else{ m.setState(0); send(m); } }else if(m.getState()==3){ //正在传输包 byte b[]=m.getFileByte(); //设置进度条 chat.getProgressBar().setVisible(true); chat.getProgressBar().setMaximum(m.getLength()/10); chat.getProgressBar().setMinimum(0); bos.write(b); }else if (m.getState()==0){ //不同意发送 JOptionPane.showMessageDialog(chat, "对方拒绝接收文件", "通知", JOptionPane.WARNING_MESSAGE); }else if (m.getState()==1){ //同意发送 System.out.println("同意发送"); chat.SendFile(); }else if(m.getState()==4){ System.out.println(m.getFileName()+"传输完成"); chat.getProgressBar().setVisible(false); chat.append(new Color(0,0,255), "文件"+m.getFileName()+"(" +m.getLength()/1000+"M)"+"接收完成"+"\\n"); } } } 2)服务器端代码: 服务端只负责转发,关键代码如下: else if(m.getMsgType()==MessageType.message_file.intValue()){ Integer receiverAccount=m.getReceiverAccount(); ClientThread t=ClientThreadManager.getClientThread(receiverAccount); t.send(m); } 5.6 用户管理模块 5.6.1 流程图 图 5-10 web用户登录图 5.6.2 关键代码 用户管理的前台页面是jsp页面, 在页面中可以对用户进行增删改查。 图 5-11 用户管理图 关键代码如下:以页数列出用户 public List List conn=getConnection(); try { ps=conn.prepareStatement(FIND_BY_Limit); ps.setInt(1, begin); ps.setInt(2, num); rs=ps.executeQuery(); while(rs.next()){ User u=rowMapper(rs); list.add(u); } PreparedStatement p=conn.prepareStatement("select count(*) from user"); rs=p.executeQuery(); while(rs.next()){ rowCount=rs.getInt(1); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ close(); } return list; } 删除用户: public void delete(Integer account){ conn=getConnection(); try { ps=conn.prepareStatement(DELETE); ps.setInt(1, account); ps.execute(); System.out.println("delete over"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ close(); } } 踢用户下线的逻辑也很简单,关闭socket,将服务器端所对应的线程停止,并且从线程集合中删除改线程就可,关键代码如下: System.out.println("==========IOException=========="); e.printStackTrace(); //停止线程 interrupt(); System.out.println(getName()+"下线"); //从线程集中删去本线程 ClientThreadManager.removeClientThread(Integer.parseInt(getName())); //在线人数 System.out.println("在线人数: "+ClientThreadManager.size()); //发送数据包通知下线 notifyOffline(); 用户被题下线后: 图 5-12 用户下线图 5.7 其他功能的实现 5.7.1 截图功能的实现 截图类的核心是JDK中:Robot类中createScreenCapture(Rectangle screenRect) 创建包含从屏幕中读取的像素的图像。 先用new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())); 截取全部屏幕的图片,然后显示出来,覆盖住整个屏幕。然后监听鼠标事件,再用createScreenCapture(Rectangle screenRect)方法截图. 主要代码如下: public void mouseDragged(MouseEvent e) { int w=width; int h=height; // System.out.println(x+" "+y+" "+width+" "+height+" "); width=e.getX()-x; height=e.getY()-y; w=(w>width?w:width); h=(h>height?h:height); //重绘的边界要比真实的稍微大点 //只repaint一定区域,防止屏幕闪烁 repaint(x,y,w+2,h+2); } 如果点击了鼠标右键则取消截图,双击确定截图完成 public void mouseClicked(MouseEvent e) { //如果是鼠标右键的话 if(e.getButton()==MouseEvent.BUTTON3){ dispose(); } if(e.getClickCount()==2){ success(); dispose(); } } 5.7.2 聊天记录功能的实现 如果是图片则显示出,发送时间和图片大小。如果是文件则显示出请求发送时间,同意接收时间,接收完成时间,和文件大小。 主要代码如下: public static void afterSend(Object o) { Message msg=(Message) o; try { File f = new File("c:/"+msg.getSenderAccount().toString()+".txt"); if (!f.exists()) { f.createNewFile(); } BufferedWriter bw=new BufferedWriter(new FileWriter(f,true)); if(msg.getMsgType().intValue()==MessageType.message_comm_mes){ bw.write(msg.getTime()+" 发送消息给 "+msg.getReceiverAccount()+":"+"\\n"); bw.write(msg.getContent()+"\\n"); bw.write("\\n"); } else if(msg.getMsgType().intValue()==MessageType.message_img){ bw.write(msg.getTime()+" 发送图片给 "+msg.getReceiverAccount()+":"+"\\n"); bw.write("图片大小 :"+msg.getImageByte().length+"byte"+"\\n"); bw.write("\\n"); } else if(msg.getMsgType().intValue()==MessageType.message_file){ if(msg.getState()==2){ bw.write(msg.getTime()+" 请求发送文件给 "+msg.getReceiverAccount()+":"+"\\n"); bw.write("文件名 :"+msg.getFileName()+" 文件大小: "+msg.getLength()+"KB"+"\\n"); } if(msg.getState()==0){ bw.write(msg.getTime()+" 拒绝接受文件 :"+msg.getFileName()+"\\n"); } if(msg.getState()==1){ bw.write(msg.getTime()+" 你同意接受文件 :"+msg.getFileName()+"\\n"); } if(msg.getState()==4){ bw.write(msg.getTime()+" 发送给 "+msg.getReceiverAccount()+"的文件 :"+msg.getFileName()+"发送完成"+"\\n"); bw.write("\\n"); } } bw.flush(); bw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 5.7.3 服务端线程的实现 在服务器上,截图信息和文件信息都是进行简单的转发: if(m.getMsgType()==MessageType.message_img.intValue()){ Integer receiverAccount=m.getReceiverAccount(); ClientThread t= ClientThreadManager.getClientThread(receiverAccount); t.send(m);} 转发文字信息时会判断用户是否在线: if (m.getMsgType() == MessageType.message_comm_mes.intValue()) { System.out.println(m.getSenderAccount() + "send to"+ m.getReceiverAccount() + "at" + m.getTime()); Integer receiverAccount = m.getReceiverAccount(); ClientThread t = ClientThreadManager.getClientThread(receiverAccount); if (t == null) { //不在线就暂时存储在服务器上 OffLineMsgManager.putSingle(receiverAccount, m); } else t.send(m); } 5.8 用户界面的设计 5.8.1 登陆界面 登陆界面的实现比较简单,是Jlabel ,JtextFeild,Jpanel等控件的集合 图 5-13 用户登录界面 5.8.2 好友列表 好友列表采用了绝对布局,用到了CardLayout和JscrollPane: 图 5-14 好友列表界面 好友列表展开后: 图 5-15 好友列表展开界面 5.8.3 聊天界面 双击好友头像后,就会显示出聊天界面: 图 5-16 聊天界面 结语 本系统采用C/S和B/S模式,聊天室页面简洁,操作便捷,稳定可靠,性能优良。本系统可以实现通讯系统最基本的两个功能:一是双方能够互相收发信息,二是双方能够互传文件。后台还可以对用户进行管理。 这次课程设计用到了,Swing, Socket,JSP,Struts2,AJAX,JDBC,Thread,JavaScript等各种技术,采用MySQL作为数据库,MyEclipse作为开发工具。基本上包含了大学所学习J2EE和J2SE 的所有知识。在课程设计的过程中,加强了对多线程的理解和掌控。 通过这次的课程设计,我充分体会到学习理论知识的重要性,但更加体会到动手实践的必要性。只有通过亲身的动手实践,才能发现并解决问题,才能真正领悟某一技术的精髓。我相信自己会在将来的软件设计、开发过程中更进一步地学习,不断提升自己的专业能力。