webSocket

掃碼_搜索聯(lián)合傳播樣式-標(biāo)準(zhǔn)色版-壓縮版.jpg

webSocket

1.為什么會(huì)有webSocket的出現(xiàn)?

默認(rèn)HTTP協(xié)議只支持請(qǐng)求響應(yīng)模式,也就是常說(shuō)的請(qǐng)求-響應(yīng)模式,這樣做可以簡(jiǎn)化Web服務(wù)器,減少服務(wù)器的負(fù)擔(dān),加快響應(yīng)速度。這種機(jī)制對(duì)于信息變化不是特別頻繁的應(yīng)用尚能相安無(wú)事,但是對(duì)于那些實(shí)時(shí)要求比較高的應(yīng)用來(lái)說(shuō),比如說(shuō)在線游戲、在線證券、設(shè)備監(jiān)控、新聞在線播報(bào)、RSS 訂閱推送等等,當(dāng)客戶端瀏覽器準(zhǔn)備呈現(xiàn)這些信息的時(shí)候,這些信息在服務(wù)器端可能已經(jīng)過(guò)時(shí)了。所以我們需要一種服務(wù)端可以主動(dòng)向客戶端推送消息的技術(shù),來(lái)保證消息的實(shí)時(shí)性。但是在webSocket出現(xiàn)之前,實(shí)時(shí)獲取消息也是有幾種解決方案的。

輪詢:

輪詢是比較常用并且簡(jiǎn)單的實(shí)現(xiàn)實(shí)時(shí)消息的方式,簡(jiǎn)單來(lái)說(shuō)就是客戶端不斷向服務(wù)端請(qǐng)求數(shù)據(jù),這個(gè)頻率可能是一分鐘甚至一秒一次,以此保持盡可能實(shí)時(shí)的獲取最新數(shù)據(jù)。

以Ajax+輪詢的方式,實(shí)現(xiàn)消息的獲取

setInterval('send()', 1000);//輪詢執(zhí)行,1s一次

 function send() {
 $.ajax({
 url : '../case/quatrzLoad.ajax',
 type : 'post',
 dataType : 'JSON',
 async : false,
 cache : false,
 data : {},
 success : function(result) {
 if (result.success) {
 //右下角消息提醒
 bottomRight();
 //聲音提醒
 playSound("../audio/remind.mp3");
 }
?
 }
 });
 }

輪詢的方式適用于用戶量比較少的應(yīng)用,而且實(shí)現(xiàn)簡(jiǎn)單。但是頻繁的請(qǐng)求會(huì)給服務(wù)器帶來(lái)很大的壓力。

長(zhǎng)連接:

在頁(yè)面里嵌入一個(gè)隱蔵iframe,將這個(gè)隱蔵iframe的src屬性設(shè)為對(duì)一個(gè)長(zhǎng)連接的請(qǐng)求或是采用xhr請(qǐng)求,服務(wù)器端就能源源不斷地往客戶端輸入數(shù)據(jù)。 優(yōu)點(diǎn):消息即時(shí)到達(dá),不發(fā)無(wú)用請(qǐng)求;管理起來(lái)也相對(duì)便。 缺點(diǎn):服務(wù)器維護(hù)一個(gè)長(zhǎng)連接會(huì)增加開(kāi)銷。

長(zhǎng)輪詢:

客戶端向服務(wù)器發(fā)送Ajax請(qǐng)求,服務(wù)器接到請(qǐng)求后hold住連接,直到有新消息才返回響應(yīng)信息并關(guān)閉連接,客戶端處理完響應(yīng)信息后再向服務(wù)器發(fā)送新的請(qǐng)求。 優(yōu)點(diǎn):在無(wú)消息的情況下不會(huì)頻繁的請(qǐng)求,耗費(fèi)資小。 缺點(diǎn):服務(wù)器hold連接會(huì)消耗資源,返回?cái)?shù)據(jù)順序無(wú)保證,難于管理維護(hù)。

2.webSocket是什么?

WebSocket同樣是HTML 5規(guī)范的組成部分之一。WebSocket 相較于上述幾種連接方式,實(shí)現(xiàn)原理較為復(fù)雜,用一句話概括就是:客戶端向 WebSocket 服務(wù)器通知(notify)一個(gè)帶有所有 接收者ID(recipients IDs)的事件(event),服務(wù)器接收后立即通知所有活躍的(active)客戶端,只有ID在接收者ID序列中的客戶端才會(huì)處理這個(gè)事件。由于 WebSocket 本身是基于TCP協(xié)議的,所以在服務(wù)器端我們可以采用構(gòu)建 TCP Socket 服務(wù)器的方式來(lái)構(gòu)建 WebSocket 服務(wù)器。

這個(gè) WebSocket 是一種全新的協(xié)議。它將 TCP 的 Socket(套接字)應(yīng)用在了web page上,從而使通信雙方建立起一個(gè)保持在活動(dòng)狀態(tài)連接通道,并且屬于全雙工(雙方同時(shí)進(jìn)行雙向通信)。

其實(shí)是這樣的,WebSocket 協(xié)議是借用 HTTP協(xié)議 的 101 switch protocol 來(lái)達(dá)到協(xié)議轉(zhuǎn)換的,從HTTP協(xié)議切換成WebSocket通信協(xié)議。

它的最大特點(diǎn)就是,服務(wù)器可以主動(dòng)向客戶端推送信息,客戶端也可以主動(dòng)向服務(wù)器發(fā)送信息,是真正的雙向平等對(duì)話,屬于服務(wù)器推送技術(shù)的一種。其他特點(diǎn)包括:

  • 建立在 TCP 協(xié)議之上,服務(wù)器端的實(shí)現(xiàn)比較容易。

  • 與 HTTP 協(xié)議有著良好的兼容性。默認(rèn)端口也是 80 和 443 ,并且握手階段采用 HTTP 協(xié)議,因此握手時(shí)不容易屏蔽,能通過(guò)各種 HTTP 代理服務(wù)器。

  • 數(shù)據(jù)格式比較輕量,性能開(kāi)銷小,通信高效。

  • 可以發(fā)送文本,也可以發(fā)送二進(jìn)制數(shù)據(jù)。

  • 沒(méi)有同源限制,客戶端可以與任意服務(wù)器通信。

  • 協(xié)議標(biāo)識(shí)符是ws(如果加密,則為wss),服務(wù)器網(wǎng)址就是 URL。

3.WebSocket如何創(chuàng)建?

WebSocket并不是全新的協(xié)議,而是利用了HTTP協(xié)議來(lái)建立連接。我們來(lái)看看WebSocket連接是如何創(chuàng)建的。

首先,WebSocket連接必須由瀏覽器發(fā)起,因?yàn)檎?qǐng)求協(xié)議是一個(gè)標(biāo)準(zhǔn)的HTTP請(qǐng)求,格式如下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

該請(qǐng)求和普通的HTTP請(qǐng)求有幾點(diǎn)不同:

  1. GET請(qǐng)求的地址不是類似/path/,而是以ws://開(kāi)頭的地址;

  2. 請(qǐng)求頭Upgrade: websocketConnection: Upgrade表示這個(gè)連接將要被轉(zhuǎn)換為WebSocket連接;

  3. Sec-WebSocket-Key是用于標(biāo)識(shí)這個(gè)連接,并非用于加密數(shù)據(jù);

  4. Sec-WebSocket-Version指定了WebSocket的協(xié)議版本。

隨后,服務(wù)器如果接受該請(qǐng)求,就會(huì)返回如下響應(yīng):

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

該響應(yīng)代碼101表示本次連接的HTTP協(xié)議即將被更改,更改后的協(xié)議就是Upgrade: websocket指定的WebSocket協(xié)議。

版本號(hào)和子協(xié)議規(guī)定了雙方能理解的數(shù)據(jù)格式,以及是否支持壓縮等等。如果僅使用WebSocket的API,就不需要關(guān)心這些。

現(xiàn)在,一個(gè)WebSocket連接就建立成功,瀏覽器和服務(wù)器就可以隨時(shí)主動(dòng)發(fā)送消息給對(duì)方。消息有兩種,一種是文本,一種是二進(jìn)制數(shù)據(jù)。通常,我們可以發(fā)送JSON格式的文本,這樣,在瀏覽器處理起來(lái)就十分容易。

為什么WebSocket連接可以實(shí)現(xiàn)全雙工通信而HTTP連接不行呢?實(shí)際上HTTP協(xié)議是建立在TCP協(xié)議之上的,TCP協(xié)議本身就實(shí)現(xiàn)了全雙工通信,但是HTTP協(xié)議的請(qǐng)求-應(yīng)答機(jī)制限制了全雙工通信。WebSocket連接建立以后,其實(shí)只是簡(jiǎn)單規(guī)定了一下:接下來(lái),咱們通信就不使用HTTP協(xié)議了,直接互相發(fā)數(shù)據(jù)吧。

安全的WebSocket連接機(jī)制和HTTPS類似。首先,瀏覽器用wss://xxx創(chuàng)建WebSocket連接時(shí),會(huì)先通過(guò)HTTPS創(chuàng)建安全的連接,然后,該HTTPS連接升級(jí)為WebSocket連接,底層通信走的仍然是安全的SSL/TLS協(xié)議。

4.webSocket 客戶端基本使用:

webSocket的api很簡(jiǎn)潔,甚至感覺(jué)粗暴。但是確實(shí)使用起來(lái)很方便:

?
$(function() {
 var socket;
 if(typeof(WebSocket) == "undefined") {
 alert("您的瀏覽器不支持WebSocket");
 return;
 }
?
 $("#btnConnection").click(function() {
 //實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口
 socket = new WebSocket("ws://192.16.20.39:8080/ops_console_war/ws/"+$("#cur_userId").val());
 //打開(kāi)事件
 socket.onopen = function() {
 alert("Socket 已打開(kāi)");
 //socket.send("這是來(lái)自客戶端的消息" + location.href + new Date());
 };
 //獲得消息事件
 socket.onmessage = function(msg) {
 console.log(msg);
 if (msg.success) {
 console.log("111");
 }else{
 console.log("222");
 }
 };
 //關(guān)閉事件
 socket.onclose = function() {
 alert("Socket已關(guān)閉");
 };
 //發(fā)生了錯(cuò)誤事件
 socket.onerror = function() {
 alert("發(fā)生了錯(cuò)誤");
 }
 });
?
 $("#btnSend").click(function() {
 socket.send("這是來(lái)自客戶端的消息" + location.href + new Date());
 });
?
 $("#btnClose").click(function() {
 socket.close();
 });
?
?
 $("#btnSendToAll").click(function(){
 $.ajax({
 url : '../user/sendMessage.ajax',
 type : 'post',
 dataType : 'JSON',
 data : {
 },
 success : function(data) {
 console.log("成功")
 },error : function(msg) {
 console.log("失敗")
 }
 });
 });
});

5.webSocket服務(wù)端基本使用:

?
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
?
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
?
/**
 * @ClassName WebSocketServer
 * @Description webSocket服務(wù)
 * @Author
 * @Date 2019-10-22 下午 3:58
 * @Version V1.0
 */
@ServerEndpoint("/ws/{userId}")
public class WebSocketServer {
?
 private static WebSocketServer server = new WebSocketServer();
?
 /***
 * 日志
 */
 Logger logger = LoggerFactory.getLogger(this.getClass());
?
 /***
 * 當(dāng)前在線用戶數(shù)量 包含同一用戶登錄多個(gè)瀏覽器的情況 不等于socketMap的大小
 */
 private static AtomicInteger onlineUserCount = new AtomicInteger(0);
 /***
 * 存儲(chǔ)用戶id和session對(duì)應(yīng)關(guān)系的map
 */
 private static Map<String,Session> socketMap = new ConcurrentHashMap<>(32);
?
?
 /****
 * 當(dāng)socket開(kāi)始連接時(shí)觸發(fā)
 * @param userId
 * @param session
 */
 @OnOpen
 public void onOpen(@PathParam("userId") String userId, Session session) {
?
 if(StringUtils.isEmpty(userId)){
?
 logger.error(" 客戶端開(kāi)始建立連接---但連接中缺失參數(shù)" );
?
 }else{
?
 socketMap.put(userId,session);
?
?
 logger.info(" 用戶id為: "+userId+"  建立socket連接,當(dāng)前在線用戶數(shù)量:  "+ socketMap.size());
?
 }
?
 }
?
?
 /****
 * 服務(wù)端收到客戶端消息時(shí)觸發(fā)
 * @param message
 * @param session
 */
 @OnMessage
 public void onMessage(String message, Session session) {
?
 logger.info(" 客戶端向服務(wù)端發(fā)送消息  : {} ",message);
?
 }
?
 /****
 * socket連接關(guān)閉時(shí)觸發(fā)
 * @param session
 * @param closeReason
 */
 @OnClose
 public void onClose(@PathParam("userId")String userId,Session session, CloseReason closeReason) {
 //通過(guò)用戶id 將用戶session刪除
 socketMap.remove(userId);
?
 logger.info(" id 為 :"+userId+"  的用戶下線,當(dāng)前在線用戶數(shù)量 : "+socketMap.size());
 }
?
 /***
 * 連接錯(cuò)誤時(shí)執(zhí)行
 * @param t
 */
 @OnError
 public void onError(Throwable t) {
?
 t.printStackTrace();
?
 }
?
 /***
 *
 * 通過(guò)用戶id向指定用戶推送消息
 * @param message
 * @param userId
 * @throws IOException
 */
 public void sendMessage(String message,String userId) throws IOException {
?
 socketMap.get(userId).getBasicRemote().sendText(message);
?
 }
?
?
 /****
 * 向所有用戶發(fā)送消息 群發(fā)消息
 * @param message
 * @throws IOException
 */
 public void sendMessageToAllUser(String message) throws IOException{
?
 long start = System.currentTimeMillis();
 logger.info(" 服務(wù)端開(kāi)始群發(fā)消息 , 消息內(nèi)容:  "+message);
?
 for (String userId : socketMap.keySet()) {
?
 socketMap.get(userId).getBasicRemote().sendText(message);
?
 }
 long end = System.currentTimeMillis();
 logger.info(" 服務(wù)端群發(fā)消息結(jié)束 , 共耗時(shí) :  "+(end-start)+" ms");
?
 }
?
?
 public static Map<String,Session> getSocketMap(){
 return socketMap;
 }
?
}

本人最近因?yàn)轫?xiàng)目原因,正在將以前的輪詢獲取消息的代碼進(jìn)行重構(gòu)。并且因?yàn)橛脩魯?shù)量并不是太多,所以沒(méi)有選用其他三方webSocket插件,因此接觸到webSocket。過(guò)幾天,會(huì)將完整的消息推送項(xiàng)目分享給大家。

掃碼_搜索聯(lián)合傳播樣式-標(biāo)準(zhǔn)色版-壓縮版.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢(mèng)敢當(dāng)閱讀 9,032評(píng)論 0 50
  • Socket并非是一個(gè)協(xié)議,而是為了方便使用TCP而抽象出來(lái)的一層,是位于應(yīng)用層和傳輸控制層之間的一組接口。換句話...
    JunChow520閱讀 3,525評(píng)論 0 4
  • 什么是WebSocket呢? WebSocket是HTML5新增的一種通信協(xié)議,目標(biāo)主流的瀏覽器都支持這個(gè)協(xié)議,比...
    JunChow520閱讀 7,538評(píng)論 1 5
  • WebSocket簡(jiǎn)介 談到Web實(shí)時(shí)推送,就不得不說(shuō)WebSocket。在WebSocket出現(xiàn)之前,很多網(wǎng)站為...
    吧啦啦小湯圓閱讀 8,332評(píng)論 15 75
  • WebSocket 機(jī)制 WebSocket 是 HTML5 一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,能更...
    勇敢的_心_閱讀 2,382評(píng)論 0 4

友情鏈接更多精彩內(nèi)容