WEBSOCKET協議判斷 握手及反饋


    消息中心的布點,是通過WEBSOCKET與后端服務器建立長連接的方式實現的,這種方式的優點一是節約網絡帶寬,二是用戶可以實時的收到由后台發過來的消息,后端的實現采用的是NETTY,經過壓力測試,每台服務器可以承受50萬的長連接,也就是同時50萬個用戶(只為每個網站用戶建立一個長連接),性能上還是比較好的。

    要建立長連接,首先需要由客戶端發起與服務端的握手動作,以下是從wikipedia找的一個示例:

    瀏覽器請求:    

 

GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
Sec-WebSocket-Protocol: sample
Upgrade: WebSocket
Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
Origin: http://example.com
^n:ds[4U

    在請求中的“Sec-WebSocket-Key1”, “Sec-WebSocket-Key2”和最后的“^n:ds[4U”都是隨機的,服務器端會用這些數據來構造出一個16字節的應答。其中:^n:ds[4U為請求的內容,其它的都是http請求頭。

    注:Sec-WebSocket-Key1和Sec-WebSocket-Key2在舊的WEBSOCKET協議中是沒有的,因為判斷當前請求是否WEBSOCKET,主要還是通過請求頭中的Connection是不是等於Upgrade以及Upgrade是否等於WebSocket,也就是說判斷一個請求是否WEBSOCKET請求,只需要判斷請求頭中的Connection及Upgrade,判斷新舊版本可以通過是否包含“Sec-WebSocket-Key1”和“Sec-WebSocket-Key2”。以下是一小段判斷是否WEBSOCKET請求的代碼:

 

//注:代碼是基於Netty的
private boolean isWebSocketReq(HttpRequest req) {
        return (HttpHeaders.Values.UPGRADE.equalsIgnoreCase(req.getHeader(HttpHeaders.Names.CONNECTION)) && HttpHeaders.Values.WEBSOCKET.equalsIgnoreCase(req.getHeader(HttpHeaders.Names.UPGRADE)));
    }

 

    服務端回應:    

 

HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Location: ws://example.com/demo
Sec-WebSocket-Protocol: sample
8jKS’y:G*Co,Wxa-
    把請求的第一個Key中的數字除以第一個Key的空白字符的數量,而第二個Key也是如此。然后把這兩個結果與請求最后的8字節字符串連接起來成為一個字符串,服務器應答正文(“8jKS’y:G*Co,Wxa-”)即這個字符串的MD5 sum。以下是一段基於Netty的響應JAVA代碼:    

 

    //基於Netty的WEBSOCKET響應代碼
    private HttpResponse buildWebSocketRes(HttpRequest req) {
        HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                   new HttpResponseStatus(101, "Web Socket Protocol Handshake"));
        res.addHeader(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET);
        res.addHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE);
        // Fill in the headers and contents depending on handshake method.
        if (req.containsHeader(Names.SEC_WEBSOCKET_KEY1) && req.containsHeader(Names.SEC_WEBSOCKET_KEY2)) {//草案7.5、7.6和協議標准
            // New handshake method with a challenge:
            res.addHeader(Names.SEC_WEBSOCKET_ORIGIN, req.getHeader(Names.ORIGIN));
            res.addHeader(Names.SEC_WEBSOCKET_LOCATION, getWebSocketLocation(req));
            String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
            if (protocol != null) {
                res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
            }

            // Calculate the answer of the challenge.
            String key1 = req.getHeader(Names.SEC_WEBSOCKET_KEY1);
            String key2 = req.getHeader(Names.SEC_WEBSOCKET_KEY2);
            int a = (int) (Long.parseLong(getNumeric(key1)) / getSpace(key1).length());
            int b = (int) (Long.parseLong(getNumeric(key2)) / getSpace(key2).length());
            long c = req.getContent().readLong();
            ChannelBuffer input = ChannelBuffers.buffer(16);
            input.writeInt(a);
            input.writeInt(b);
            input.writeLong(c);
            ChannelBuffer output = null;
            try {
                output = ChannelBuffers.wrappedBuffer(MessageDigest.getInstance("MD5").digest(input.array()));
            } catch (NoSuchAlgorithmException e) {
                logger.error("no such Algorithm : MD5. ", e);
            }

            res.setContent(output);
        } else {//最老的websocket協議
            // Old handshake method with no challenge:
            if (req.getHeader(Names.ORIGIN) != null) {
                res.addHeader(Names.WEBSOCKET_ORIGIN, req.getHeader(Names.ORIGIN));
            }
            res.addHeader(Names.WEBSOCKET_LOCATION, getWebSocketLocation(req));
            String protocol = req.getHeader(Names.WEBSOCKET_PROTOCOL);
            if (protocol != null) {
                res.addHeader(Names.WEBSOCKET_PROTOCOL, protocol);
            }
        }

        return res;
    }
    // 去掉傳入字符串的所有非數字
    private String getNumeric(String str) {
        return str.replaceAll("\\D", "");
    }

    // 返回傳入字符串的空格
    private String getSpace(String str) {
        return str.replaceAll("\\S", "");
    }

后記:

    最近發現chrome14及FF6.5中使用最新的websocket草案10協議,也就是說上面的例出的代碼還不能夠支持草案10的握手協議,草案手的協議變量比較大,如傳輸是通過幀來進行的,並且對幀的位有權限檢查等,詳細可以查看我的另外一篇文章:http://blog.csdn.net/fenglibing/article/details/6852497

 

本文出自:馮立彬的博客

 

 


 

再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow


免責聲明!

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



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