spring websocket开发

零、总体介绍

项目背景

该项目是学校通信软件开发实训课程的一门作业,要求完成一个聊天系统,但要求有至少一个亮点,无论是功能上的还是技术上的。 我们小组比较擅长Java Web开发,因此决定在技术上做一个亮点,使用WebSocket

文档说明

该文档按照如下顺序进行介绍

  1. 技术背景 介绍该项目要用到的一些背景知识
  2. 项目目录结构 该项目使用myeclipse的Java EE项目,大体上可分为前台与后台
  3. 项目框架搭建 介绍前台用到的技术,以及后台框架的搭建
  4. 项目成果展示
  5. 代码分享

一、技术背景

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);//url必须为ws://开头的相关协议
socket.onopen = function(event){
//连接初始化代码
};
sokcet.onmessage = function(event){
var text = event.data;
//处理接受到的消息
};
socket.onclose = function(event){
//连接关闭时触发
};
socket.onerror = function(event){
//连接过程中出错时触发
}

2.本次实验所封装的一些函数的解析

  1. 消息封装函数,用于生成能直接添加到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) {
/*
message{
userName : xx,
timeSign : 22:12:44,
content : abc
}
*/
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;
}
  1. 消息处理函数,用于处理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
/*
jsonData : {
type:1(聊天信息)||2(用户列表更新信息),
username(1,2):xx,
timeSign(1):xx:xx:xx,
content(1):xxxxxxxxxxxxx,
}
*/
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:数据库连接密码
  • xml映射文件
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">
<!-- 通过username获得密码 -->
<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");
//TODO
}
@Override
public void handleMessage(WebSocketSession session,
WebSocketMessage<?> message) throws Exception {
//TODO
}
@Override
public void handleTransportError(WebSocketSession session,
Throwable exception) throws Exception {
//TODO
}
@Override
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
System.out.println("ConnectionClosed");
//TODO
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}

四、运行结果

用户注册

在浏览器中输入 http://localhost:8080/RealTimeChat/register.do 注册界面 填写用户名及密码后,点击注册按钮 注册成功后,自动跳转至聊天页面

聊天界面

聊天界面1 顶部为当前登录用户 左上方为聊天室的聊天窗口 右上方为当前聊天室所有的在线用户 下方为用户的输入窗口

登录界面

新用户登录 http://localhost:8080/RealTimeChat/login.do 登录界面

多人聊天

新用户加入 chat2

用户退出

kyle用户退出 logout

五、项目代码

github项目地址