零、总体介绍
项目背景
该项目是通信软件开发实训课程的一门作业,要求完成一个聊天系统,但要求有至少一个亮点,无论是功能上的还是技术上的。
我们小组比较擅长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用户退出

网友评论