前言
不知大家在平時的需求中有沒有遇到需要實(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í)時的聊天室。如下圖:
采用
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纹冤。