一个基于WebSocket的网页聊天室项目:
先展示一下最终结果(代码在文末):
注册时判断数据库中是否已有用户名与预注册用户名相同,若有,则弹出窗口,提示"注册失败",点击确定后回到注册页面,继续注册。
注册成功之后页面跳转至登录页面,输入用户名或密码,若用户名或密码错误,则弹出窗口,提示"用户名或密码错误",点击确定后回到登录页面,继续登录,若用户名和密码都正确,则进入聊天页面。
每当有一个用户登录,其他在线用户都会收到上线通知,每当有用户下线或直接退出,其他在线用户也会收到下线通知
当左上角所有用户用户名前格子显示被选中时,任何一个用户发送消息,其他用户都能看到,即群聊功能
要实现私聊功能,只需要点击左上角对应用户名复选框,被取消选中的用户将看不到发送者发送的消息,要实现私聊,只需要选中私聊对象用户,发送消息即可
结果展示结束,总结一下具体实现方法及所用到的知识
首先,这个项目中的用户名,密码等信息需要存储在数据库中,并且需要在合适的时候使用MySQL,语句操作数据库进行一些判断(如注册时需要判断预注册用户名是否在数据库中已存在或在登录时需要判断输入的用户名和密码是否在数据库中已存在),所以,需要先了解一下JDBC。
Java数据库连接(java DataBase Connectivity),又称JDBC,用于在Java程序中实现数据库操作功能,它提供了执行SQL语句,访问各种数据库的方法,并为各种不同的数据库提供统一的操作接口,java.sql包中包含了JDBC操作数据库的所有类。通过JDBC访问数据库一般有如下步骤:
在Java程序中,可以通过 Class.forName(“指定数据库的驱动程序”)
方式来加载添加到开发环境中的驱动程序,本项目使用的是MySQL数据库,加载MySQL的数据驱动程序的代码为:Class.forName("com.mysql.jdbc.Driver");
通过DriverManager类创建数据库连接对象Connection。DriverManager类作用于Java程序和JDBC驱动程序之间,用于检查所加载的驱动程序是否可以建立连接,然后通过它的getConnection方法,根据数据库的URL、用户名和密码,创建一个JDBC Connection 对象。如:Connection connection = DriverManager.geiConnection(“连接数据库的URL", "用户名", "密码”)
。其中,URL=协议名+IP地址(域名)+端口+数据库名称;用户名和密码是指登录数据库时所使用的用户名和密码。
Statement 类的主要是用于执行静态 SQL 语句并返回它所生成结果的对象。通过Connection 对象的 createStatement()
方法可以创建一个Statement对象:Statement statament = connection.createStatement()
;
但是Statement对象存在SQL注入漏洞的问题:即SQL可以使用关键字将一条查询语句变成多条,或将后面的语句变成注释【如:or,–】,这个问题可以通过**使用PrepareedStatement(预处理SQL)**来解决:
PrepareStatement继承于Statement,但他与Statement在两方面有所不同:
1)PreparedStatement 实例包含已编译的 SQL 语句。这就是使语句“准备好”。包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX 方法来提供。
2)由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
调用Statement对象的相关方法执行相对应的 SQL 语句。
例如通过execuUpdate()方法用来判断数据是否插入成功,再这个项目中可转换为是否注册成功的判断。
又如通过executeQuery()方法把数据库响应的查询结果存放在ResultSet类对象中,供我们判断或查询,在这个项目中可以转换为登录时用户名和密码是否是数据库中存在的用户的判断。
使用完数据库或者不需要访问数据库时,通过Connection和Statement的close() 方法关闭连接和Statement对象,若执行了查询操作(即本项目中的登录操作),则还需要通过ResultSet类的close()方法关闭查询的结果集对象。
在JDBC常规操作流程中,需要第一步首先是加载数据库驱动,然后通过DriverManager.geiConnection()
获取连接,如果是对于一个简单的数据库,且对数据库访问不是很频繁,这种方式完全可行,但是如果面对较复杂的数据库且需要频繁访问数据库时,频繁的建立、关闭连接,会极大的减低系统的性能,所以可以考虑使用DataSource数据源,数据源建立多个数据库连接,这些数据库连接会保存在数据库连接池中,当需要访问数据库时,只需要从数据库连接池中获取空闲的数据库连接,当程序访问数据库结束时,数据库连接会放回数据库连接池中。其基本原理是是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。
使用数据库连接池的优势:
1、资源复用
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
2、更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接至于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间,从而缩减了系统整体响应时间。
3、统一的连接管理,避免数据库连接泄漏
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。
private static DataSource dataSource;
Properties properties = CommUtils. loadProperties("datasource.properties");
dataSource = DruidDataSourceFactory.createDataSource(properties);
DriverManager.getConnection()
,而是DataSource.getConnection();
Druid号称是Java语言中最好的数据库连接池。
Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,广泛应用于各种数据的交互中,尤其是服务器与客户端的交互。
在需要提交的属性或元素项较多时,一个一个提交是一件很麻烦的事:
toJson();
方法将任意类型的元素合成为一个Json字符串。fromJson();
方法将一个Json字符串还原为原始类型。Servlet(Server Applet)是Java Servlet的简称,称为小服务程序,即用Java编写的服务器端程序;主要功能在于交互式地浏览和修改数据,生成动态Web内容。
狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了Servlet接口的类。
一般情况下,我们将Servlet理解为前者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
Servlet 的主要功能在于交互式地浏览和修改数据,生成动态 Web 内容
一个 Servlet 就是 Java 编程语言中的一个类,它被用来扩展服务器的性能,服务器上驻留着可以通过“请求-响应”编程模型来访问的应用程序。虽然 Servlet 可以对任何类型的请求产生响应,但通常只用来扩展 Web 服务器的应用程序。
FreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出。它不仅可以用作表现层的实现技术,而且还可以用于生成XML,JSP或Java等。模板用servlet提供的数据动态地生成HTML,通常由Java程序准备要显示的数据,由FreeMarker生成页面,通过模板显示准备的数据。
后端必须加载模板引擎
WebSocket 的实现分为握手,数据发送/读取,关闭连接
实现后端JDBC的操作:在数据库连接池加载配置文件,获取连接,建立Statement对象或PreparedStatement对象,执行SQL语句, 关闭数据库资源。
实现用户注册功能:在前端页面输入待注册用户名和密码,通过websocket提交到后端注册模块,进入步骤1中‘执行SQL语句’过程,返回数据库被改变的行数,返回0,则说明待注册用户名已存在不可注册,留在注册页面继续注册。返回1,则说明待注册用户名在数据库中不存在,可以注册,注册成功之后跳转至登录页面。
实现用户登录功能:在前端页面输入待注册用户名和密码,通过websocket提交到后端登录模块,进入步骤1中‘执行SQL语句’过程,返回一个查询的结果集,若返回为空,说明该用户名和密码在数据库中不存在,或用户名/密码错误,返回登录页面重新登录,若得到结果不为空,说明该用户名和密码在数据库中存在,可以直接登录,跳转至聊天页面。
实现上线通知和下线通知:
上线:将当前客户端聊天实体,用户名和SessionID保存到会话中,向所有在线用户广播上线通知,同时进入聊天就绪状态;
下线:将当前客户端聊天实体移除,将登入的用户名和SessionID保存到会话中,向所有在线用户广播下线通知
聊天功能实现:服务端收到当前被序列化处理后的Json字符串,将其反序列化,通过判断得到聊天类型(群聊/私聊),分别进行处理,若为群聊,需得到发送消息的用户名和消息实体,再将其序列化为Json字符串向当前WebSocket中的所有在线用户发送。若为私聊,只需在接受的信息中心添加发送对象信息,再经过判断以同样的方式将消息发送给特定对象即可。
GitHub:网页聊天室
参考资料:
数据库连接池优缺点详解
DruidDataSource源码解析
Gson全解析
Servlet