1. 程式人生 > >WebSocket學習與實現

WebSocket學習與實現

1、WebSocket Protocol

WebSocket協議標準是在2014年8月13日被JCP確認釋出,也就是JSR356,具體詳情,請點選這裡

Websocket協議它的描述如下:

websocket協議允許在客戶端和服務端之間建立一條雙向傳遞資訊的通道,它是建立在TCP協議之上的,首先通過”握手“來確認和建立通道,之後客戶端和服務端可以通過這個通道傳遞資訊,而不需要再次發起請求,而且客戶端和服務端都可以主動的傳送訊息。這種技術不依賴於HTTP連線(比如XMLHttpRequest,iframe),可以實現實時訊息傳遞。

Websocket的建立過程如下:

客戶端客戶端服務端服務端shankehand(握手)對請求訊息頭進行識別和簽名握手確認通道建立成功訊息傳遞訊息傳遞

整個過程大體分為兩個階段:握手和資料傳輸

客戶端向服務端發起的握手訊息頭如下:

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

服務端對握手請求成功的迴應為:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Sec-WebSocket-Protocol: chat
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

至此,握手成功,也就是通道已經成功建立,可以進行資料傳輸。

檢視網路連線如下:

Request URL:ws://server.example.com/chat 
Request Method:GET 
Status Code:101 Switching Protocols

請求協議為ws,方法為GET,成功返回狀態為101。訊息頭中的Sec-WebSocket-Key和Sec-WebSocket-Accept為base-64編碼的,且Sec-WebSocket-Key為隨機生成的,Sec-WebSocket-Accept則為服務端對Sec-WebSocket-Key進行SHA-1 hash計算後返回的base-64編碼的字串。

接下來,我們通過程式來研究一下Websocket,工具:

JDK 7, Eclipse Kepler, Tomcat 7.0.67, Chrome 47, Window 10

2、WebSocket 服務端實現

我們將使用到websocket-api,這個JAR包則在Tomcat的lib下面,新建工程後,需要將Server Library新增到構建路徑中(期間我嘗試過只引用這個包,而使用低版本的Tomcat,websocket則無法成功),而在同目錄下有個tomcat7-websocket包,具體作用未探知。

在websocket-api中,我們使用註解來完成服務端的配置可實現,用到的註解有@ServerEndpoint@OnOpen@OnMessage@OnClose
在寫本案例時,新建的是Maven工程,工程名為websocket(此工程已提交到Github,地址見底部),目錄結構為:

這裡寫圖片描述

WebSocketServer的程式碼為:

@ServerEndpoint("/chat")
public class WebSocketServer {

    private Session session;
    private Logger logger = Logger.getLogger(WebSocketServer.class);

    @OnOpen
    public void open(Session session){
        this.session = session;
        logger.info("WebSocket is opening...");
        logger.info("Sesson id: " + this.session.getId());
        logger.info("Query string: " + this.session.getQueryString());
    }

    @OnMessage
    public void onMessage(String message){
        logger.info("Client send message: " + message);
        try {
            this.session.getBasicRemote().sendText("Server message form Websocket server");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(){
        logger.info("Websocket closed");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

為了探究請求,還特意新增一個過濾器,DispatcherFilter.java,程式碼如下:


@WebFilter(urlPatterns={"/*"})
public class DispatcherFilter implements Filter {

    private Logger logger = Logger.getLogger(DispatcherFilter.class);

    public void destroy() {
        // Auto-generated method stub

    }

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain chain) throws IOException, ServletException {
        // Auto-generated method stub
        HttpServletRequest request = (HttpServletRequest) arg0;
        logger.info("Request URL: " + request.getRequestURL());
        chain.doFilter(arg0, arg1);
    }

    public void init(FilterConfig arg0) throws ServletException {
        // Auto-generated method stub

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

同時也別忘了日誌的配置資訊:

log4j.rootLogger=DEBUG,STDOUT 
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender 
log4j.appender.STDOUT.Threshold=DEBUG 
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout 
log4j.appender.STDOUT.layout.ConversionPattern= %d %5p (%c:%L) - %m%n

服務端的實現程式碼就是這些。

3、WebSocket客戶端實現

這裡所說的客戶端就指的是瀏覽器,在瀏覽器端的實現則是需要對HTML5的支援,當然,谷歌瀏覽器為首選,毋庸置疑,他是對這些支援最好的而且除錯很方便(順便鄙視一下某E瀏覽器)。

var webSocket = new WebSocket('ws://localhost:8080/websocket/chat?query=Picasso');
webSocket.onerror = function(event) {
    //TODO
};

webSocket.onopen = function(event) {
    //TODO
};
//接受訊息
webSocket.onmessage = function(event) {
    //TODO
    console.log(event.data)
};
//傳送訊息
webSocket.send('hello ' + Math.random());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

這些程式碼已經完成WebSocket的建立和訊息傳輸,這些程式碼位於src\main\webapp\index.jsp中,其餘樣式和佈局程式碼,不再這裡貼出,請檢視原始碼。

4、WebSocket除錯

首先,將此工程新增到Eclipse Servers檢視新建的tomcat伺服器中,啟動。開啟瀏覽器輸入地址:

當然,tomcat的預設埠為8080,如有衝突或其他,可在Server.xml中修改配置。

記得同時開啟Google的開發工具F12,待會兒要檢視請求頭:

這裡寫圖片描述

檢視網路請求狀況:

這裡寫圖片描述

清楚的可以看見請求頭和響應頭,而此時檢視伺服器的控制檯,則有如下資訊:

2015-12-20 22:12:03,913 INFO (cc.eabour.websocket.filter.DispatcherFilter:30) - Request URL: http://localhost:8080/websocket/ 
2015-12-20 22:12:03,931 INFO (cc.eabour.websocket.filter.DispatcherFilter:30) - Request URL: http://localhost:8080/websocket/chat 
2015-12-20 22:12:03,933 INFO (cc.eabour.websocket.WebSocketServer:22) - WebSocket is opening… 
2015-12-20 22:12:03,933 INFO (cc.eabour.websocket.WebSocketServer:24) - Sesson id: 6 
2015-12-20 22:12:03,933 INFO (cc.eabour.websocket.WebSocketServer:25) - Query string: query=Picasso

從過濾器攔截日誌可以看出,websocket的建立顯示通過http請求來進行握手,之後則在建立好的通道上傳輸資料。

5、後記

通過簡單的實現來了解一下WebSocket的工作原理和實現方法,後續將進一步強化和增加實現案例。

如有問題,歡迎指教

參考資料:

首次使用Github,Maven也不是很熟,還請多多包涵。

(不得不說,CSDN的Markdown編輯器真不賴)