[Tomcat] Tomcat 7中的Websocket


1. WebSocket介紹

  WebSocket協議是一種雙向通信協議,它建立在TCP之上,同http一樣通過TCP來傳輸數據,但是它和http最大的不同有兩點:1.WebSocket是一種雙向通信協議,在建立連接后,WebSocket服務器和Browser/UA都能主動的向對方發送或接收數據,就像Socket一樣,不同的是WebSocket是一種建立在Web基礎上的一種簡單模擬Socket的協議;2.WebSocket需要通過握手連接,類似於TCP它也需要客戶端和服務器端進行握手連接,連接成功后才能相互通信。簡單的建立握手的時序圖如下:

握手過程:

  1. Browser與WebSocket服務器通過TCP三次握手建立連接,如果這個建立連接失敗,那么后面的過程就不會執行,Web應用程序將收到錯誤消息通知。
  2. 在TCP建立連接成功后,Browser/UA通過http協議傳送WebSocket支持的版本號,協議的字版本號,原始地址,主機地址等等一些列字段給服務器端。
  3. WebSocket服務器收到Browser/UA發送來的握手請求后,如果數據包數據和格式正確,客戶端和服務器端的協議版本號匹配等等,就接受本次握手連接,並給出相應的數據回復,同樣回復的數據包也是采用http協議傳輸。
  4. Browser收到服務器回復的數據包后,如果數據包內容、格式都沒有問題的話,就表示本次連接成功,觸發onopen消息,此時Web開發者就可以在此時通過send接口想服務器發送數據。否則,握手連接失敗,Web應用程序會收到onerror消息,並且能知道連接失敗的原因。

2. Tomcat 7中的Websocket架構

如圖所示,因為Websocket通信分為握手和數據傳輸兩個過程,兩個過程中需要用到的處理方式是不一樣的,握手過程是基於HTTP 1.1基礎上的,而數據傳輸是直接基於TCP的流傳輸。

       握手過程中,在HttpServletRequest的基礎上,封裝了WsHttpServletRequest類,添加了對Request的失效操作函數invalidate()。而在數據通信時,接受和處理數據過程中,基於org.apache.coyote.http11.upgrade.UpgradeInbound重新封裝了用於處理數據輸入流的類StreamInbound,並在StreamInbound的基礎上擴展生成了用於消息處理的類MessageInbound。在這兩個數據處理類中均留有onData,onTextData/onBinaryData,onOpen,onClose等事件操作函數接口,這些接口將在載入的代碼類中實現業務邏輯。在用於數據輸出流的類WsOutbound則是封裝了UpgradeOutbound對象實例,基於UpgradeOutbound對象的基礎上,添加了websocket響應有關的處理邏輯。這里處理函數均為同步調用的函數,保證websocket響應的時序性。

       Tomcat中Websocket的處理流程如下:

  1. 接收客戶端發來的握手請求,Coyote.http11連接器對socket進行解析,形成HttpServletRequest發送給Container。
  2. Container中的相應WebsocketServlet處理請求,如不接受連接請求,則返回,如接受連接請求,則對請求作出響應,建立起客戶端和服務器的socket連接。
  3. 服務器此時可以通過WsOutbound發送數據給客戶端,同時通過StreamInbound監聽socket。
  4. 如果接收到客戶端發來的數據,則將socket數據解析成frame,判斷frame類型,通過事件分發數據到不同的邏輯處理流程。
  5. 數據返回時調用WsOutbound對返回的數據進行封裝處理,發送給客戶端

3. 代碼分析

  1. WebSocketServlet

這個類負責WS的握手過程,通過對HTTP請求頭的判斷確定是否接受連接請求。接受連接請求后則建立websocket數據連接,連接建立過程如下所示:

WsHttpServletRequestWrapper wrapper = new WsHttpServletRequestWrapper(req); //將HttpServletRequest封裝可進行失效操作的WsHttpServletRequestWrapper
StreamInbound inbound = createWebSocketInbound(subProtocol, wrapper); //建立數據連接,監聽對應的端口
wrapper.invalidate(); //握手完,對這個Request進行invalidate處理

  2. StreamInbound

這個類最關鍵的是onData()函數,即接收到數據后的處理函數。這個函數里對接受的數據進行解析,並根據操作碼分發給不同的處理函數。

WsInputStream wsIs = new WsInputStream(processor, getWsOutbound()); //根據當前的Processor和定制的WsOutbound輸出流對象,構建輸入流的解析對象
        try {
            WsFrame frame = wsIs.nextFrame(true); //查找數據中的下一個Frame
            while (frame != null) {
                byte opCode = frame.getOpCode(); //查找Frame中的操作碼
                if (opCode == Constants.OPCODE_BINARY) {
                    doOnBinaryData(wsIs); //處理Binary數據
                } else if (opCode == Constants.OPCODE_TEXT) {
                    InputStreamReader r =
                            new InputStreamReader(wsIs, new Utf8Decoder());
                    doOnTextData(r); //處理文本數據
                } else if (opCode == Constants.OPCODE_CLOSE){
                    closeOutboundConnection(frame); //數據發送完畢,發送close frame
                    return SocketState.CLOSED;
                } else if (opCode == Constants.OPCODE_PING) {
                    getWsOutbound().pong(frame.getPayLoad()); //發送pong frame
                } else if (opCode == Constants.OPCODE_PONG) {
                } else {
                    closeOutboundConnection(
                            Constants.STATUS_PROTOCOL_ERROR, null);
                    return SocketState.CLOSED;
                }
                frame = wsIs.nextFrame(false);
            }
        } 

  3. MessageInbound

該類是StreamInbound的擴展類,實現了對文本數據的解析函數。文本處理過程中,主要用到了ByteBuffer和CharBuffer,通過對Buffer的操作實現文本數據的解析。

  4. WsOutbound

該類是處理Websocket輸出流的類,實現了Websocket幾個close,pong,ping和正常數據響應frame。比如在輸出文本數據的處理函數里:

public synchronized void writeTextData(char c) throws IOException {
        if (closed) { //數據流已關閉
            throw new IOException(sm.getString("outbound.closed"));
        }
        if (cb.position() == cb.capacity()) { //沒有數據可以返回
            doFlush(false);
        }
        if (text == null) {
            text = Boolean.TRUE;
        } else if (text == Boolean.FALSE) { //如果已經寫好數據准備傳輸
            flush(); //輸出數據
            text = Boolean.TRUE;
        }
        cb.append(c); //將添加到CharBuffer中
    } 

  5. WsInputStream

這個類主要用於輸入流的解析,將數據從InputStream中解析成websocket的frame。類的關鍵邏輯在read()函數中:

public int read(byte b[], int off, int len) throws IOException {
        makePayloadDataAvailable(); //確保有Payload數據可供讀取
        if (remaining == 0) { //frame數據已經讀到尾
            return -1;
        }
        if (len > remaining) { //重置可讀取數據長度
            len = (int) remaining;
        }
        int result = processor.read(true, b, off, len); //調用Processor進行數據讀取
        if(result == -1) {
            return -1;
        }
        for (int i = off; i < off + result; i++) {
            b[i] = (byte) (b[i] ^
                    frame.getMask()[(int) ((readThisFragment + i - off) % 4)]); //獲取幀的Mask
        }
        remaining -= result;
        readThisFragment += result;  //已讀幀數據
        return result;
    }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM