spring websocket開發(fā)

零国拇、總體介紹

項目背景

該項目是通信軟件開發(fā)實訓(xùn)課程的一門作業(yè)考廉,要求完成一個聊天系統(tǒng)昙篙,但要求有至少一個亮點腊状,無論是功能上的還是技術(shù)上的。
我們小組比較擅長Java Web開發(fā)苔可,因此決定在技術(shù)上做一個亮點缴挖,使用WebSocket

文檔說明

該文檔按照如下順序進行介紹

  1. 技術(shù)背景
    介紹該項目要用到的一些背景知識
  2. 項目目錄結(jié)構(gòu)
    該項目使用myeclipse的Java EE項目,大體上可分為前臺與后臺
  3. 項目框架搭建
    介紹前臺用到的技術(shù)焚辅,以及后臺框架的搭建
  4. 項目成果展示
  5. 代碼分享

一映屋、技術(shù)背景

1.WebSocket

WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協(xié)議。WebSocket通訊協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455法焰,WebSocketAPI被W3C定為標(biāo)準(zhǔn)秧荆。
在WebSocket API中倔毙,瀏覽器和服務(wù)器只需要做一個握手的動作埃仪,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道陕赃。兩者之間就直接可以數(shù)據(jù)互相傳送卵蛉。

2.Spring

Spring是一個開源輕量級的框架,使用控制反轉(zhuǎn)(IoC)和面向切面(AOP)么库,它可以讓開發(fā)變得簡單傻丝、輕便、快速與更加靈活诉儒。

3.Spring WebSocket

Spring從4.0開始加入了spring-websocket這個模塊葡缰,并能夠全面支持WebSocket,它與Java WebSocket API標(biāo)準(zhǔn)(JSR-356)保持一致,同時提供了額外的服務(wù)泛释。

4.Spring MVC

Spring MVC是一個model-view-controller(MVC)框架滤愕,能很好地將數(shù)據(jù)、業(yè)務(wù)與展現(xiàn)進行分離怜校。Spring MVC的設(shè)計是圍繞DispatcherServlet展開的间影,DispatcherServlet負(fù)責(zé)將請求派發(fā)到特定的handler。

5.MyBatis

MyBatis 是支持定制化 SQL茄茁、存儲過程以及高級映射的優(yōu)秀的持久層框架魂贬。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或注解裙顽,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄付燥。

在本項目中,我們使用了以上幾個框架進行搭建愈犹。

二机蔗、目錄結(jié)構(gòu)

  • Com.tx.
    Config websocket配置文件
    Controller spring mvc 控制層
    DAO 數(shù)據(jù)庫連接操作
    Handler websocket操作
    Model 數(shù)據(jù)實體
    Service 服務(wù)器處理
    Tool 工具類

  • Webroot
    前臺代碼目錄
    Js javascript代碼
    Style css代碼
    Test 前臺測試代碼

  • WEN-INF 配置文件夾
    Jsp jsp文件夾
    Lib 項目所需的外部jar包
    As-servlet.xml spring mvc 配置文件
    Web.xml web項目配置文件

目錄結(jié)構(gòu)

三、實驗步驟

web服務(wù)前端

1甘萧、socket相關(guān)代碼結(jié)構(gòu)

var socket = new Socket(url);//url必須為ws://開頭的相關(guān)協(xié)議
socket.onopen = function(event){
    //連接初始化代碼
};
sokcet.onmessage = function(event){
    var text = event.data;
    //處理接受到的消息
};
socket.onclose = function(event){
    //連接關(guān)閉時觸發(fā)
};
socket.onerror = function(event){
    //連接過程中出錯時觸發(fā)
}

2.本次實驗所封裝的一些函數(shù)的解析

  1. 消息封裝函數(shù)萝嘁,用于生成能直接添加到html的dom節(jié)點的文檔類型
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. 消息處理函數(shù),用于處理onmessage中由服務(wù)器發(fā)送到客戶端的消息扬卷,并規(guī)定了消息類型與相關(guān)的處理方式
/*
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;
        //向已經(jīng)在線的用戶發(fā)送用戶列表更新信息
        case 2:
            var $userName = $("<p></p>");
            $userName.text(data.username);
            $("#usersInfo").append($userName);

            break;
        //將所有已經(jīng)在線的用戶信息發(fā)送給剛加入的用戶
        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 過濾器 并設(shè)置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攔截器 設(shè)置匹配后綴名為.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>

設(shè)置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用于接收和發(fā)送前后臺的請求與響應(yīng)
前后臺定義用來傳送的json字符串

聊天消息:
'{"type":1,"username":"xxxx","timeSign":"15:21:02","content":"消息內(nèi)容"}'
用戶列表更新消息(已加入):
'{"type":2,"username":"xxxx"}'

用戶列表全部消息(剛加入):
'{"type":3,"usernames":["a","b","c"]}'

用戶列表某用戶信息刪除 :
{"type":4,"username":"xx"}

本例以用戶登錄、注冊為例怪得,其余部分請參考頁面最下方提供的源代碼
這里使用了Spring MVC注解來進行請求的處理

例:

@RequestMapping里面填寫的是請求的地址
return 根據(jù)返回值的不同進行頁面跳轉(zhuǎn)等操作咱枉,這里返回 login 表示跳轉(zhuǎn)到 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配置

有關(guān)mybatis的具體使用說明可以參考 mybatis文檔

1.建立與數(shù)據(jù)庫表對應(yīng)的model,POJO類

這里僅以Student.java類來說明:
類名同數(shù)據(jù)庫中的表名一致徒恋,變量名同同數(shù)據(jù)庫中的字段名一致蚕断,并編寫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設(shè)置

我們使用xml來對mybatis數(shù)據(jù)庫進行配置,里面包括:

  • driver: 數(shù)據(jù)庫驅(qū)動程序
  • url: 數(shù)據(jù)庫連接地址及數(shù)據(jù)庫名
  • username:數(shù)據(jù)庫連接用戶名
  • password:數(shù)據(jù)庫連接密碼
  • 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設(shè)置

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層,調(diào)用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層調(diào)用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設(shè)置

注冊Spring WebSocket服務(wù)

其中registerWebSocketHandlers方法:
registry.addHandler(systemWebSocketHandler(),"xxx");
xxx填寫準(zhǔn)備使用的WebSocket服務(wù)器請求地址径筏,本項目中以 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設(shè)置

服務(wù)器如何處理WebSocket請求在這里面填寫

成功建立連接
接收到消息處理
處理異常
連接關(guān)閉后

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;
    }

}

四葛假、運行結(jié)果

用戶注冊

在瀏覽器中輸入 http://localhost:8080/RealTimeChat/register.do

注冊界面

填寫用戶名及密碼后,點擊注冊按鈕
注冊成功后滋恬,自動跳轉(zhuǎn)至聊天頁面

聊天界面

聊天界面1

頂部為當(dāng)前登錄用戶
左上方為聊天室的聊天窗口
右上方為當(dāng)前聊天室所有的在線用戶
下方為用戶的輸入窗口

登錄界面

新用戶登錄 http://localhost:8080/RealTimeChat/login.do

登錄界面

多人聊天

新用戶加入


chat2

用戶退出

kyle用戶退出


logout

五聊训、項目代碼

github項目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恢氯,隨后出現(xiàn)的幾起案子带斑,更是在濱河造成了極大的恐慌鼓寺,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勋磕,死亡現(xiàn)場離奇詭異侄刽,居然都是意外死亡,警方通過查閱死者的電腦和手機朋凉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門州丹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杂彭,你說我怎么就攤上這事墓毒。” “怎么了亲怠?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵所计,是天一觀的道長。 經(jīng)常有香客問我团秽,道長主胧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任习勤,我火速辦了婚禮踪栋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘图毕。我一直安慰自己夷都,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布予颤。 她就那樣靜靜地躺著囤官,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛤虐。 梳的紋絲不亂的頭發(fā)上党饮,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音驳庭,去河邊找鬼刑顺。 笑死,一個胖子當(dāng)著我的面吹牛嚷掠,可吹牛的內(nèi)容都是我干的捏检。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼不皆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了熊楼?” 一聲冷哼從身側(cè)響起霹娄,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤能犯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后犬耻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體踩晶,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年枕磁,在試婚紗的時候發(fā)現(xiàn)自己被綠了渡蜻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡计济,死狀恐怖茸苇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沦寂,我是刑警寧澤学密,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站传藏,受9級特大地震影響腻暮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毯侦,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一哭靖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侈离,春花似錦款青、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蔗坯,卻和暖如春康震,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宾濒。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工腿短, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绘梦。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓橘忱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卸奉。 傳聞我的和親對象是個殘疾皇子钝诚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容