Ajax輪詢,Ajax長輪詢和websocket(詳細使用)
阿新 • • 發佈:2019-02-09
1.三者介紹
【1】http協議介紹 1)介紹:http協議是請求/響應正規化的,每個http 響應都對應一個 http 請求,http協議是無狀態的,多個http請求之間是沒有關係的; 2)http協議的被動性:在標準的HTTP請求響應語義中,瀏覽器發起請求,伺服器傳送一個響應,這意味著在瀏覽器發起新請求前,伺服器不能傳送新資訊給客戶端瀏覽器; 【2】http 長輪詢 和 短輪詢 【2.1】http 長輪詢 1)介紹:http 長輪詢是server 收到請求後如果有資料,立刻響應請求;如果沒有資料 就會 停留 一段時間,這段時間內,如果 server 請求的資料到達(如查詢資料庫或資料的邏輯處理完成),就會立刻響應;如果這段時間過後,還沒有資料到達,則以空資料的形式響應http請求;若瀏覽器收到的資料為空,會再次傳送同樣的http請求到server;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
自定義config類<!--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>
自定義handpackage 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()); } }
自定義socketpackage 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); } }
頁面程式碼: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.後續我整合到實際專案中使用後,會完善此貼!