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相关代码结构

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节点的文档类型
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中由服务器发送到客户端的消息,并规定了消息类型与相关的处理方式
/*
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

注册界面

填写用户名及密码后,点击注册按钮
注册成功后,自动跳转至聊天页面

聊天界面

聊天界面1

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

登录界面

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

登录界面

多人聊天

新用户加入


chat2

用户退出

kyle用户退出


logout

五、项目代码

github项目地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容