零、总体介绍
项目背景
该项目是学校通信软件开发实训课程的一门作业,要求完成一个聊天系统,但要求有至少一个亮点,无论是功能上的还是技术上的。
我们小组比较擅长Java Web开发,因此决定在技术上做一个亮点,使用WebSocket
文档说明
该文档按照如下顺序进行介绍
- 技术背景
介绍该项目要用到的一些背景知识
- 项目目录结构
该项目使用myeclipse的Java EE项目,大体上可分为前台与后台
- 项目框架搭建
介绍前台用到的技术,以及后台框架的搭建
- 项目成果展示
- 代码分享
一、技术背景
1.WebSocket
WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通讯协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
2.Spring
Spring是一个开源轻量级的框架,使用控制反转(IoC)和面向切面(AOP),它可以让开发变得简单、轻便、快速与更加灵活。
3.Spring WebSocket
Spring从4.0开始加入了spring-websocket这个模块,并能够全面支持WebSocket,它与Java WebSocket API标准(JSR-356)保持一致,同时提供了额外的服务。
4.Spring MVC
Spring MVC是一个model-view-controller(MVC)框架,能很好地将数据、业务与展现进行分离。Spring MVC的设计是围绕DispatcherServlet展开的,DispatcherServlet负责将请求派发到特定的handler。
5.MyBatis
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
在本项目中,我们使用了以上几个框架进行搭建。
二、目录结构
-
Com.tx.
Config websocket配置文件
Controller spring mvc 控制层
DAO 数据库连接操作
Handler websocket操作
Model 数据实体
Service 服务器处理
Tool 工具类
-
Webroot
前台代码目录
Js javascript代码
Style css代码
Test 前台测试代码
-
WEN-INF 配置文件夹
Jsp jsp文件夹
Lib 项目所需的外部jar包
As-servlet.xml spring mvc 配置文件
Web.xml web项目配置文件
三、实验步骤
web服务前端
1、socket相关代码结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var socket = new Socket(url); socket.onopen = function(event){ }; sokcet.onmessage = function(event){ var text = event.data; }; socket.onclose = function(event){ }; socket.onerror = function(event){ }
|
2.本次实验所封装的一些函数的解析
- 消息封装函数,用于生成能直接添加到html的dom节点的文档类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function messagePackage(message) {
var element_section = $("<section></section>"); var element_section_p1 = $("<p></p>"); var element_section_p1_user = $("<span></span>"); var element_section_p1_time = $("<time></time>"); var element_section_p2_content = $("<p></p>"); element_section.addClass("message"); element_section_p1.addClass("header"); element_section_p2_content.addClass("content"); element_section_p1_user.text(message.username); element_section_p1_time.text(message.timeSign); element_section_p2_content.text(message.content); element_section_p1.append(element_section_p1_user); element_section_p1.append(element_section_p1_time); element_section.append(element_section_p1); element_section.append(element_section_p2_content); return element_section; }
|
- 消息处理函数,用于处理onmessage中由服务器发送到客户端的消息,并规定了消息类型与相关的处理方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
function messageHandle(event) { var jsonStr = event.data; var data = JSON.parse(jsonStr); var $message = null; switch(data.type) { case 1: if(data.username == currentUser) return; $message = messagePackage({ username : data.username, timeSign : data.timeSign, content : data.content }); $show.append($message); $show.get(0).scrollTop = $show.get(0).scrollHeight; break; case 2: var $userName = $("<p></p>"); $userName.text(data.username); $("#usersInfo").append($userName);
break; case 3: var usernames = data.usernames; var $usersInfo = $("#usersInfo"); $usersInfo.empty(); for(var i= 0,len=usernames.length;i<len;i++) { var $userName = $("<p></p>"); $userName.text(usernames[i]); $usersInfo.append($userName); } break; case 4: var $usersInfo = $("#usersInfo"); $usersInfo.find(":contains("+data.username+")").remove(); } }
|
Spring & Spring MVC配置
1、Web.xml 配置
配置spring 过滤器 并设置UTF-8编码
1 2 3 4 5 6 7 8 9 10 11 12
| <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
配置servlet spring mvc拦截器 设置匹配后缀名为.do
1 2 3 4 5 6 7 8 9 10
| <display-name>as</display-name> <servlet> <servlet-name>as</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>as</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
|
2.as-servlet.xml 配置Spring MVC
使用Spring注解的方式
1 2 3 4 5
| <mvc:annotation-driven /> <context:annotation-config /> <context:component-scan base-package="com.tx.*" />
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
|
返回json模板
1 2 3 4 5 6 7
| <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" /> </list> </property> </bean>
|
设置spring mvc视图
1 2 3 4 5
| <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean>
|
3.编写Spring MVC controller
controller用于接收和发送前后台的请求与响应
前后台定义用来传送的json字符串
1 2 3 4 5 6 7 8 9 10
| 聊天消息: '{"type":1,"username":"xxxx","timeSign":"15:21:02","content":"消息内容"}' 用户列表更新消息(已加入): '{"type":2,"username":"xxxx"}'
用户列表全部消息(刚加入): '{"type":3,"usernames":["a","b","c"]}'
用户列表某用户信息删除 : {"type":4,"username":"xx"}
|
本例以用户登录、注册为例,其余部分请参考页面最下方提供的源代码
这里使用了Spring MVC注解来进行请求的处理
例:
@RequestMapping里面填写的是请求的地址
return 根据返回值的不同进行页面跳转等操作,这里返回 login 表示跳转到 login.jsp 这个页面
1 2 3 4
| @RequestMapping("login.do") public String gtLogin() { return "login"; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Controller public class MainController {
@RequestMapping("register.do") public String gtRegister() { return "register"; }
@RequestMapping("login.do") public String gtLogin() { return "login"; }
@RequestMapping("loginServer.do") public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password) {
ModelAndView modelAndView = new ModelAndView(); StudentService ss = new StudentService(); boolean r = ss.login(username, password); if (r) { modelAndView.addObject("name", username); modelAndView.setViewName("chat"); } else { modelAndView.setViewName("login"); } return modelAndView; }
@RequestMapping(value = "registerServer.do", method = RequestMethod.POST) public ModelAndView gtRegister(@RequestParam("username") String username, @RequestParam("password") String password) { StudentService ss = new StudentService(); ModelAndView modelAndView = new ModelAndView(); boolean r = ss.register(username, password); if (r) { modelAndView.addObject("name", username); modelAndView.setViewName("chat"); } else { modelAndView.setViewName("login"); } return modelAndView; }
}
|
Mybatis配置
有关mybatis的具体使用说明可以参考 mybatis文档
1.建立与数据库表对应的model,POJO类
这里仅以Student.java类来说明:
类名同数据库中的表名一致,变量名同同数据库中的字段名一致,并编写get()和set()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.tx.model;
public class Student { private int id; private String name; private String password;
public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
|
2.mybatis-config.xml设置
我们使用xml来对mybatis数据库进行配置,里面包括:
- driver: 数据库驱动程序
- url: 数据库连接地址及数据库名
- username:数据库连接用户名
- password:数据库连接密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/chat"/> <property name="username" value="root"/> <property name="password" value="password"/> </dataSource> </environment> </environments>
<mappers> <mapper resource="com/tx/model/mapping.xml"/> </mappers>
</configuration>
|
3.mapping.xml设置
mybatis的映射表,通过写sql语句来进行查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tx.model.Mapper">
<select id="getpwd" parameterType="String" resultType="String"> select stuPassword from student where stuName=#{stuName} </select>
<insert id="register" parameterType="String"> insert into student (stuName, stuPassword) values (#{stuName}, #{stuPassword}) </insert>
</mapper>
|
DAO层代码
mybatis的映射文件,方法名与xml中的id相同
1 2 3 4 5 6 7 8 9
| package com.tx.model;
import org.apache.ibatis.annotations.Param;
public interface Mapper { public String getpwd(String stuName); public int register(@Param("stuName")String stuName, @Param("stuPassword")String stuPassword); }
|
DAO层,调用Mybatis的Mapper接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.tx.DAO;
import org.apache.ibatis.session.SqlSession;
import com.tx.model.Mapper; import com.tx.tools.Helper;
public class StudentDAO { SqlSession session = Helper.getSessionFactory().openSession(); Mapper mapper = session.getMapper(Mapper.class); public String getpwd(String username){ return mapper.getpwd(username); } public int register(String name, String password) { int result = 0; SqlSession session = Helper.getSessionFactory().openSession(); try { Mapper mapper = session.getMapper(Mapper.class); result = mapper.register(name, password); session.commit(); } finally { session.close(); } return result; } }
|
service层代码
service层调用DAO层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.tx.service;
import com.tx.DAO.StudentDAO;
public class StudentService {
StudentDAO sdao = new StudentDAO();
public boolean login(String name, String password) { boolean result = false; if (password.equals(sdao.getpwd(name))) { result = true; } return result; } public boolean register(String name, String password) { if(sdao.register(name, password) == 1) { return true; } else { return false; } } }
|
Spring websocket配置
1.WebSocketConfigurer设置
注册Spring WebSocket服务
其中registerWebSocketHandlers方法:
registry.addHandler(systemWebSocketHandler(),“xxx”);
xxx填写准备使用的WebSocket服务器请求地址,本项目中以 webSocketServer.do 为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.tx.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.client.standard.WebSocketContainerFactoryBean; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import com.tx.handler.SystemWebSocketHandler;
@Configuration @EnableWebMvc @EnableWebSocket public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{
@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(systemWebSocketHandler(),"webSocketServer.do"); }
@Bean public WebSocketHandler systemWebSocketHandler(){ return new SystemWebSocketHandler(); }
@Bean public WebSocketContainerFactoryBean createWebSocketContainer() { WebSocketContainerFactoryBean container = new WebSocketContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); return container; } }
|
2.WebSocketHandler设置
服务器如何处理WebSocket请求在这里面填写
成功建立连接
接收到消息处理
处理异常
连接关闭后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class SystemWebSocketHandler implements WebSocketHandler {
@Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { System.out.println("ConnectionEstablished"); }
@Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { }
@Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { }
@Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { System.out.println("ConnectionClosed"); }
@Override public boolean supportsPartialMessages() { return false; }
}
|
四、运行结果
用户注册
在浏览器中输入 http://localhost:8080/RealTimeChat/register.do
填写用户名及密码后,点击注册按钮
注册成功后,自动跳转至聊天页面
聊天界面
顶部为当前登录用户
左上方为聊天室的聊天窗口
右上方为当前聊天室所有的在线用户
下方为用户的输入窗口
登录界面
新用户登录 http://localhost:8080/RealTimeChat/login.do
多人聊天
新用户加入
用户退出
kyle用户退出
五、项目代码
github项目地址