零、总体介绍
项目背景
该项目是通信软件开发实训课程的一门作业,要求完成一个聊天系统,但要求有至少一个亮点,无论是功能上的还是技术上的。
我们小组比较擅长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相关代码结构
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.本次实验所封装的一些函数的解析
- 消息封装函数,用于生成能直接添加到html的dom节点的文档类型
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;
}
- 消息处理函数,用于处理onmessage中由服务器发送到客户端的消息,并规定了消息类型与相关的处理方式
/*
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编码
<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
<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注解的方式
<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模板
<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视图
<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字符串
聊天消息:
'{"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 这个页面
@RequestMapping("login.do")
public String gtLogin() {
return "login";
}
```java
@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()方法
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映射文件
<?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语句来进行查询
<?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相同
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接口
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层
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 为例
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请求在这里面填写
成功建立连接
接收到消息处理
处理异常
连接关闭后
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
填写用户名及密码后,点击注册按钮
注册成功后,自动跳转至聊天页面
聊天界面
顶部为当前登录用户
左上方为聊天室的聊天窗口
右上方为当前聊天室所有的在线用户
下方为用户的输入窗口
登录界面
新用户登录 http://localhost:8080/RealTimeChat/login.do
多人聊天
新用户加入
用户退出
kyle用户退出