1. 程式人生 > >Ajax輪詢,Ajax長輪詢和websocket(詳細使用)

Ajax輪詢,Ajax長輪詢和websocket(詳細使用)

1.三者介紹

【1】http協議介紹 1)介紹:http協議是請求/響應正規化的,每個http 響應都對應一個 http 請求,http協議是無狀態的,多個http請求之間是沒有關係的; 2)http協議的被動性:在標準的HTTP請求響應語義中,瀏覽器發起請求,伺服器傳送一個響應,這意味著在瀏覽器發起新請求前,伺服器不能傳送新資訊給客戶端瀏覽器; 【2】http 長輪詢 和 短輪詢 【2.1】http 長輪詢 1)介紹:http 長輪詢是server 收到請求後如果有資料,立刻響應請求;如果沒有資料 就會 停留 一段時間,這段時間內,如果 server 請求的資料到達(如查詢資料庫或資料的邏輯處理完成),就會立刻響應;如果這段時間過後,還沒有資料到達,則以空資料的形式響應http請求;若瀏覽器收到的資料為空,會再次傳送同樣的http請求到server;
2)http 長輪詢 的缺點:server 沒有資料到達時,http連線會停留一段時間,這會造成伺服器資源浪費; 3)看個荔枝:假設有 1000個人停留在某個客戶端頁面,等待server端的資料更新,那就很有可能伺服器這邊掛著1000個執行緒,在不停檢測資料是否發生變化,這依然是有問題的; 【2.2】http 短輪詢 1)介紹:http 短輪詢是 server 收到請求 不管是否有資料到達都直接響應http 請求;如果瀏覽器收到的資料為空,則隔一段時間,瀏覽器又會發送相同的http請求到server 以獲取資料響應; 2) http 短輪詢的缺點:訊息互動的實時性較低(server端到瀏覽器端的資料反饋效率低);
【2.3】http 長輪詢 和 短輪詢的異同 1)相同點:當server 的資料不可達時,基於http長輪詢和短輪詢 的http請求,都會 停留一段時間; 2)不同點:http長輪詢是在伺服器端的停留,而http 短輪詢是在 瀏覽器端的停留; 3)效能總結:從這裡可以看出,不管是長輪詢還是短輪詢,都不太適用於客戶端數量太多的情況,因為每個伺服器所能承載的TCP連線數是有上限的,這種輪詢很容易把連線數頂滿; 【3】WebSocket 1)介紹:WebSocket 是 html5 規範釋出的新協議,和 http協議完全是兩個不同的概念,或者說基本沒關係;WebSocket 協議 和 http協議的唯一聯絡點在於,WebSocket 協議為了相容現有瀏覽器的握手規範而採用了 http協議中的握手規範 以建立WebSocket連線;
2)WebSocket協議:其客戶端與伺服器建立的是 持久連線; 3)WebSocket 解決了 HTTP 的幾個難題
3.1)難題1(http協議的被動性):採用 WebSocket 協議後,伺服器可以主動推送訊息給客戶端;而不需要客戶端以(長/短)輪詢的方式發起http請求到server以獲取資料更新反饋;這樣一來,客戶端只需要經過一次HTTP請求,就可以做到源源不斷的資訊傳送了(在程式設計中,這種設計叫做回撥,即:server 端有資訊了再來通知client 端,而不是 client 端 每次都傻乎乎地跑去輪詢server端 是否有訊息更新); 3.2)難題2(http協議的無狀態性/健忘性):短輪詢是每次http請求前都要建立連線,而長輪詢是相鄰幾次請求前都要建立連線;http請求響應完成後,伺服器就會斷開連線,且把連線的資訊全都忘記了;所以每次建立連線都要重新傳輸連線上下文(下面有補充),將 client 端的連線上下文來告訴server 端;而 WebSockct只需要一次HTTP 握手,整個通訊過程是建立在一次連線(狀態)中的,server 端會一直推送訊息更新反饋到客戶端,直到客戶端關閉請求,這樣就無需 客戶端為傳送訊息而建立不必要的 tcp 連線 和 為了建立tcp連線而傳送不必要的冗餘的連線上下文訊息;
4)連線上下文補充:連線上下文,如限定客戶端和伺服器平臺的所有頭資訊,認證屬性,負載描述等;看個荔枝:

2.三者之間比較

傳統(短)輪詢 長輪詢 WebSocket
瀏覽器支援 幾乎所有現代瀏覽器 幾乎所有現代瀏覽器 IE 10+ Edge Firefox 4+ Chrome 4+ Safari 5+ Opera 11.5+
伺服器負載 較少的CPU資源,較多的記憶體資源和頻寬資源 與傳統輪詢相似,但是佔用頻寬較少 無需迴圈等待(長輪詢),CPU和記憶體資源不以客戶端數量衡量,而是以客戶端事件數衡量。三種方式裡效能最佳。
客戶端負載 佔用較多的記憶體資源與請求數。 與傳統輪詢相似。 同Server-Sent Event。
延遲 非實時,延遲取決於請求間隔。 同傳統輪詢。 實時。
實現複雜度 非常簡單。 需要伺服器配合,客戶端實現非常簡單。 需要Socket程式實現和額外埠,客戶端實現簡單。

3.三者總結:

個人覺得大概的可以理解為:

1.輪詢就是定時傳送請求,響應請求

2.長輪詢,定時就是傳送請求,響應請求,客戶端接收到響應後,繼續傳送請求,從而達到不間斷.

3.socket就是發出請求,標識這個請求為長連線,服務端知道後,以後就不需要客戶端傳送請求,服務端就可以向客戶端推送資料.

4.在SSM框架中使用springSocket(後續擴充套件實際專案如何使用)

首先要知道流程是如何走的,客戶端像服務端發出請求,並標識這個請求是長連線,服務端接收到後,處理業務,並將資料傳遞給客戶端(當然也可以不傳遞),這樣每次服務端都可以像客戶端推送資料.,大致的流程就是這樣
pom.xml
        <!--socket使用的jar-->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
自定義config類
package com.bile.socket;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
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 javax.annotation.Resource;

/**
 *Title:      MyWebSocketConfig<br/>
 *Description: 介面地址例項層
 *  服務一啟動就呼叫
 */
@Component
@EnableWebMvc
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{

    @Resource
    MyWebSocketHandler handler;
    @Resource
    UserWebSocketHandler handler2;
    @Resource
    NewStatisticsWebSocketHandler handler3;
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        System.out.println("初始化進來-----");
        // TODO Auto-generated method stub
        registry.addHandler(handler, "/wsMy").addInterceptors(new HandshakeInterceptor());
        registry.addHandler(handler, "/wsMy/sockjs").addInterceptors(new HandshakeInterceptor()).withSockJS();
        registry.addHandler(handler2, "/wsUser").addInterceptors(new HandshakeInterceptor());
        registry.addHandler(handler2, "/wsUser/sockjs").addInterceptors(new HandshakeInterceptor()).withSockJS();
        
        registry.addHandler(handler3, "/wsNewStatistics").addInterceptors(new HandshakeInterceptor());
        registry.addHandler(handler3, "/wsNewStatistics/sockjs").addInterceptors(new HandshakeInterceptor());
    }

}
自定義hand
package com.bile.socket;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import java.util.Map;


/**
 *Title:      HandshakeInterceptor<br/>
 *Description:  會話標記層
 *  web斷先進入當前方法--->再進入MyWebSocketHandler去快取當前session的客戶端
 */
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        System.out.println("第2步進來:HandshakeInterceptor->beforeHandshake");
        // TODO Auto-generated method stub
        String uid = ((ServletServerHttpRequest) request).getServletRequest().getParameter("uid");
        // 標記使用者
        if(uid!=null){
            attributes.put("uid", uid);
        }else{
            return false;
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);
        
    }

    @Override
    public void afterHandshake(ServerHttpRequest request,  ServerHttpResponse response, WebSocketHandler wsHandler,  Exception ex) {
        System.out.println("第3步進來:HandshakeInterceptor->afterHandshake");
        super.afterHandshake(request, response, wsHandler, ex);
    }

}
自定義socket
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;


/**
 *Title:      MyWebSocketHandler<br/>
 *Description: 會話連線層
 */
@Component
public class MyWebSocketHandler implements WebSocketHandler{
	

    public static final Map<String, WebSocketSession> userSocketSessionMap;

    static {
        userSocketSessionMap = new HashMap<String, WebSocketSession>();
    }

    /**
     * 連線成功時候,會觸發頁面上onopen方法
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // TODO Auto-generated method stub
    	//String jspCode = (String) session.getAttributes().get("SID");
        if (userSocketSessionMap.get(session.getId()) == null) {
        	userSocketSessionMap.put(session.getId(), session);
        }
        System.out.println("第4步進來::Socket會話連線成功::Key="+session.getId());
        
    }

    //暫時沒用
    /**
     * js呼叫websocket.send時候,會呼叫該方法
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {

    }
    /**
     * 訊息傳輸錯誤處理
     */
    //暫時沒用
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("第1步:開始移除使用者" );
        if (session.isOpen()) { session.close(); }
        Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
        // 移除Socket會話
        while (it.hasNext()) {
            Entry<String, WebSocketSession> entry = it.next();
            //if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                System.out.println("--------->>>>使用者Key::" + entry.getKey());
                break;
            //}
        }
    }

    /**
     * 關閉連線時觸發
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
    	System.out.println("Websocket:" + session.getId() + "已經關閉");
        Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
        // 移除Socket會話
        System.out.println("=======關閉連線=====");
        while (it.hasNext()) {
            Entry<String, WebSocketSession> entry = it.next();
            //if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                System.out.println("Socket會話已經移除:使用者ID" + entry.getKey());
                break;
            //}
        }
    	
    }

    @Override
    public boolean supportsPartialMessages() {
        // TODO Auto-generated method stub
        return false;
    }
    /**
     * 群發
     * @Title:       broadcast
     * @Description: TODO
     * @param:       @param message
     * @param:       @throws IOException
     * @return:      void
     * @throws
     */
    public void broadcast(final TextMessage message) throws IOException {
        Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
        VoThread thread=null;
        // 多執行緒群發
        while (it.hasNext()) {

            final Entry<String, WebSocketSession> entry = it.next();

            if (entry.getValue().isOpen()) {
            	thread=new VoThread(entry.getValue(),message);
            	new Thread(thread).start();
            	//注意這裡不能使用匿名內部類,不然會出現問題
/*                new Thread(new Runnable() {

                    public void run() {
                        try {
                            if (entry.getValue().isOpen()) {
                                entry.getValue().sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }).start();*/
            }

        }
    }

    /**
     * 給某個使用者傳送訊息
     * 
     * @param userName
     * @param message
     * @throws IOException
     */
    public void sendMessageToUser(String uid, TextMessage message)
            throws IOException {
        WebSocketSession session = userSocketSessionMap.get(uid);
        System.out.println("======給"+uid+"使用者傳送訊息======");
        if (session != null && session.isOpen()) {
            session.sendMessage(message);
        }
    }

}
頁面程式碼:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>socket</title>
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
<script type="text/javascript">
    var sock=null;
    if (window['WebSocket']) {
            sock= new WebSocket('ws://' + window.location.host+'/bile.api/wsMy?uid=1');
    }
    sock.onopen = function() { /* 連線成功時 */
    //頁面載入完畢,出發onopen方法,這時候可以選擇傳送引數,也可以不傳送
    sock.send(JSON.stringify({to:'1my'}));
    };
    sock.onmessage = function(e) {/* 服務端推送資料過來 */
    //在後臺socket中像客戶端傳送資料時,自動呼叫這方法拿到資料
    $('#data').text(e.data);
    };
    sock.onclose = function() {
       alert('Closing');
    };
</script>
</head>
<body>
<h1 id="data"></h1>
</body>
</html>

5.後語

以上只是我自己寫的demo,僅供參考,希望大家和我一起學習,有想法提出來,一起討論,我也是最近才學socket.後續我整合到實際專案中使用後,會完善此貼!