小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

【Swoole系列5.2】TCP數(shù)據(jù)邊界(粘包)

 硬核項(xiàng)目經(jīng)理 2022-09-15 發(fā)布于湖南

TCP數(shù)據(jù)邊界(粘包)

說到 TCP 數(shù)據(jù)邊界問題,可能很多人還反應(yīng)不過來,這是個(gè)啥東西?不過它還有另外一個(gè)更出名的代名詞,叫做 粘包 。如果以正式的說法來說的話,其實(shí)并不存在所謂的 粘包 問題 ,或者說,這個(gè)東西本身不能算做是一個(gè)問題。這個(gè)我們最后再說,先來看看到底啥是數(shù)據(jù)邊界。

什么是數(shù)據(jù)邊界問題

一般來說,我們在 Swoole 中運(yùn)行之前寫過的 TCP 相關(guān)的測試代碼,都沒有什么問題。那是因?yàn)槲覀冎皇亲约簡螜C(jī)測試,沒什么并發(fā)流量,如果一旦踫到高并發(fā),那么就有可能會(huì)出現(xiàn) TCP 數(shù)據(jù)包的邊界問題。

在學(xué)習(xí)網(wǎng)絡(luò)相關(guān)的知識(shí)時(shí),我們知道 TCP 是解決了 UDP 的順序和丟包這兩個(gè)重要的問題,它是會(huì)相互建立連接的,但是連接建立后,TCP 會(huì)以流式的方式進(jìn)行數(shù)據(jù)傳輸。還記得我們使用 Socket 建立連接時(shí)使用的那個(gè)區(qū)分 TCP 和 UDP 的常量嗎?TCP 使用的就是 SOCK_STREAM ,是不是從名字就看出來了,STREAM 代表的就是流。

然而,這種流式數(shù)據(jù)包的傳輸是沒有邊界的。怎么說呢?直接用例子來說話。

// 服務(wù)端
Swoole\Coroutine\run (function () {
    $server = new Swoole\Coroutine\Server('0.0.0.0'9501false);
    $server->handle(function(Swoole\Coroutine\Server\Connection $conn){
        while($data = $conn->recv()){
            echo $data," EOF ====== ", PHP_EOL;
        }
    });

    $server->start();
});

服務(wù)端就是我們之前經(jīng)常用的那個(gè) TCP 服務(wù)端,在這里我們直接循環(huán)掛起接收 recv() 方法獲得的數(shù)據(jù),然后在里面打印出來,每次打印后面都加上一段分隔描述的字符串。

<?php
\Swoole\Coroutine\run(function () {
    $client = new \Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
    if (!$client->connect('127.0.0.1'95010.5)) {
        echo "connect failed. Error: {$client->errCode}\n";
    }

    for($i = 1;$i<=10;$i++){
        $client->send("hello world {$i},");
    }
    for($i = 11;$i<=20;$i++){
        $client->send("hello world {$i},");
    }
    co::sleep(1);
    for($i = 21;$i<=30;$i++){
        $client->send("hello world {$i},");
        $client->send("\r\n");
    }
});

客戶端也很簡單,三個(gè)循環(huán),分別使用 send() 發(fā)送數(shù)據(jù),然后第二個(gè)個(gè)循環(huán)之后 sleep() 一下。

到這里,都沒什么問題吧?猜猜我們運(yùn)行客戶端之后,服務(wù)端會(huì)輸出什么?

hello world 1,hello world 2,hello world 3,hello world 4,hello world 5,hello world 6,hello world 7,hello world 8,hello world 9,hello world 10,hello world 11,hello world 12,hello world 13,hello world 14,hello world 15,hello world 16,hello world 17,hello world 18,hello world 19,hello world 20,
 EOF ======
hello world 21,
hello world 22
hello world 23,
hello world 24,
hello world 25
hello world 26,
hello world 27
hello world 28,
hello world 29,
hello world 30
 EOF ======

注意,我們是三個(gè)循環(huán),每個(gè)循環(huán)里至少有一個(gè) send() ,但是注意服務(wù)端,只輸出了兩個(gè)帶 EOF+等號(hào) 的 recv() 信息。

看明白了嗎?send() 和 recv() 并不是匹配的一對,并不是說客戶端發(fā)一次 send() ,服務(wù)端就要接收一次 recv() 。真實(shí)的情況是,如果沒有休息的那一秒的話,我們在這邊一直 send() 到最后,服務(wù)端才會(huì)輸出一條接收到的數(shù)據(jù)。因?yàn)?,send() 發(fā)送的是數(shù)據(jù)流,在未超時(shí)中斷的情況下,它會(huì)持續(xù)發(fā)送,就像高并發(fā)的時(shí)候,數(shù)據(jù)不斷傳送,服務(wù)端最后接到的數(shù)據(jù)可能就是有問題的數(shù)據(jù)。

這就是最直觀的一種數(shù)據(jù)包邊界問題的展示。如果是這種情況,你會(huì)想到什么解決方案呢?我們可以自己定義一個(gè)分隔字符,然后在服務(wù)端截開不同的數(shù)據(jù),就像 HTTP 的請求一樣,HTTP 的描述、頭、Body信息就是以換行符分隔的。HTTP 本身就是一種 TCP 的高層服務(wù),它當(dāng)然也會(huì)有這個(gè)問題,只不過服務(wù)器程序和瀏覽器已經(jīng)幫我們解決了。

上面這個(gè)問題可以再細(xì)分成一個(gè)操作,那就是 分包 操作。另外還有一種情況,比如說一些大文件的傳輸,有可能一次傳輸只是傳來了一部分,這時(shí)候我們需要將之前先接收到的數(shù)據(jù)緩存起來,然后等待后續(xù)的數(shù)據(jù)繼續(xù)發(fā)送過來之后合并在一起成為一個(gè)完整的數(shù)據(jù)包。這種情況叫做 合包 。

接下來,我們看看 Swoole 中處理這種數(shù)據(jù)邊界問題的方式。

解決邊界問題

在 Swoole 中,有兩種解決數(shù)據(jù)邊界問題的試試,一個(gè)是通過 EOF 結(jié)束符協(xié)議,另一個(gè)是通過固定包頭+包體協(xié)議的試。我們今天就用 EOF 結(jié)束符協(xié)議來演示一下。

這個(gè) EOF 結(jié)束符,其實(shí)就是我們在上面講到的方法,定義一個(gè)結(jié)束符號(hào),然后讓框架自動(dòng)幫我們完成 分/合包 操作。

// 服務(wù)端
    $server->set(array(
        // 'open_eof_split' => true,
        'open_eof_check'=>true,
        'package_eof' => "\r\n",
    ));

// 客戶端
    $client->set(array(
        // 'open_eof_split' => true,
        'open_eof_check'=>true,
        'package_eof' => "\r\n",
    ));

    for($i = 1;$i<=10;$i++){
        $client->send("hello world {$i},");
    }
    co::sleep(1);
    for($i = 11;$i<=20;$i++){
        $client->send("hello world {$i},");
    }
    $client->send("\r\n");
    co::sleep(1);
    for($i = 21;$i<=30;$i++){
        $client->send("hello world {$i},");
        $client->send("\r\n");
    }

在客戶端和服務(wù)端,我們都增加了一個(gè) set() ,然后設(shè)置了一些屬性,open_eof_check 表示開啟 EOF 結(jié)束符檢查,package_eof 表示指定的結(jié)束符號(hào),這里我們使用的是大部分情況下通用的 換行 標(biāo)記,你可以試著換成其它符號(hào)。

然后,客戶端還有一些小變化。我們在第一和第二個(gè)循環(huán)之間加了一個(gè) sleep() ,如果是之前的情況,1-10 和 11-20 是會(huì)因?yàn)闀r(shí)間問題分成兩個(gè) recv() 的,但現(xiàn)在,我們在加了 sleep() 之后,在第二個(gè)循環(huán)結(jié)束的時(shí)候才再發(fā)送了 換行 標(biāo)記??梢韵认胂耄@里輸出的結(jié)果會(huì)是什么樣子的。

第三個(gè)循環(huán)還是老樣子,但是在循環(huán)中,有兩個(gè) send() ,第二個(gè) send() 每次循環(huán)都會(huì)發(fā)送一個(gè) 換行 標(biāo)記。

試試運(yùn)行的結(jié)果吧!

hello world 1,hello world 2,hello world 3,hello world 4,hello world 5,hello world 6,hello world 7,hello world 8,hello world 9,hello world 10,hello world 11,hello world 12,hello world 13,hello world 14,hello world 15,hello world 16,hello world 17,hello world 18,hello world 19,hello world 20,
 EOF ======
hello world 21,
 EOF ======
hello world 22,
 EOF ======
hello world 23,
 EOF ======
hello world 24,
 EOF ======
hello world 25,
 EOF ======
hello world 26,
 EOF ======
hello world 27
 EOF ======
hello world 28,
 EOF ======
hello world 29
 EOF ======
hello world 30,
 EOF ======

有意思嗎?1-20 還是在一起輸出了,看著和最上面的測試代碼效果一樣呀。不不不,在這段測試代碼中,我們中間 sleep() 了,然后服務(wù)端在第一個(gè)循環(huán)數(shù)據(jù)接收完之后,沒有看到 換行 標(biāo)記,于是等待了 1 秒,接著繼續(xù)接收 11-20 的數(shù)據(jù)之后,看到了 換行 標(biāo)記,才結(jié)束了這次請求接收。這是一個(gè)合包的過程。

第三段循環(huán)中,每次循環(huán)都有一個(gè)結(jié)束標(biāo)記,于是,每一段循環(huán)都會(huì)在服務(wù)端獲得一個(gè) recv() 數(shù)據(jù),就像我們把字符串分割開了一樣。很明顯,這是一個(gè) 分包 過程。

EOF 這種情況比較好理解,另外一種處理 固定包頭 的方式則是另外一種概念。它的特點(diǎn)是一個(gè)數(shù)據(jù)包總是由包頭 + 包體 2 部分組成。包頭由一個(gè)字段指定了包體或整個(gè)包的長度,長度一般是使用 2 字節(jié) /4 字節(jié)整數(shù)來表示。服務(wù)器收到包頭后,可以根據(jù)長度值來精確控制需要再接收多少數(shù)據(jù)就是完整的數(shù)據(jù)包。

關(guān)于固定包頭的處理方式的演示我就不展示了,因?yàn)槲乙矝]怎么接觸過,或者說并沒有太深入的學(xué)習(xí)過這一方面的內(nèi)容,大家有興趣的可以自行查閱相關(guān)資料。

真的有“粘包問題”嗎?

其實(shí),粘包,或者說數(shù)據(jù)邊界,本身就是 TCP 協(xié)議在設(shè)計(jì)時(shí)的特點(diǎn)。它本身并不構(gòu)成問題,只是我們在使用的時(shí)候,需要知道多少數(shù)據(jù)應(yīng)該是一個(gè)完整的數(shù)據(jù)包,因此,這是因?yàn)槲覀兊男枨蠖霈F(xiàn)的問題。說得有點(diǎn)繞,換個(gè)角度說,它不是一個(gè)技術(shù)問題,而是一個(gè)需求問題。

好吧,如果還不理解的話,下面參考文檔中第二條鏈接中,知乎的各路大神們早就對這個(gè)問題展開了各種探討,大家可以移步前去觀戰(zhàn)。

總結(jié)

要真是細(xì)說起來,這一塊的知識(shí)其實(shí)是網(wǎng)絡(luò)編程相關(guān)的內(nèi)容,但是,如果你要是在簡歷中提到了 Swoole 的話,那么這個(gè)問題的面試出題率還是相當(dāng)高的,畢竟太出名了。希望在今天的學(xué)習(xí)之后,大家能夠更加重視編程四大件的學(xué)習(xí),還記得是哪四個(gè)嗎?操作系統(tǒng)、算法與數(shù)據(jù)結(jié)構(gòu)、計(jì)算機(jī)網(wǎng)絡(luò)、設(shè)計(jì)模式。如果還有一個(gè)的話,可以再把數(shù)據(jù)庫原理加上。要知道,我們之前學(xué)習(xí)的進(jìn)程、線程、協(xié)程全是操作系統(tǒng)這個(gè)學(xué)科中的知識(shí),而TCP、UDP、SOCKET這些又全是計(jì)算機(jī)網(wǎng)絡(luò)中的知識(shí)。學(xué)海無涯呀,各位大佬們好好加油吧。

測試代碼:

https://github.com/zhangyue0503/swoole/blob/main/5.%E5%85%B6%E5%AE%83/source/5.2TCP%E6%95%B0%E6%8D%AE%E8%BE%B9%E7%95%8C%EF%BC%88%E7%B2%98%E5%8C%85%E9%97%AE%E9%A2%98%EF%BC%89.php

參考文檔:

https://wiki./#/learn?id=tcp數(shù)據(jù)包邊界問題

https://www.zhihu.com/question/20210025

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多