SSM(五)基于webSocket的聊天室

o

前言

不知大家在平時的需求中有沒有遇到需要實(shí)時處理信息的情況,如站內(nèi)信蚂维,訂閱戳粒,聊天之類的。在這之前我們通常想到的方法一般都是采用輪訓(xùn)的方式每隔一定的時間向服務(wù)器發(fā)送請求從而獲得最新的數(shù)據(jù)虫啥,但這樣會浪費(fèi)掉很多的資源并且也不是實(shí)時的蔚约,于是隨著HTML5的推出帶來了websocket可以根本的解決以上問題實(shí)現(xiàn)真正的實(shí)時傳輸。

websocket是什么涂籽?

至于websocket是什么苹祟、有什么用這樣的問題一Google一大把,這里我就簡要的說些websocket再本次實(shí)例中的作用吧评雌。
由于在本次實(shí)例中需要實(shí)現(xiàn)的是一個聊天室树枫,一個實(shí)時的聊天室。如下圖:

1.gif

采用websocket之后可以讓前端和和后端像C/S模式一樣實(shí)時通信景东,不再需要每次單獨(dú)發(fā)送請求砂轻。由于是基于H5的所以對于老的瀏覽器如IE7、IE8之類的就沒辦法了斤吐,不過H5是大勢所趨這點(diǎn)不用擔(dān)心舔清。

后端

既然推出了websocket,作為現(xiàn)在主流的Java肯定也有相應(yīng)的支持曲初,所以在JavaEE7之后也對websocket做出了規(guī)范,所以本次的代碼理論上是要運(yùn)行在Java1.7+和Tomcat7.0+之上的杯聚。
看過我前面幾篇文章的朋友應(yīng)該都知道本次實(shí)例也是運(yùn)行在之前的SSM之上的臼婆,所以這里就不再贅述了。
首先第一步需要加入websocket的依賴:

        <!-- https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api -->
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${spring.version}</version>
        </dependency>

以上就是使用websocket所需要用到的包幌绍。spring-websocket這個主要是在之后需要在websocket的后端注入service所需要的颁褂。
之后再看一下后端的核心代碼MyWebSocket.java

package com.crossoverJie.controller;

/**
 * Created by Administrator on 2016/8/7.
 */
import com.crossoverJie.pojo.Content;
import com.crossoverJie.service.ContentService;
import org.apache.camel.BeanInject;
import org.apache.camel.EndpointInject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.springframework.web.socket.server.standard.SpringConfigurator;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.annotation.PostConstruct;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

//該注解用來指定一個URI故响,客戶端可以通過這個URI來連接到WebSocket。
/**
  類似Servlet的注解mapping颁独。無需在web.xml中配置彩届。
 * configurator = SpringConfigurator.class是為了使該類可以通過Spring注入。
 */
@ServerEndpoint(value = "/websocket",configurator = SpringConfigurator.class)
public class MyWebSocket {
    //靜態(tài)變量誓酒,用來記錄當(dāng)前在線連接數(shù)樟蠕。應(yīng)該把它設(shè)計成線程安全的。
    private static int onlineCount = 0;

    public MyWebSocket() {
    }

    @Autowired
    private ContentService contentService ;

    //concurrent包的線程安全Set靠柑,用來存放每個客戶端對應(yīng)的MyWebSocket對象寨辩。
    // 若要實(shí)現(xiàn)服務(wù)端與單一客戶端通信的話,可以使用Map來存放歼冰,其中Key可以為用戶標(biāo)識
    private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();

    //與客戶端的連接會話靡狞,需要通過它來給客戶端發(fā)送數(shù)據(jù)
    private Session session;

    /**
     * 連接建立成功調(diào)用的方法
     * @param session  可選的參數(shù)。session為與某個客戶端的連接會話隔嫡,需要通過它來給客戶端發(fā)送數(shù)據(jù)
     */
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在線數(shù)加1
        System.out.println("有新連接加入甸怕!當(dāng)前在線人數(shù)為" + getOnlineCount());
    }

    /**
     * 連接關(guān)閉調(diào)用的方法
     */
    @OnClose
    public void onClose(){
        webSocketSet.remove(this);  //從set中刪除
        subOnlineCount();           //在線數(shù)減1
        System.out.println("有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount());
    }

    /**
     * 收到客戶端消息后調(diào)用的方法
     * @param message 客戶端發(fā)送過來的消息
     * @param session 可選的參數(shù)
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("來自客戶端的消息:" + message);
        //群發(fā)消息
        for(MyWebSocket item: webSocketSet){
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    /**
     * 發(fā)生錯誤時調(diào)用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error){
        System.out.println("發(fā)生錯誤");
        error.printStackTrace();
    }

    /**
     * 這個方法與上面幾個方法不一樣腮恩。沒有用注解梢杭,是根據(jù)自己需要添加的方法。
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException{
        //保存數(shù)據(jù)到數(shù)據(jù)庫
        Content content = new Content() ;
        content.setContent(message);
        SimpleDateFormat sm = new SimpleDateFormat("yyyy-MM-dd HH:mm:dd") ;
        content.setCreateDate(sm.format(new Date()));
        contentService.insertSelective(content) ;

        this.session.getBasicRemote().sendText(message);
        //this.session.getAsyncRemote().sendText(message);


    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        MyWebSocket.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        MyWebSocket.onlineCount--;
    }

}

這就是整個websocket的后端代碼庆揪∈角看起來也比較簡單主要就是使用那幾個注解。每當(dāng)有一個客戶端連入缸榛、關(guān)閉吝羞、發(fā)送消息都會調(diào)用各自注解的方法。這里我講一下sendMessage()這個方法内颗。

websocket繞坑

sendMessage()方法中我只想實(shí)現(xiàn)一個簡單的功能钧排,就是將每次的聊天記錄都存到數(shù)據(jù)庫中【模看似一個簡單的功能硬是花了我半天的時間恨溜。
我先是按照以前的慣性思維只需要在這個類中注入service即可。但是無論怎么弄每次都注入不進(jìn)來都是null找前。
最后沒辦法只有g(shù)oogle了糟袁,最后終于在神級社區(qū)StackOverFlow中找到了答案,就是前邊所說的需要添加的第二個 maven依賴躺盛,然后加入@ServerEndpoint(value = "/websocket",configurator = SpringConfigurator.class)這個注解即可利用Spring注入了项戴。接著就可以做消息的保存了。

前端

前端我采用了Bootstrap做的槽惫,不太清楚Bootstrap的童鞋建議先看下官方文檔也比較簡單周叮。還是先貼一下代碼:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>

<!DOCTYPE HTML>
<html>
<head>
    <base href="<%=basePath%>">
    <!-- Bootstrap -->
    <link rel="stylesheet"
          >
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="http://cdn.bootcss.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="http://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->
    <script type="text/javascript" charset="utf-8" src="<%=path%>/ueditor/ueditor.config.js"></script>
    <script type="text/javascript" charset="utf-8" src="<%=path%>/ueditor/ueditor.all.min.js"> </script>
    <!--建議手動加在語言辩撑,避免在ie下有時因?yàn)榧虞d語言失敗導(dǎo)致編輯器加載失敗-->
    <!--這里加載的語言文件會覆蓋你在配置項目里添加的語言類型,比如你在配置項目里配置的是英文仿耽,這里加載的中文合冀,那最后就是中文-->
    <script type="text/javascript" charset="utf-8" src="<%=path%>/ueditor/lang/zh-cn/zh-cn.js"></script>

    <title>聊天室</title>
</head>

<body data="/ssm">
<input id="text" type="text"/>
<button onclick="send()">發(fā)送</button>
<button onclick="closeWebSocket()">關(guān)閉連接</button>
<div id="message">
</div>


<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-primary">
                <div class="panel-heading">聊天室</div>
                <div id="msg" class="panel-body">

                </div>
                <div class="panel-footer">
                    在線人數(shù)<span id="onlineCount">1</span>人
                </div>
            </div>
        </div>
    </div>
</div>

<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <script id="editor" type="text/plain" style="width:1024px;height:200px;"></script>
        </div>
    </div>

</div>

<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <p class="text-right">
            <button onclick="sendMsg();" class="btn btn-success">發(fā)送</button>
            </p>
        </div>
    </div>

</div>

</body>

<script type="text/javascript">
    var ue = UE.getEditor('editor');
    var websocket = null;

    //判斷當(dāng)前瀏覽器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://192.168.0.102:8080/ssm/websocket");
    }
    else {
        alert("對不起!你的瀏覽器不支持webSocket")
    }

    //連接發(fā)生錯誤的回調(diào)方法
    websocket.onerror = function () {
        setMessageInnerHTML("error");
    };

    //連接成功建立的回調(diào)方法
    websocket.onopen = function (event) {
        setMessageInnerHTML("加入連接");
    };

    //接收到消息的回調(diào)方法
    websocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    };

    //連接關(guān)閉的回調(diào)方法
    websocket.onclose = function () {
        setMessageInnerHTML("斷開連接");
    };

    //監(jiān)聽窗口關(guān)閉事件项贺,當(dāng)窗口關(guān)閉時君躺,主動去關(guān)閉websocket連接,
    // 防止連接還沒斷開就關(guān)閉窗口敬扛,server端會拋異常晰洒。
    window.onbeforeunload = function () {
        var is = confirm("確定關(guān)閉窗口?");
        if (is){
            websocket.close();
        }
    };

    //將消息顯示在網(wǎng)頁上
    function setMessageInnerHTML(innerHTML) {
        $("#msg").append(innerHTML+"<br/>")
    };

    //關(guān)閉連接
    function closeWebSocket() {
        websocket.close();
    }

    //發(fā)送消息
    function send() {
        var message = $("#text").val() ;
        websocket.send(message);
        $("#text").val("") ;
    }

    function sendMsg(){
        var msg = ue.getContent();
        websocket.send(msg);
        ue.setContent('');
    }
</script>

<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="http://cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="http://cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script type="text/javascript" src="<%=path%>/js/Globals.js"></script>
<script type="text/javascript" src="<%=path%>/js/websocket.js"></script>
</html>

其實(shí)其中重要的就是那幾個JS方法啥箭,都寫有注釋谍珊。需要注意的是這里

    //判斷當(dāng)前瀏覽器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://192.168.0.102:8080/ssm/websocket");
    }
    else {
        alert("對不起!你的瀏覽器不支持webSocket")
    }

當(dāng)項目跑起來之后需要將這里的地址改為你項目的地址即可急侥。
哦對了砌滞,我在這里采用了百度的一個Ueditor的富文本編輯器(雖然百度搜索我現(xiàn)在很少用了,但是這個編輯器確實(shí)還不錯)坏怪,這個編輯器也比較簡單只需要個性化的配置一下個人的需求即可贝润。

Ueditor相關(guān)配置

直接使用我項目運(yùn)行的童鞋就不需要重新下載了,我將資源放在了webapp目錄下的ueditor文件夾下面的铝宵。
值得注意的是我們首先需要將jsp-->lib下的jar包加入到項目中打掘。加好之后會出現(xiàn)一個想下的箭頭表示已經(jīng)引入成功。

目錄
鹏秋,之后修改該目錄下的config.json文件尊蚁,主要修改以下內(nèi)容即可:

    "imageAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 上傳圖片格式顯示 */
    "imageCompressEnable": true, /* 是否壓縮圖片,默認(rèn)是true */
    "imageCompressBorder": 1600, /* 圖片壓縮最長邊限制 */
    "imageInsertAlign": "none", /* 插入的圖片浮動方式 */
    "imageUrlPrefix": "http://192.168.0.102:8080/ssm", /* 圖片訪問路徑前綴 */
    "imagePathFormat": "/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}",

這里主要是要修改imageUrlPrefix為你自己的項目地址就可以了。ueditor一個我認(rèn)為很不錯的就是他支持圖片侣夷、多圖横朋、截圖上傳,而且都不需要手動編寫后端接口百拓,所有上傳的文件琴锭、圖片都會保存到項目發(fā)布出去的jsp-->upload文件夾下一看就明白了。更多關(guān)于ueditor的配置可以查看官網(wǎng)衙传。

其中值得注意一點(diǎn)的是决帖,由于項目采用了Spring MVC并攔截了所有的請求,導(dǎo)致靜態(tài)資源不能訪問蓖捶,如果是需要用到上傳txt文件之類的需求可以參照web.xml中修改古瓤,如下:

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.txt</url-pattern>
</servlet-mapping>

這樣就可以訪問txt文件了,如果還需要上傳PPT之類的就以此類推。

總結(jié)

這樣一個簡單的基于websocket的聊天室就算完成了落君,感興趣的朋友可以將項目部署到外網(wǎng)服務(wù)器上這樣好基友之間就可以愉快的聊(zhuang)天(bi)了。
當(dāng)然這只是一個簡單的項目亭引,感興趣的朋友再這基礎(chǔ)之上加入實(shí)時在線人數(shù)绎速,用戶名和IP之類的。

項目地址:https://github.com/crossoverJie/SSM.git
個人博客地址:http://crossoverjie.top焙蚓。
GitHub地址:https://github.com/crossoverJie纹冤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市购公,隨后出現(xiàn)的幾起案子萌京,更是在濱河造成了極大的恐慌,老刑警劉巖宏浩,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件知残,死亡現(xiàn)場離奇詭異,居然都是意外死亡比庄,警方通過查閱死者的電腦和手機(jī)求妹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佳窑,“玉大人制恍,你說我怎么就攤上這事∩翊眨” “怎么了净神?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長溉委。 經(jīng)常有香客問我鹃唯,道長,這世上最難降的妖魔是什么薛躬? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任俯渤,我火速辦了婚禮,結(jié)果婚禮上型宝,老公的妹妹穿的比我還像新娘八匠。我一直安慰自己,他們只是感情好趴酣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布梨树。 她就那樣靜靜地躺著,像睡著了一般岖寞。 火紅的嫁衣襯著肌膚如雪抡四。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機(jī)與錄音指巡,去河邊找鬼淑履。 笑死,一個胖子當(dāng)著我的面吹牛藻雪,可吹牛的內(nèi)容都是我干的秘噪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼勉耀,長吁一口氣:“原來是場噩夢啊……” “哼指煎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起便斥,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤至壤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枢纠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體像街,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年京郑,在試婚紗的時候發(fā)現(xiàn)自己被綠了宅广。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡些举,死狀恐怖跟狱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情户魏,我是刑警寧澤驶臊,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站叼丑,受9級特大地震影響关翎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸠信,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一纵寝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧星立,春花似錦爽茴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至劲装,卻和暖如春胧沫,著一層夾襖步出監(jiān)牢的瞬間昌简,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工绒怨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纯赎,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓南蹂,卻偏偏與公主長得像址否,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碎紊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,095評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)樊诺,斷路器仗考,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 你的發(fā)梢在跳,我的眉眼在鬧词爬,這樣子的我和他充盈著青春的味道秃嗜。有時候是他白襯衫上不著痕跡的洗衣粉的味道,有時候是...
    贈你十里春風(fēng)閱讀 339評論 0 1
  • 悠長顿膨,小道倩影獨(dú)行照斜陽凄凄然心有萬語千言能與何人說來兮锅锨,歸兮誰堪撫清淚?俏郎君何處尋恋沃?細(xì)雨敲窗必搞,一盞孤燈伊人獨(dú)坐...
    上川夏春閱讀 582評論 0 1
  • 宇樓飛月,倩清秋賞葉囊咏,血楓聽闕恕洲。 一哂愁情無地也,錐刺風(fēng)云流瀉梅割。 玉柱颙昂霜第,人歸迥野,望遍重城榭户辞。 更無一處泌类,利名...
    大美不仁閱讀 815評論 3 4