由於最近學習微信小程序做聊天時,有幸涉及到WebSocket協議;在這裏我就把我認爲最好理解的方法給大家呈現下前輩的文章;
聲明參考引用的文章有:
http://blog.csdn.net/wwd0501/article/details/54582912
https://www.cnblogs.com/xdp-gacl/p/5193279.html
https://www.cnblogs.com/winkey4986/p/5478332.html
首先需要了解啥什麼是WebSocket:
定義:是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信(full-duplex)。一開始的握手需要藉助HTTP請求完成。
原理:WebSocket同HTTP一樣也是應用層的協議,但是它是一種雙向通信協議,是建立在TCP之上的。
目的:即時通訊,替代輪詢
優點:輪詢技術會導致過多不必要的請求,浪費流量和服務器資源,每一次請求、應答,都浪費了一定流量在相同的頭部信息上,然而WebSocket的出現可以彌補這一缺點。在WebSocket中,只需要服務器和瀏覽器通過HTTP協議進行一個握手的動作,然後單獨建立一條TCP的通信通道進行數據的傳送。
注意:WebSocket和Socket沒有半毛錢關係;是兩個不完全不一樣的東西;Socket是位於應用層和傳輸控制層之間的一組接口;(建立網絡通信連接至少要一對端口號(socket)。socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口);然而,WebSocket是應用層的協議。
隨着H5以及微信公衆號,小程序的快速發展;用戶的消息等待時間急劇減短;響應速度也隨之提高,這時及時消息也成爲了程序的必須,之前的輪詢機制太過浪費,所以WebSocket必定被重視;實現web端消息即時通訊的代碼:
前端代碼:
<%@ page language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Java後端WebSocket的Tomcat實現</title>
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button onclick="send()">發送消息</button>
<hr/>
<button onclick="openWebSocket()">開啓WebSocket連接</button>
<button onclick="closeWebSocket()">關閉WebSocket連接</button>
<hr/>
<div id="message"></div>
</body>
<script type="text/javascript">
(function(window){
openWebSocket_m()
})(this);
var websocket = null;
function openWebSocket_m(){
//判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/WX_auth/websocket/李");
}else {
alert('當前瀏覽器 Not support websocket')
}
//連接發生錯誤的回調方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發生錯誤");
};
//連接成功建立的回調方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
}
//接收到消息的回調方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//連接關閉的回調方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關閉");
}
//監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉窗口,server端會拋異常。
window.onbeforeunload = function () {
closeWebSocket();
}
//將消息顯示在網頁上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
}
//開啓WebSocket連接
function openWebSocket() {
openWebSocket_m();
}
//關閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發送消息
function send() {
var message = ‘{To:All,message:’+document.getElementById('text').value+'}';
websocket.send(message);
}
</script>
</html>
後端代碼:
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSONObject;
/**
* @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端,
* 註解的值將被用於監聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket服務器端
*/
@ServerEndpoint("/websocket/{username}")
public class WebSocket {
//靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
private static int onlineCount = 0;
//concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通信的話,可以使用Map來存放,其中Key可以爲用戶標識( 這裏就使用的Map實現一對一和一對多)
//private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
private static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();
//與某個客戶端的連接會話,需要通過它來給客戶端發送數據
private Session session;
//單個連接的名稱,及其session的關鍵字
private String username;
/**
* 連接建立成功調用的方法
* @param session 可選的參數。session爲與某個客戶端的連接會話,需要通過它來給客戶端發送數據
*/
@OnOpen
public void onOpen(@PathParam("username") String username, Session session) throws IOException {
this.username = username;
this.session = session;
addOnlineCount(); //在線數加1
clients.put(username, this); //將webSocket的連接存在map中,通過用戶名來指定對應的session
System.out.println(username+"已連接"+session.getId()+"在線人數"+ getOnlineCount());
}
/**
* 連接關閉調用的方法
*/
@OnClose
public void onClose() throws IOException {
clients.remove(username);
subOnlineCount(); //在線數減1
System.out.println("有一連接關閉!當前在線人數爲" + getOnlineCount());
}
/**
* 收到客戶端消息後調用的方法
* @param message 客戶端發送過來的消息
* @param session 可選的參數
*/
@OnMessage
public void onMessage(String message) throws IOException { //message是服務器收到的消息json串;裏面包括髮給說,發的什麼
System.out.println("來自客戶端的消息:" + message);
JSONObject jsonTo = JSONObject.parseObject(message);
if (!jsonTo.get("To").equals("All")){ //一對一發
sendMessageTo("給一個人說:"+jsonTo.get("message ").toString(),
jsonTo.get("To").toString());
}else{ //羣發
sendMessageAll("給所有人說:"+jsonTo.get("message ").toString());
}
}
/**
* 發生錯誤時調用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
/**
* 發給某一個人。
* @param message
* @throws IOException
*/
public void sendMessageTo(String message, String To) throws IOException {
// session.getBasicRemote().sendText(message);
//session.getAsyncRemote().sendText(message);
for (WebSocket item : clients.values()) {
if (item.username.equals(To) )
item.session.getAsyncRemote().sendText(To+‘---’+message);
}
}
//羣發
public void sendMessageAll(String message) throws IOException {
for (WebSocket item : clients.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
//保證onlineCount的線程安全,使用同步鎖
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocket.onlineCount--;
}
public static synchronized Map<String, WebSocket> getClients() {
return clients;
}
}
最後給大家說下如何後臺主動推送消息給前端(消息推送,而非即時通訊)
第一步:
將之前的WebSocket端點設置成靜態公共
public static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();
第二步:
在服務端的某個方法中,調用該Set集合
//羣發消息
for(MyWebSocket item: MyWebSocket.webSocketSet){
try {
if(message!=null&&!"".equals(message)){
item.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
或者:直接是其類中方法onMessage
@RequestMapping(value = "/login1", method = RequestMethod.GET)
public void login1(ModelMap modelMap,HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String message="你好-----";
//羣發消息
WebSocketTest.onMessage( message, WebSocketTest.session);
}
結果: