久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Websocket協(xié)議簡介

 昵稱9519415 2012-03-31
 

今天@julyclyde 在微博上問我websocket的細(xì)節(jié),。但是這個用70個字是無法說清楚的,所以就整理在這里吧,。恰好我最近要重構(gòu)年前寫的websocket的代碼,。

眾所周知,HTTP是一種基于消息(message)的請求(request )/應(yīng)答(response)協(xié)議,。當(dāng)我們在網(wǎng)頁中點擊一條鏈接(或者提交一個表單)的時候,,瀏覽器給服務(wù)器發(fā)一個request message,然后服務(wù)器算啊算,,答復(fù)一條response message,。主動發(fā)起TCP連接的是client,,接受TCP連接的是server,。HTTP消息只有兩種:request和response。client只能發(fā)送request message,,server只能發(fā)送response message,。一問一答,因此按HTTP協(xié)議本身的設(shè)計,,服務(wù)器不能主動的把消息推給客戶端,。而像微博、網(wǎng)頁聊天,、網(wǎng)頁游戲等都需要服務(wù)器主動給客戶端推東西,,現(xiàn)在只能用long polling等方式模擬,,很不方便。

 

OK,,來看看internet的另一邊,,網(wǎng)絡(luò)游戲是怎么工作的?

我之前在一個游戲公司工作,。我們做游戲的時候,,普遍采用的模式是雙向、異步消息模式,。

首先通信的最基本單元是message,。(這點和HTTP一樣)

其次,是雙向的,。client和server都可以給對方發(fā)消息(這點和HTTP不一樣)

最后,,消息是異步的。我給服務(wù)器發(fā)一條消息出去,,然后可能有一條答復(fù),,也可能有多條答復(fù),也可能根本沒有答復(fù),。無論如何,,調(diào)用完send方法我就不管了,我不會傻乎乎的在這里等答復(fù),。服務(wù)器和客戶端都會有一個線程專門負(fù)責(zé)read,,以及一個大大的switch… case,根據(jù)message id做相應(yīng)的action,。

while( msg=myconnection.readMessage()){

switch(msg.id()){

case LOGIN: do_login(); break;
case TALK: do_talk(); break;

}

}

Websocket就是把這樣一種模式,,搬入到HTTP/WEB的框架內(nèi)。它主要解決兩個問題:

  1. 從服務(wù)器給客戶端主動推東西,。
  2. HTTP協(xié)議傳輸效率低下的問題,。這一點在web service中尤為突出。每個請求和應(yīng)答都得帶上很長的http header,!

websocket協(xié)議在RFC 6455中定義,,這個RFC在上個月(2011年12月)才終于定稿、提交,。所以目前沒有任何一個瀏覽器是能完全符合這個RFC的最終版的,。Google是websocket協(xié)議的主力支持者,目前主流的瀏覽器中,,對websocket支持最好的就是chrome,。chrome目前的最新版本是16,支持的是RFC 6455的draft 13,http://tools./html/draft-ietf-hybi-thewebsocketprotocol-13 ,。IE9則是完全不支持websocket,。而server端,只有jetty和Node.js對websocket的支持比較好,。

 

Websocket協(xié)議可以分為兩個階段,,一個是握手階段,一個是數(shù)據(jù)傳輸階段,。

在建立TCP連接之后,,首先是websocket層的握手。這階段很簡單,,client給server發(fā)一個http request,,server給client一個http response。這個階段,,所有數(shù)據(jù)傳輸都是基于文本的,,和現(xiàn)有的HTTP/1.1協(xié)議保持兼容。

這是一個請求的例子:
Connection:Upgrade

Host:echo.

Origin:http://

Sec-WebSocket-Key:ov0xgaSDKDbFH7uZ1o+nSw==

Sec-WebSocket-Version:13

Upgrade:websocket

 

(其中Host和Origin不是必須的)

Connection是HTTP/1.1中常用的一個header,,以前經(jīng)常填的是keepalive或close,。這里填的是Upgrade。在設(shè)計HTTP/1.1的時候,,委員們就想到一個問題,,假如以后出HTTP 2.0了,那么現(xiàn)有的這套東西怎么辦呢,?所以HTTP協(xié)議中就預(yù)定義了一個header叫Upgrade,。如果客戶端發(fā)來的請求中有這個,那么意思就是說,,我支持某某協(xié)議,,并且我更偏向于用這個協(xié)議,你看你是不是也支持,?你要是支持,,咱們就換新協(xié)議吧!

然后就是websocket協(xié)議中所定義的兩個特殊的header,,Sec-WebSocket-Key和Sec-WebSocket-Version,。

其中Sec-WebSocket-Key是客戶端生的一串隨機(jī)數(shù),然后base64之后填進(jìn)去的,。Sec-WebSocket-Version是指協(xié)議的版本號,。這里的13,代表draft 13,。下面給出,我年前寫的發(fā)送握手請求的JAVA代碼:

// 生一個隨機(jī)字符串,,作為Sec-WebSocket-Key

View Code JAVA
        byte[] nonce = new byte[16];
        rand.nextBytes(nonce);
        BASE64Encoder encode = new BASE64Encoder();
        String chan = encode.encode(nonce);
 
        HttpRequest request = new BasicHttpRequest("GET", "/someurl");
        request.addHeader("Host", host);
        request.addHeader("Upgrade", "websocket");
        request.addHeader("Connection", "Upgrade");
        request.addHeader("Sec-WebSocket-Key", chan); // 生的隨機(jī)串
         request.addHeader("Sec-WebSocket-Version", "13");
        HttpResponse response;
        try {
            conn.sendRequestHeader(request);
            conn.flush();
            request.toString();
            response = conn.receiveResponseHeader();
        } catch (HttpException ex) {
            throw new RuntimeException("handshake fail", ex);
        }

 

服務(wù)器在收到握手請求之后需要做相應(yīng)的答復(fù),。消息的例子如下:

HTTP/1.1 101 Switching Protocols

Connection:Upgrade

Date:Sun, 29 Jan 2012 18:05:49 GMT

Sec-WebSocket-Accept:7vI97qQ5QRxq6lD6E5RRX36mOBc=

Server:jetty

Upgrade:websocket

(其中Date,、Server都不是必須的)

第一行是HTTP的Status-Line。注意,,這里的Status Code是101,。很少見吧!Sec-WebSocket-Accept字段是一個根據(jù)client發(fā)來的Sec-WebSocket-Key得到的計算結(jié)果,。

算法為:

把客戶端發(fā)來的key作為字符串,,與” 258EAFA5-E914-47DA-95CA-C5AB0DC85B11″這個字符串連接起來,然后做sha1 Hash,,將計算結(jié)果base64編碼,。注意,用來和” 258EAFA5-E914-47DA-95CA-C5AB0DC85B11″做連接操作的字符串,,是base64編碼后的,。也就是說,客戶端發(fā)來什么樣就是什么樣,,不要試圖去做base64解碼,。

示例代碼如下:

    std::string value=request.get("Sec-WebSocket-Key");
    value+="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    unsigned char hash[20];
    sha1::calc(value.c_str(),value.length(),hash);
    std::string res=base64_encode(hash,sizeof(hash));
    std::ostringstream oss;
    oss<<"HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: "<<res<<"\r\n";    
               connection.send(oss.str());

握手成功后,進(jìn)入數(shù)據(jù)流階段,。這個階段就和http協(xié)議沒什么關(guān)系了,。是在TCP流的基礎(chǔ)上,把數(shù)據(jù)分成frame而已,。首先,,websocket的一個message,可以被分成多個frame,。從邏輯上來看,,frame的格式如下

isFinal

opcode

isMasked

Data length

Mask key

Data(可以是文本,也可以是二進(jìn)制)

 

isFinal:

每個frame的第一個字節(jié)的最高位,,代表這個frame是不是該message的最后一個frame,。1代表是最后一個,0代表后面還有,。

opcode:

指明這個frame的類型,。目前定義了這么幾類continuation、text ,、binary ,、connection close、ping,、pong,。對于應(yīng)用而言,最關(guān)心的就是,這個message是binary的呢,,還是text的,?因為html5中,對于這兩種message的接口有細(xì)微不一樣,。

isMasked:

客戶端發(fā)給服務(wù)器的消息,,要求加擾之后發(fā)送。加擾的方式是:客戶端生一個32位整數(shù)(mask key),,然后把data和這32位整數(shù)做異或,。

mask key:

前面已經(jīng)說過了,就是用來做異或的隨機(jī)數(shù),。

Data:

這才是我們真正要傳輸?shù)臄?shù)據(jù)?。?!

發(fā)送frame時加擾的代碼如下:

        java.util.Random rand ;

        ByteBuffer buffer;

        byte[] dataToSend;

        …

        

        byte[] mask = new byte[4];

        rand.nextBytes(mask);

        buffer.put(mask);

        int oldpos = buffer.position();        

        buffer.put(data);

        int newpos = buffer.position();

        // 按位異或

        for (int i = oldpos; i != newpos; ++i) {

            int maskIndex = (i – oldpos) % mask.length;

            buffer.put(i, (byte) (buffer.get(i) ^ (byte) mask[maskIndex]));

        }

下面討論一下這個協(xié)議的某些設(shè)計:

為什么要做這個異或操作呢,?

說來話長。首先從Connection:Upgrade這個header講起,。本來它是留給TLS用的,。就是,假如我從80端口去連接一個服務(wù)器,,然后通過發(fā)送Connection:Upgrade,,仿照上面所說的流程,把http協(xié)議”升級”成https協(xié)議,。但是實際上根本沒人這么用,。你要用https,你就去連接443,。我80端口根本不處理https,。由于這個header只是出現(xiàn)在rfc中,并未實際使用,,于是大多數(shù)cache server看不懂這個header,。這樣的結(jié)果是,cache server可能以為后面的傳輸數(shù)據(jù)依然是普通的http協(xié)議,,然后按照原來的規(guī)則做cache,。那么,如果這個client和server都已經(jīng)被黑客很好的操控,,他就可以往這個cache server上投毒,。比如,從client發(fā)送一個websocket frame,,但是偽裝成普通的http GET請求,,指向一個JS文件,。但是這個GET請求的目的地未必是之前那個websocket server,可能是另外一臺web server,。然后他再去操控這個web server,,做好配合,,給一個看起來像http response的答復(fù)(實際是websocket frame),,里面放的是被修改過的js文件。然后cache server就會把這個js文件錯誤的緩存下來,,以后發(fā)給其他人,。

首先,client是誰,?是瀏覽器,。它在一個不很安全的環(huán)境中,很容易受到XSS或者流氓插件的攻擊,。假如我們的頁面遭到了xss,,使得攻擊者可以利用JS從受害者的頁面上發(fā)送任意字符串給服務(wù)器,如果沒有這個異或操作,,那么他就可以控制什么樣的二進(jìn)制數(shù)據(jù)出現(xiàn)在信道上,,從而實現(xiàn)上述攻擊。但是我還是覺得有點問題,。proxy server一般都會對目的地做嚴(yán)格的限制,,比如,sina的squid肯定不會幫new.163.com做cache,。那么既然你已經(jīng)控制了一個web server,,為什么不讓js直接這么做呢?那篇paper的名字叫《Talking to Yourself for Fun and Pro?t》,,有空我繼續(xù)看,。貌似是中國人寫的。

還有,,為什么要把message分成frame呢,? 因為HTTP協(xié)議有chunk功能,可以讓服務(wù)器一邊生數(shù)據(jù),,一邊發(fā),。而websocket協(xié)議也考慮到了這點。如果沒有framing功能,,那么我必須知道整個message的長度之后,,才能開始發(fā)送message的data。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多