談websocket之前,我們可以先複習一下web通信的一些概念。
推(PUSH)和拉(PULL):
推和拉是Web通信的兩種方式,從定義上來說它們最主要的差別在于方向性,推是伺服器主動向用戶端推送消息,拉則是用戶端主動發請求向伺服器擷取資料。
優缺點及适用場景:
1、拉模式是最常用、最成熟的一種方式,它依托了非常成熟的HTTP協定,有大量的工具類和庫可以使用,請求/響應模式幾乎每個程式員都知道,出現問題也容易找到相應的解決方案。
2、拉模式的限制特别少,任何一款浏覽器,隻要能稱為浏覽器的,不管多古老都支援拉,推的話像websocket是H5才支援,IE9都不支援,不僅是用戶端,服務端也是有限制的,要較新的伺服器才支援websocket,如Tomcat 7.0.5,WebLogic 12c ,IIS 7.0...以上的版本才“真正”支援websocket。
3、對于一般請求,拉模式占用的資源也少,比如請求個清單或者明細,伺服器處理完并傳回資料後,連接配接就斷開了,簡潔明了不占用連接配接資源。是以拉适合大多數一般的請求場景,但對于背景資料變化需要實時更新的,比如股票市場、網頁遊戲和聊天實等程式,拉就不适合了。
長輪詢和長連接配接
在websocket出現之前,要實作伺服器推,有兩種方式,分别是長輪詢和長連接配接。
長輪詢:
輪詢(Polling):用戶端(浏覽器)設一個定時器或死循環不停地向服務端發送查詢請求,輪詢的結果可能是服務端有更新就傳回新的資料,也可能時服務端根本就沒更新,得到一個空的資訊。但是不管結果如何,用戶端在下一個時間節點都會進行新的一輪請求。
短輪詢: 服務端對每次請求立即傳回結果。
長輪詢: 長輪詢是對短輪詢的改善,服務端接收到請求後先檢視資料,如果有更新立即傳回,如果沒有則把請求挂起一段時間,在這段時間内有更新就傳回,如果一直沒更新直到逾時,伺服器會主動向用戶端發送本次輪詢無新資訊的正常響應,并斷開連接配接,這種響應也被稱之為“心跳”。再有就是如果網絡故障導緻輪詢的意外中斷,此時浏覽器将收到錯誤資訊。
由上可知,對于用戶端來說,不管是長輪詢還是短輪詢,用戶端的動作都是一樣的,就是死循環不停的去請求。即一種輪詢方式是否為長輪詢,是根據服務端的處理方式來決定的,與用戶端沒有關系。
長連接配接:
HTTP長連接配接:HTTP分為長連接配接和短連接配接,它們外在表現形式的差別就是請求/響應頭中是否有Connection:keep-alive。内在的差別就是TCP連接配接是否會被複用,即多個HTTP請求是否可以複用同一個TCP連接配接,可以即長連接配接,HTTP長連接配接可以節省了很多TCP連接配接建立和斷開的消耗。比如請求一個網頁,現在的網頁大多不僅僅隻有HTML,一般還包含了CSS、JS、圖檔等一系列資源,如果用短連接配接就需要建立幾個甚至幾十個TCP連接配接,長連接配接複用一個就好了,HTTP1.1以後預設都是長連接配接了。
實作伺服器推的長連接配接:基于iframe流的方式,其實作原理主要是在頁面隐藏一個iframe(display:none),前台設定iframe的src屬性為對一個長連接配接的請求,伺服器傳回對頁面函數的調用。使用iframe流方式的話浏覽器會一直轉圈(進度條一直沒完)。這種iframe流的長連接配接實作伺服器推的模式比較少見,與ajax長輪詢稍有差別。一般ajax輪詢使用XMLHttpRequest 對象發送請求,在 XMLHttpRequest 的 readystate 為 4(即資料傳輸結束)時使用回調函數對傳回的資料進行處理。而iframe服務端傳回的是JS腳本,用戶端得到該腳本後就自動執行。簡單的說,ajax傳回的是資料而後再使用回調函數處理該資料,iframe傳回的是JS腳本(腳本内可能包含資料),浏覽器自動執行。
如:
iframe流實作方式:
服務端代碼:
[java] view plain copy
- while(true){
- while(!condition){<span style="font-family:Arial, Helvetica, sans-serif;">//模拟阻塞,等待條件成熟,推送資訊</span>
- <span style="white-space:pre;"> </span>Thread.sleep(1000);<span style="font-family:Arial, Helvetica, sans-serif;"> </span>
- }
- num++;
- PrintWriter pw = resp.getWriter();
- pw.println("<script>parent.changeNumber("+num+")</script>");
- pw.flush();
- }
可以看到,服務端使用的是PrintWriter,等于說是将 [java] view plain copy
- <script>parent.changeNumber("+num+")</script>
寫在浏覽器裡面
用戶端代碼:
[html] view plain copy
- <iframe id="polling" src="IframeCometServlet" style="display:none;"></iframe>
- function changeNumber(num) { //服務端傳回的字元串調用的方法
- document.getElementById("number").innerHTML = num;
- }
注意changeNumber(num)這個函數,與服務端傳回的一緻(服務端傳回的為什麼要帶一個parent.字首?因為是iframe接收啊,而changeNumber是定義在外面的)。
ajax長輪詢方式:
服務端代碼:
[java] view plain copy
- @RequestMapping(value = "/list")
- public String list(HttpServletRequest request) {
- while(condition){//等待條件成熟
- //do something
- break;
- }
- return "something";
- }
用戶端代碼(該代碼要在循環裡面才能達到輪詢效果):
[html] view plain copy
- $.ajax({
- type: "POST",
- url: "/web/goods/list",
- data: JSON.stringify({data:'hello'}),
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- success: function (result) {
- //do something
- }
- });

通過長輪詢和長連接配接可以看到,它們都并不是真正的伺服器推,隻是變相的拉而已,這是對HTTP體系的妥協。
由此引出真正的伺服器推技術:WebSocket
WebSocket
WebSocket是什麼?
WebSocket和HTTP一樣,是基于TCP的應用層通信協定,通過維持一條持久的連接配接以實作浏覽器與伺服器全雙工通信,是随着H5一起出來的。
WebSocket和HTTP有什麼關系麼?
Websocket其實是一個新協定,除了最開始借用了HTTP來完成握手,後面跟HTTP基本沒有關系了。
傳統的HTTP要不斷的建立、關閉連接配接(這裡的關閉并不是說關閉TCP連接配接,而是關閉HTTP連接配接),而且由于HTTP是無狀态性的,每次都要重新傳輸identity info(鑒别資訊),來告訴服務端你是誰,當然,一般隻是帶個JSESSIONID之類的,服務端通過這個就知道你的其他資訊了。WebSocket 是類似 Socket 的 TCP 長連接配接的通訊模式,一旦 WebSocket 連接配接建立後,後續資料都以幀序列的形式傳輸。在斷掉 WebSocket 連接配接前,都不需要用戶端和服務端重新發起連接配接請求,即隻需一次HTTP握手,整個通訊過程是建立在一次連接配接/狀态中,也就避免了HTTP的無狀态性。
WebSocket握手
WebSocket的握手過程就是建立WebSocket連接配接的過程,握手是借用了HTTP的。
總體步驟大緻如下:
Step 1: 建立TCP連接配接(這一步是一切的基礎,HTTP也一樣)。
Step 2: 浏覽器借助一個WebSocket 用戶端對象,連接配接類似 ws://yourdomain:port/path或wss://yourdomain:port/path的URL,WebSocket 用戶端對象會自動解析并識别為要建立WebSocket 連接配接,進而發送HTTP Get 請求,并為請求自動添加一些供WebSocket使用的HTTP Headers字段。注意,連接配接頭變成了ws,而不是一般的http,這是協定的意思,ws代表websocket。
Step 3: Server收到HTTP請求後,會把Step 1的TCP連接配接的應用層協定從HTTP轉變為WebSocket。至此,HTTP部分就退出舞台了,WebSocket開始接管一切。
WebSocket握手封包(HTTP)分析:
請求
[html] view plain copy
- GET /chat HTTP/1.1
- Host: 127.0.0.1:8080
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Key: JkTukkPc4Rha+nIkbYhEkQ==
- Sec-WebSocket-Version: 13
- Origin: http://localhost:8080
響應
[html] view plain copy
- HTTP/1.1 101 Switching Protocols
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Accept: RD69G0BS8RPH/GbY6rBsZI75pjk=
- Server:Apache-Coyote/1.1
- Date:Sat, 17 Jun 2017 15:35:41 GMT
以上封包和一般的HTTP有很多不同的地方:
1、請求和響應都有Upgrade: websocket和Connection: Upgrade,這是協定轉換的意思,也就是請求時告訴伺服器,本次連接配接需要的是websocket通信協定,而不是一般的HTTP,響應也帶有該響應頭表示轉換成功。
一般響應成功的響應頭是 HTTP/1.1 200 OK、HTTP/1.1 404 NOT FOUND等,此處是HTTP/1.1 101 Switching Protocols,同樣101也是HTTP的一種響應狀态,表示協定轉換成功。
2、Sec-WebSocket-Key: JkTukkPc4Rha+nIkbYhEkQ==和Sec-WebSocket-Accept: RD69G0BS8RPH/GbY6rBsZI75pjk=
這是websocket握手的檢驗,也即測試伺服器是否提供websocket服務,如果提供,Sec-WebSocket-Key和Sec-WebSocket-Accept必然是有對應關系的,具體的關系如下:
Sec-WebSocket-Key拼接上258EAFA5-E914-47DA-95CA-C5AB0DC85B11(這串magic string是寫在協定裡面的,所有的websocket底層實作都要拼接該串)
得到對拼接結果
JkTukkPc4Rha+nIkbYhEkQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
使用SHA-1(160數位)進行哈希操作,對哈希後的結果base64 進行編碼,就可以得到結果
RD69G0BS8RPH/GbY6rBsZI75pjk=
以上操作不僅僅是服務端要做,用戶端也同樣做了該操作,然後将自己計算得到的結果和伺服器傳來的Sec-WebSocket-Accept作比較,如果相等則沒問題,握手成功,如果不一緻,則握手失敗。
3、Origin用于防止未授權的跨域腳本攻擊,伺服器可以從Origin決定是否接受該WebSocket連接配接,所有來源于浏覽器的請求都會帶上該請求頭。如,已授權站點www.mydomain.com,并且産生cookie,使用者沒關閉授權站點的時候點選另外的www.hack.com下面的連結,則該連接配接可以帶着授權的cookie去做很多事。如果有了Origin,并且在服務端setAllowedOrigins("www.mydomain.com")則可以避免。
4、Sec-WebSocket-Version: 13 ,表明了websocket的版本,以前各廠商混戰的時候什麼樣的版本都有,還好現在已經統一了,用13版即可。
代碼以及部署
我的程式部署于tomcat7.0.5,基于Spring 4.3.7,需要使用支援websocket的IE10、Chrome和Firefox等較進階别版本的浏覽器打開。
程式主要分為6個部分:2個配置xml、3個java檔案、1個jsp。
前台主要用H5提供的websocket對象,它存在于頂層對象window下,凡是支援H5的浏覽器都實作了該對象。
背景主要用Spring提供的websocket配置類WebSocketConfigurer、攔截器HttpSessionHandshakeInterceptor和處理器AbstractWebSocketHandler來實作。
如下:
web.xml
[html] view plain copy
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
- <display-name>WebsocketTest</display-name>
- <servlet>
- <servlet-name>springmvc</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <!-- <param-value>classpath:spring-mvc.xml</param-value> --><!-- 類路徑 -->
- <param-value>/WEB-INF/spring-mvc.xml</param-value> <!-- 相對路徑 -->
- </init-param>
- </servlet>
- <servlet-mapping>
- <servlet-name>springmvc</servlet-name>
- <url-pattern>*.sc</url-pattern>
- </servlet-mapping>
- <welcome-file-list>
- <welcome-file>index.html</welcome-file>
- <welcome-file>index.htm</welcome-file>
- <welcome-file>index.jsp</welcome-file>
- <welcome-file>default.html</welcome-file>
- <welcome-file>default.htm</welcome-file>
- <welcome-file>default.jsp</welcome-file>
- </welcome-file-list>
- </web-app>
spring-mvc.xml
[html] view plain copy
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
- <context:component-scan base-package="com.websocket"></context:component-scan>
- </beans>
WebSocketConfig.java
[java] view plain copy
- package com.websocket;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.socket.config.annotation.EnableWebSocket;
- import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
- import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
- @Configuration //配置類
- @EnableWebSocket //聲明支援websocket
- public class WebSocketConfig implements WebSocketConfigurer{
- @Override
- public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
- <span style="white-space:pre;"> </span>String[] allowsOrigins={"http://localhost:8080"};
- <span style="white-space:pre;"> </span>//addHandler注冊和路由的功能,當用戶端發起websocket連接配接,把/path交給對應的handler處理,而不實作具體的業務邏輯,可以了解為收集和任務分發中心。
- <span style="white-space:pre;"> </span>//setAllowedOrigins(String[] domains),允許指定的域名或IP(含端口号)建立長連接配接,預設隻有本地。如果不限時使用"*"号,如果指定了域名,則必須要以http或https開頭。
- <span style="white-space:pre;"> </span>//addInterceptors,顧名思義就是為handler添加攔截器,可以在調用handler前後加入自定義的邏輯代碼。
- registry.addHandler(ChatRoom(), "/chat.sc").setAllowedOrigins("*").addInterceptors(handshakeInterceptor());
- //允許用戶端使用SockJS
- //SockJS 是一個浏覽器上運作的 JavaScript 庫,如果浏覽器不支援 WebSocket,該庫可以模拟對 WebSocket 的支援。
- registry.addHandler(ChatRoom(), "/sockjs/chat.sc").addInterceptors(handshakeInterceptor()).withSockJS();
- }
- @Bean
- public HandshakeInterceptor handshakeInterceptor(){
- return new HandshakeInterceptor();
- }
- @Bean
- public ChatRoom ChatRoom(){
- return new ChatRoom();
- }
- }
HandshakeInterceptor.java
[java] view plain copy
- package com.websocket;
- import java.util.Map;
- import java.util.Random;
- import org.springframework.http.server.ServerHttpRequest;
- import org.springframework.http.server.ServerHttpResponse;
- import org.springframework.web.socket.WebSocketHandler;
- import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
- public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{
- @Override
- public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Map<String, Object> attributes) throws Exception {
- //attributes是session裡面的所有屬性的map表示
- attributes.put("user", getRandomNickName());
- return super.beforeHandshake(request, response, handler, attributes);
- }
- @Override
- public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
- super.afterHandshake(request, response, wsHandler, ex);
- }
- //給每個進來的人(session)随機配置設定個昵稱,這裡沒做控制,是以聊天室内的昵稱可能發生重複
- public String getRandomNickName(){
- String[] nickNameArray={"Captain America","Deadpool","Hawkeye","Hulk","Iron Man","Spider Man","Thor","Wolverine","Black Panther","Colossus"};
- Random random=new Random();
- return nickNameArray[random.nextInt(10)];
- }
- }
ChatRoom.java
[html] view plain copy
- package com.websocket;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Date;
- import java.util.List;
- import org.apache.log4j.Logger;
- import org.springframework.web.socket.BinaryMessage;
- import org.springframework.web.socket.CloseStatus;
- import org.springframework.web.socket.TextMessage;
- import org.springframework.web.socket.WebSocketSession;
- import org.springframework.web.socket.handler.AbstractWebSocketHandler;
[html] view plain copy
- public class ChatRoom extends AbstractWebSocketHandler{
- public final static List<WebSocketSession> sessionList = Collections.synchronizedList(new ArrayList<WebSocketSession>());
- SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
- Logger logger = Logger.getLogger(this.getClass());
- FileOutputStream output;
- @Override
- public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
- System.out.println("Connection established..."+webSocketSession.getRemoteAddress());
- System.out.println(webSocketSession.getAttributes().get("user")+" Login");
- webSocketSession.sendMessage(new TextMessage("I'm "+(webSocketSession.getAttributes().get("user"))));
- sessionList.add(webSocketSession);
- }
- @Override
- public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus status) throws Exception {
- System.out.println("Connection closed..."+webSocketSession.getRemoteAddress()+" "+status);
- System.out.println(webSocketSession.getAttributes().get("user")+" Logout");
- sessionList.remove(webSocketSession);
- }
- @Override
- public void handleTextMessage(WebSocketSession websocketsession, TextMessage message)
- {
- String payload=message.getPayload();
- String textString;
- try {
- if(payload.endsWith(":fileStart")){
- output=new FileOutputStream(new File("D:\\images\\"+payload.split(":")[0]));
- }else if(payload.endsWith(":fileFinishSingle")){
- output.close();
- String fileName=payload.split(":")[0];
- for(WebSocketSession session:sessionList){
- if(session.getId().equals(websocketsession.getId())){
- textString=" I ("+format.format(new Date())+")<br>";
- }else{
- textString=websocketsession.getAttributes().get("user")+" ("+format.format(new Date())+")<br>";
- }
- TextMessage textMessage = new TextMessage(textString);
- session.sendMessage(textMessage);
- sendPicture(session,fileName);
- }
- }else if(payload.endsWith(":fileFinishWithText")){
- output.close();
- String fileName=payload.split(":")[0];
- for(WebSocketSession session:sessionList){
- sendPicture(session,fileName);
- }
- }else{
- for(WebSocketSession session: sessionList){
- if(session.getId().equals(websocketsession.getId())){
- textString=" I ("+format.format(new Date())+")<br>"+payload;
- }else{
- textString=websocketsession.getAttributes().get("user")+" ("+format.format(new Date())+")<br>"+payload;
- }
- TextMessage textMessage = new TextMessage(textString);
- session.sendMessage(textMessage);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void handleBinaryMessage(WebSocketSession session, BinaryMessage message)
- {
- ByteBuffer buffer= message.getPayload();
- try {
- output.write(buffer.array());
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
- if(webSocketSession.isOpen()){
- webSocketSession.close();
- }
- System.out.println(throwable.toString());
- System.out.println("WS connection error,close..."+webSocketSession.getRemoteAddress());
- }
- @Override
- public boolean supportsPartialMessages() {
- return true;
- }
- public void sendPicture(WebSocketSession session,String fileName){
- FileInputStream input;
- try {
- File file=new File("D:\\images\\"+fileName);
- input = new FileInputStream(file);
- byte bytes[] = new byte[(int) file.length()];
- input.read(bytes);
- BinaryMessage byteMessage=new BinaryMessage(bytes);
- session.sendMessage(byteMessage);
- input.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
chatRoom.jsp
[html] view plain copy
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Insert title here</title>
- <script type="text/javascript" src="js/sockjs.min.js"></script>
- <script type="text/javascript" src="js/jquery.js"></script>
- <script type="text/javascript">
- var url = "127.0.0.1:8080/websocket";
- var websocket = null;
- if ('WebSocket' in window) {
- websocket = new WebSocket("ws://" + url + "/chat.sc");
- } else {
- websocket = new SockJS("http://" + url + "/sockjs/chat.sc");
- }
- websocket.onopen = onOpen;
- websocket.onmessage = onMessage;
- websocket.onerror = onError;
- websocket.onclose = onClose;
- function onOpen(openEvent) {
- document.getElementById("plane").innerHTML = document.getElementById("plane").innerHTML+ "OPEN<br/>";
- }
- function onMessage(event) {
- if(typeof event.data =='string'){
- var element = document.createElement("p");
- element.innerHTML=event.data;
- document.getElementById("plane").appendChild(element);
- }else{
- var reader = new FileReader();
- reader.onload=function(eve){
- if(eve.target.readyState==FileReader.DONE)
- {
- var img = document.createElement("img");
- img.src=this.result;
- document.getElementById("plane").appendChild(img);
- }
- };
- reader.readAsDataURL(event.data);
- }
- }
- function onError() {
- }
- function onClose(event) {
- console.log(event.reason)
- document.getElementById("plane").innerHTML = document.getElementById("plane").innerHTML+ "CLOSE<br/>";
- }
- function doSend() {
- if (websocket.readyState == 1) { //0-CONNECTING;1-OPEN;2-CLOSING;3-CLOSED
- var msg = document.getElementById("message").value;
- if(msg){
- websocket.send(msg);
- }
- sendFile(msg);
- document.getElementById("message").value="";
- } else {
- alert("connect fail!");
- }
- }
- function sendFile(isWithText){
- var inputElement = document.getElementById("file");
- var fileList = inputElement.files;
- var file=fileList[0];
- if(!file) return;
- websocket.send(file.name+":fileStart");
- var reader = new FileReader();
- //以二進制形式讀取檔案
- reader.readAsArrayBuffer(file);
- //檔案讀取完畢後該函數響應
- reader.onload = function loaded(evt) {
- var blob = evt.target.result;
- //發送二進制表示的檔案
- websocket.send(blob);
- if(isWithText){
- websocket.send(file.name+":fileFinishWithText");
- }else{
- websocket.send(file.name+":fileFinishSingle");
- }
- console.log("finnish");
- }
- inputElement.outerHTML=inputElement.outerHTML; //清空<input type="file">的值
- }
- function disconnect(){
- if (websocket != null) {
- websocket.close();
- websocket = null;
- }
- }
- function reconnect(){
- if (websocket != null) {
- websocket.close();
- websocket = null;
- }
- if ('WebSocket' in window) {
- websocket = new WebSocket("ws://" + url + "/chat.sc");
- } else {
- websocket = new SockJS("http://" + url + "/sockjs/chat.sc");
- }
- websocket.onopen = onOpen;
- websocket.onmessage = onMessage;
- websocket.onerror = onError;
- websocket.onclose = onClose;
- }
- function clean(){
- document.getElementById("plane").innerHTML = "";
- }
- </script>
- </head>
- <body>
- <div>
- <button id="disconnect" onclick="disconnect()">disconnect</button>
- <button id="send" onclick="doSend()">send</button>
- <button id="reconnect" onclick="reconnect()">reconnect</button>
- <button id="clean" onclick="clean()">clean</button>
- <!-- <br>
- <input type="button" value="sendFile" onclick="sendFile()"/> -->
- <input type="file" id="file" />
- </div>
- <div>
- <input id="message" type="text" style="width: 350px"></input>
- </div>
- <div id="plane"></div>
- </body>
- </html>
部署成功後,通路http://localhost:8080/websocket/view/chatRoom.jsp 可以得到效果如下圖所示,分别是聊天室内3個人的聊天頁面: