WebSocket

WebSocket是什麼?javascript

WebSocket是一種在單個TCP鏈接上進行全雙向通訊的協議。
WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。
在WebSocket API中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。
Websocket 經過HTTP/1.1 協議的101狀態碼進行握手。html

 

握手java

在客戶端,new WebSocket實例化一個新的WebSocket客戶端對象,請求相似 ws://yourdomain:port/path 的服務端WebSocket URL,客戶端WebSocket對象會自動解析並識別爲WebSocket請求,並鏈接服務端端口,執行雙方握手過程,客戶端發送數據格式相似:web

能夠看到,客戶端發起的WebSocket鏈接報文相似傳統HTTP報文,Upgrade:websocket參數值代表這是WebSocket類型請求,Sec-WebSocket-Key是WebSocket客戶端發送的一個 base64編碼的密文,要求服務端必須返回一個對應加密的Sec-WebSocket-Accept應答,不然客戶端會拋出Error during WebSocket handshake錯誤,並關閉鏈接。spring

服務端收到報文後返回的數據格式相似:瀏覽器

Sec-WebSocket-Accept的值是服務端採用與客戶端一致的密鑰計算出來後返回客戶端的,HTTP/1.1 101 Switching Protocols表示服務端接受WebSocket協議的客戶端鏈接,通過這樣的請求-響應處理後,兩端的WebSocket鏈接握手成功, 後續就能夠進行TCP通信了。安全

 

保持長鏈接服務器

服務器和客戶端經過發送Ping/Pong+Frame(RFC+6455+-+The+WebSocket+Protocol)。這種 Frame+是一種特殊的數據包,它只包含一些元數據而不須要真正的 Data+Payload,能夠在不影響應用的狀況下維持住中間網絡的鏈接狀態。websocket

 

例子1網絡

pom文件:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

java:

package com.test;

import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketController {
    //當前在線鏈接數。應該把它設計成線程安全的。
    private static int onlineCount = 0;

    //concurrent包的線程安全Set,用來存放每一個客戶端對應的WebSocket對象。
    private static CopyOnWriteArraySet<WebSocketController> webSocketSet = new CopyOnWriteArraySet<WebSocketController>();

    //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
    private Session session;

    /**
     * 鏈接創建成功調用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);//加入set中
        addOnlineCount();//在線數加1
        System.out.println("有新鏈接加入!當前在線人數爲" + getOnlineCount());
        try {
            sendMessage("init success!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 鏈接關閉調用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);//從set中刪除
        subOnlineCount();//在線數減1
        System.out.println("有一鏈接關閉!當前在線人數爲" + getOnlineCount());
    }

    /**
     * 收到客戶端消息後調用的方法
     * @param message 客戶端發送過來的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("來自客戶端的消息:" + message);
        try {
            session.getBasicRemote().sendText("service:"+message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 發生錯誤時調用
     */
     @OnError
     public void onError(Session session, Throwable error) {
        System.out.println("發生錯誤");
        error.printStackTrace();
     }

    /**
     * 發送消息
     */
     public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
     }

     /**
      * 羣發自定義消息
      */
    public static void sendInfo(String message) {
        if(webSocketSet!=null && webSocketSet.size()>0) {
            for (WebSocketController item : webSocketSet) {
                try {
                    item.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketController.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketController.onlineCount--;
    }
}

 

package com.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

html:

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>WebSocket</title>
</head>

<body>
    Welcome Use WebSocket<br/>
    <input id="text" type="text" />
    <button onclick="send()">Send</button>
    <button onclick="closeWebSocket()">Close</button>
    <div id="message"></div>
</body>
<script type="text/javascript">
    var websocket = null;

    //判斷當前瀏覽器是否支持WebSocket
    if('WebSocket' in window){
        websocket = new WebSocket("ws://localhost:8080/websocket");
    } else{
        alert('Not support websocket')
    }

    //鏈接發生錯誤的回調方法
    websocket.onerror = function(){
        setMessageInnerHTML("error");
    };

    //鏈接成功創建的回調方法
    websocket.onopen = function(event){
        setMessageInnerHTML("open");
    }

    //接收到消息的回調方法
    websocket.onmessage = function(event){
        setMessageInnerHTML(event.data);
    }

    //鏈接關閉的回調方法
    websocket.onclose = function(){
        setMessageInnerHTML("close");
    }

    //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。
    window.onbeforeunload = function(){
        websocket.close();
    }

    //將消息顯示在網頁上
    function setMessageInnerHTML(innerHTML){
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //關閉鏈接
    function closeWebSocket(){
        websocket.close();
    }

    //發送消息
    function send(){
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

  例子2(一個websocket分發多個功能,經過參數區分)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>
package com.cuc.happyseat.config.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 開啓WebSocket支持
 */
@Configuration  
public class WebSocketConfig {  
    
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
  
}

  

WebSocket服務類對照下面的代碼看:

第20行,@ServerEndpoint("/websocket/{userID}"),括號中的內容就是客戶端請求Socket鏈接時的訪問路徑,userID是我要求客戶端傳來的參數,我這裏算是爲了標識該客戶端吧。
第28行,在該類中添加屬性 userID,並添加對應的getUserID()方法。
第46行,在onOpen()方法即創建鏈接的時候就接收參數userID,須要標識@PathParam("userID") 。接收參數後直接賦值給屬性userID。
第140-157行,是針對特定客戶端發送消息。服務器和客戶端在創建鏈接成功後就生成了一個WebSocket對象,並存在集合中,對象裏特有的屬性是咱們設置的userID。因此經過惟一的userID就能標識服務器與該客戶端創建的那個鏈接啦!這樣要求發送消息時,傳入userID與消息,服務器在本身的WebSocket鏈接集合中遍歷找到對應客戶端的鏈接,就能夠直接發消息過去啦~~

package com.cuc.happyseat.websocket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.EncodeException;
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 org.springframework.stereotype.Component;

/*@ServerEndpoint註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端,
 * 註解的值將被用於監聽用戶鏈接的終端訪問URL地址,客戶端能夠經過這個URL來鏈接到WebSocket服務器端
*/
@ServerEndpoint("/websocket/{userID}")
@Component
public class WebSocketServer {

	//每一個客戶端都會有相應的session,服務端能夠發送相關消息
	private Session session;

	//接收userID
	private Integer userID;
 
	//J.U.C包下線程安全的類,主要用來存放每一個客戶端對應的webSocket鏈接
	private static CopyOnWriteArraySet<WebSocketServer> copyOnWriteArraySet = new CopyOnWriteArraySet<WebSocketServer>();

	public Integer getUserID() {
		return userID;
	}
    
	/**
	 * @Name:onOpen
	 * @Description:打開鏈接。進入頁面後會自動發請求到此進行鏈接
	 * @Author:mYunYu
	 * @Create Date:14:46 2018/11/15
	 * @Parameters:@PathParam("userID") Integer userID
	 * @Return:
	 */
	@OnOpen
	public void onOpen(Session session, @PathParam("userID") Integer userID) {
		this.session = session;
		this.userID = userID;
		System.out.println(this.session.getId());
		copyOnWriteArraySet.add(this);
	}

	/**
	 * @Name:onClose
	 * @Description:用戶關閉頁面,即關閉鏈接
	 * @Author:mYunYu
	 * @Create Date:14:46 2018/11/15
	 * @Parameters:
	 * @Return:
	 */
	@OnClose
	public void onClose() {
		copyOnWriteArraySet.remove(this);
	}

	/**
	 * @Name:onMessage
	 * @Description:測試客戶端發送消息,測試是否聯通
	 * @Author:mYunYu
	 * @Create Date:14:46 2018/11/15
	 * @Parameters:
	 * @Return:
	 */
	@OnMessage
	public void onMessage(String message) {
		System.out.println("websocket收到客戶端發來的消息:"+message);
	}

	/**
	 * @Name:onError
	 * @Description:出現錯誤
	 * @Author:mYunYu
	 * @Create Date:14:46 2018/11/15
	 * @Parameters:
	 * @Return:
	 */
	@OnError
	public void onError(Session session, Throwable error) {
		System.out.println("發生錯誤:" + error.getMessage() + "; sessionId:" + session.getId());
		error.printStackTrace();
	}

	public void sendMessage(Object object){
		//遍歷客戶端
		for (WebSocketServer webSocket : copyOnWriteArraySet) {
			System.out.println("websocket廣播消息:" + object.toString());
			try {
				//服務器主動推送
				webSocket.session.getBasicRemote().sendObject(object) ;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @Name:sendMessage
	 * @Description:用於發送給客戶端消息(羣發)
	 * @Author:mYunYu
	 * @Create Date:14:46 2018/11/15
	 * @Parameters:
	 * @Return:
	 */
	public void sendMessage(String message) {
		//遍歷客戶端
		for (WebSocketServer webSocket : copyOnWriteArraySet) {
			System.out.println("websocket廣播消息:" + message);
			try {
				//服務器主動推送
				webSocket.session.getBasicRemote().sendText(message);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @throws Exception 
	 * @Name:sendMessage
	 * @Description:用於發送給指定客戶端消息
	 * @Author:mYunYu
	 * @Create Date:14:47 2018/11/15
	 * @Parameters:
	 * @Return:
	 */
	public void sendMessage(Integer userID, String message) throws Exception {
		Session session = null;
		WebSocketServer tempWebSocket = null;
		for (WebSocketServer webSocket : copyOnWriteArraySet) {
			if (webSocket.getUserID() == userID) {
				tempWebSocket = webSocket;
				session = webSocket.session;
				break;
			}
		}
		if (session != null) {
			//服務器主動推送
			tempWebSocket.session.getBasicRemote().sendText(message);
		} else {
			System.out.println("沒有找到你指定ID的會話:{}"+ "; userId:" + userID);
		}
	}    
}
相關文章
相關標籤/搜索