0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

探究Redis網(wǎng)絡(luò)模型究竟有多強(qiáng)大(中)

jf_78858299 ? 來(lái)源:蟬沐風(fēng)的碼場(chǎng) ? 作者:蟬沐風(fēng) ? 2023-03-03 09:49 ? 次閱讀

3.2.1 創(chuàng)建socket

創(chuàng)建socket這一步和客戶(hù)端沒(méi)啥區(qū)別,不同的是這個(gè)socket我們稱(chēng)之為 等待連接socket(或監(jiān)聽(tīng)socket) 。

3.2.2 綁定端口號(hào)

bind()函數(shù)會(huì)將端口號(hào)寫(xiě)入上一步生成的監(jiān)聽(tīng)socket中,這樣一來(lái),監(jiān)聽(tīng)socket就完整保存了服務(wù)端的IP端口號(hào)

3.2.3 listen()的真正作用

listen(<Server描述符>, <最大連接數(shù)>);

很多小伙伴一定會(huì)對(duì)這個(gè)listen()有疑問(wèn),監(jiān)聽(tīng)socket都已經(jīng)創(chuàng)建完了,端口也已經(jīng)綁定完了,為什么還要多調(diào)用一個(gè)listen()呢?

我們剛說(shuō)過(guò)監(jiān)聽(tīng)socket和客戶(hù)端創(chuàng)建的socket沒(méi)什么區(qū)別,問(wèn)題就出在這個(gè)沒(méi)什么區(qū)別上。

socket被創(chuàng)建出來(lái)的時(shí)候都默認(rèn)是一個(gè) 主動(dòng)socket ,也就說(shuō),內(nèi)核會(huì)認(rèn)為這個(gè)socket之后某個(gè)時(shí)候會(huì)調(diào)用connect()主動(dòng)向別的設(shè)備發(fā)起連接。這個(gè)默認(rèn)對(duì)客戶(hù)端socket來(lái)說(shuō)很合理,但是監(jiān)聽(tīng)socket可不行,它只能等著客戶(hù)端連接自己,因此我們需要調(diào)用listen()將監(jiān)聽(tīng)socket從主動(dòng)設(shè)置為被動(dòng),明確告訴內(nèi)核:你要接受指向這個(gè)監(jiān)聽(tīng)socket的連接請(qǐng)求!

此外,listen()的第2個(gè)參數(shù)也大有來(lái)頭!監(jiān)聽(tīng)socket真正接受的應(yīng)該是已經(jīng)完整完成3次握手的客戶(hù)端,那么還沒(méi)完成的怎么辦?總得找個(gè)地方放著吧。于是內(nèi)核為每一個(gè)監(jiān)聽(tīng)socket都維護(hù)了兩個(gè)隊(duì)列:

  • 半連接隊(duì)列(未完成連接的隊(duì)列)

這里存放著暫未徹底完成3次握手的socket(為了防止半連接攻擊,這里存放的其實(shí)是占用內(nèi)存極小的request _sock,但是我們直接理解成socket就行了),這些socket的狀態(tài)稱(chēng)為SYN_RCVD

  • 已完成連接隊(duì)列

每個(gè)已完成TCP3次握手的客戶(hù)端連接對(duì)應(yīng)的socket就放在這里,這些socket的狀態(tài)為ESTABLISHED

文字太多了,有點(diǎn)干,上個(gè)圖!

圖片

listen與3次握手

解釋一下動(dòng)圖中的內(nèi)容:

  1. 客戶(hù)端調(diào)用connect()函數(shù),開(kāi)始3次握手,首先發(fā)送一個(gè)SYN X的報(bào)文(X是個(gè)數(shù)字,下同);
  2. 服務(wù)端收到來(lái)自客戶(hù)端的SYN,然后在監(jiān)聽(tīng)socket對(duì)應(yīng)的半連接隊(duì)列中創(chuàng)建一個(gè)新的socket,然后對(duì)客戶(hù)端發(fā)回響應(yīng)SYN Y,捎帶手對(duì)客戶(hù)端的報(bào)文給個(gè)ACK;
  3. 直到客戶(hù)端完成第3次握手,剛才新創(chuàng)建的socket就會(huì)被轉(zhuǎn)移到已連接隊(duì)列;
  4. 當(dāng)進(jìn)程調(diào)用accept()時(shí),會(huì)將已連接隊(duì)列頭部的socket返回;如果已連接隊(duì)列為空,那么進(jìn)程將被睡眠,直到已連接隊(duì)列中有新的socket,進(jìn)程才會(huì)被喚醒,將這個(gè)socket返回

第4步就是阻塞的本質(zhì)啊,朋友們!

3.3 答疑時(shí)間

Q1.隊(duì)列中的對(duì)象是socket嗎?

呃。。。乖,咱就把它當(dāng)成socket就好了,這樣容易理解,其實(shí)具體里邊存放的數(shù)據(jù)結(jié)構(gòu)是啥,我也很想知道,等我寫(xiě)完這篇文章,我研究完了告訴你。

Q2.accept()這個(gè)函數(shù)你還沒(méi)講是啥意思呢?

accept()函數(shù)是由服務(wù)端調(diào)用的,用于從已連接隊(duì)列中返回一個(gè)socket描述符;如果socket為阻塞式的,那么如果已連接隊(duì)列為空,accept()進(jìn)程就會(huì)被睡眠。BIO恰好就是這個(gè)樣子。

Q3.accept()為什么不直接把監(jiān)聽(tīng)socket返回呢?

因?yàn)樵陉?duì)列中的socket經(jīng)過(guò)3次握手過(guò)程的控制信息交換,socket的4元組的信息已經(jīng)完整了,用做socket完全沒(méi)問(wèn)題。

監(jiān)聽(tīng)socket就像一個(gè)客服,我們給客服打電話(huà),然后客服找到解決問(wèn)題的人,幫助我們和解決問(wèn)題的人建立聯(lián)系,如果直接把監(jiān)聽(tīng)socket返回,而不使用連接socket,就沒(méi)有socket繼續(xù)等待連接了。

哦對(duì)了,accept()返回的socket也有個(gè)名字,叫 連接socket

3.4 BIO究竟阻塞在哪里

拿Server端的BIO來(lái)說(shuō)明這個(gè)問(wèn)題,阻塞在了serverSocket.accept()以及bufferedReader.readLine()這兩個(gè)地方。有什么辦法可以證明阻塞嗎?

簡(jiǎn)單的很!你在serverSocket.accept(); 的下一行打個(gè)斷點(diǎn),然后debug模式運(yùn)行BIOServerSocket,在沒(méi)有客戶(hù)端連接的情況下,這個(gè)斷點(diǎn)絕不會(huì)觸發(fā)!同樣,在bufferedReader.readLine();下一行打個(gè)斷點(diǎn),在已連接的客戶(hù)端發(fā)送數(shù)據(jù)之前,這個(gè)斷點(diǎn)絕不會(huì)觸發(fā)!

readLine()的阻塞還帶來(lái)一個(gè)非常嚴(yán)重的問(wèn)題,如果已經(jīng)連接的客戶(hù)端一直不發(fā)送消息,readLine()進(jìn)程就會(huì)一直阻塞(處于睡眠狀態(tài)),結(jié)果就是代碼不會(huì)再次運(yùn)行到accept(),這個(gè)ServerSocket沒(méi)辦法接受新的客戶(hù)端連接。

解決這個(gè)問(wèn)題的核心就是別讓代碼卡在readLine()就可以了,我們可以使用新的線(xiàn)程來(lái)readLine(),這樣代碼就不會(huì)阻塞在readLine()上了。

3.5 改造BIO

改造之后的BIO長(zhǎng)這樣,這下子服務(wù)端就可以隨時(shí)接受客戶(hù)端的連接了,至于啥時(shí)候能read到客戶(hù)端的數(shù)據(jù),那就讓線(xiàn)程去處理這個(gè)事情吧。

public class BIOServerSocketWithThread {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket(8099);
            System.out.println("啟動(dòng)服務(wù):監(jiān)聽(tīng)端口:8099");
            // 等待客戶(hù)端的連接過(guò)來(lái),如果沒(méi)有連接過(guò)來(lái),就會(huì)阻塞
            while (true) {
                // 表示阻塞等待監(jiān)聽(tīng)一個(gè)客戶(hù)端連接,返回的socket表示連接的客戶(hù)端信息
                Socket socket = serverSocket.accept(); //連接阻塞
                System.out.println("客戶(hù)端:" + socket.getPort());
                // 表示獲取客戶(hù)端的請(qǐng)求報(bào)文
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            BufferedReader bufferedReader = new BufferedReader(
                                    new InputStreamReader(socket.getInputStream())
                            );
                            String clientStr = bufferedReader.readLine();
                            System.out.println("收到客戶(hù)端發(fā)送的消息:" + clientStr);

                            BufferedWriter bufferedWriter = new BufferedWriter(
                                    new OutputStreamWriter(socket.getOutputStream())
                            );
                            bufferedWriter.write("ok\\n");
                            bufferedWriter.flush();
                        } catch (Exception e) {
                            //...
                        }

                    }
                }).start();
            }
        } catch (IOException e) {
            // 錯(cuò)誤處理
        } finally {
            // 其他處理
        }
    }
}

事情的順利進(jìn)展不禁讓我們飄飄然,我們居然是使用高階的多線(xiàn)程技術(shù)解決了BIO的阻塞問(wèn)題,雖然目前每個(gè)客戶(hù)端都需要一個(gè)單獨(dú)的線(xiàn)程來(lái)處理,但accept()總歸不會(huì)被readLine()卡死了。

圖片

BIO改造之后

所以我們改造完之后的程序是不是就是非阻塞IO了呢?

想多了。。。我們只是用了點(diǎn)奇技淫巧罷了,改造完的代碼在系統(tǒng)調(diào)用層面該阻塞的地方還是阻塞,說(shuō)白了,Java提供的API完全受限于操作系統(tǒng)提供的系統(tǒng)調(diào)用,在Java語(yǔ)言級(jí)別沒(méi)能力改變底層BIO的事實(shí)!

Java沒(méi)這個(gè)能力!

3.6 掀開(kāi)BIO的遮羞布

接下來(lái)帶大家看一下改造之后的BIO代碼在底層都調(diào)用了哪一些系統(tǒng)調(diào)用,讓我們?cè)诘讓由蠈?duì)上文的內(nèi)容加深一下理解。

給大家打個(gè)氣,接下來(lái)的內(nèi)容其實(shí)非常好理解,大家跟著文章一步步地走,一定能看得懂,如果自己動(dòng)手操作一遍,那就更好了。

對(duì)了,我下來(lái)使用的JDK版本是JDK8。

straceLinux上的一個(gè)程序,該程序可以追蹤并記錄參數(shù)后邊運(yùn)行的進(jìn)程對(duì)內(nèi)核進(jìn)行了哪些系統(tǒng)調(diào)用。

strace -ff -o out java BIOServerSocketWithThread

其中:

  • -o:

將系統(tǒng)調(diào)用的追蹤信息輸出到out文件中,不加這個(gè)參數(shù),默認(rèn)會(huì)輸出到標(biāo)準(zhǔn)錯(cuò)誤stderr

  • -ff

如果指定了-o選項(xiàng),strace會(huì)追蹤和程序相關(guān)的每一個(gè)進(jìn)程的系統(tǒng)調(diào)用,并將信息輸出到以進(jìn)程id為后綴的out文件中。舉個(gè)例子,比如BIOServerSocketWithThread程序運(yùn)行過(guò)程中有一個(gè)ID為30792的進(jìn)程,那么該進(jìn)程的系統(tǒng)調(diào)用日志會(huì)輸出到out.30792這個(gè)文件中。

我們運(yùn)行strace命令之后,生成了很多個(gè)out文件。

圖片

這么多進(jìn)程怎么知道哪個(gè)是我們需要追蹤的呢?我就挑了一個(gè)容量最大的文件進(jìn)行查看,也就是out.30792,事實(shí)上,這個(gè)文件也恰好是我們需要的,截取一下里邊的內(nèi)容給大家看一下。

圖片

可以看到圖中的有非常多的行,說(shuō)明我們寫(xiě)的這么幾行代碼其實(shí)默默調(diào)用了非常多的系統(tǒng)調(diào)用,拋開(kāi)細(xì)枝末節(jié),看一下上圖中我重點(diǎn)標(biāo)注的系統(tǒng)調(diào)用,是不是就是上文中我解釋過(guò)的函數(shù)?我再詳細(xì)解釋一下每一步,大家聯(lián)系上文,會(huì)對(duì)BIO的底層理解的更加通透。

  1. 生成監(jiān)聽(tīng)socket,并返回socket描述符7,接下來(lái)對(duì)socket進(jìn)行操作的函數(shù)都會(huì)有一個(gè)參數(shù)為7
  2. 8099端口綁定到監(jiān)聽(tīng)socket,bind的第一個(gè)參數(shù)就是7,說(shuō)明就是對(duì)監(jiān)聽(tīng)socket進(jìn)行的操作;
  3. listen()將監(jiān)聽(tīng)socket(參數(shù)為7)設(shè)置為被動(dòng)接受連接的socket,并且將隊(duì)列的長(zhǎng)度設(shè)置為50;
  4. 實(shí)際上就是System.out.println("啟動(dòng)服務(wù):監(jiān)聽(tīng)端口:8099");這一句的系統(tǒng)調(diào)用,只不過(guò)中文被編碼了,所以我特意把:8099圈出來(lái)證明一下;

額外說(shuō)兩點(diǎn):

其一:可以看到,這么一句簡(jiǎn)單的打印輸出在底層實(shí)際調(diào)用了兩次write系統(tǒng)調(diào)用,這就是為什么不推薦在生產(chǎn)環(huán)境下使用打印語(yǔ)句的原因,多少會(huì)影響系統(tǒng)性能;

其二:write()的第一個(gè)參數(shù)為1,也是文件描述符,表示的是標(biāo)準(zhǔn)輸出stdout

  1. 系統(tǒng)調(diào)用阻塞在了poll()函數(shù),怎么看出來(lái)的阻塞?out文件的每一行運(yùn)行完畢都會(huì)有一個(gè) = 返回值,而poll()目前沒(méi)有返回值,因此阻塞了。實(shí)際上poll()系統(tǒng)調(diào)用對(duì)應(yīng)的Java語(yǔ)句就是serverSocket.accept();

不對(duì)?。繛槭裁吹讓诱{(diào)用的不是accept()而是poll()?poll()應(yīng)該是多路復(fù)用才是啊。在JDK4之前,底層確實(shí)直接調(diào)用的是accept(),但是之后的JDK對(duì)這一步進(jìn)行了優(yōu)化,除了調(diào)用accept(),還加上了poll()。poll()的細(xì)節(jié)我們下文再說(shuō),這里可以起碼證明了poll()函數(shù)依然是阻塞的,所以整個(gè)BIO的阻塞邏輯沒(méi)有改變。

接下來(lái)我們起一個(gè)客戶(hù)端對(duì)程序發(fā)起連接,直接用Linux上的nc程序即可,比較簡(jiǎn)單:

nc localhost 8099

發(fā)起連接之后(但并未主動(dòng)發(fā)送信息),out.30792的內(nèi)容發(fā)生了變化:

圖片

  1. poll()函數(shù)結(jié)束阻塞,程序接著調(diào)用accept()函數(shù)返回一個(gè)連接socket,該socket的描述符為8;
  2. 就是System.out.println("客戶(hù)端:" + socket.getPort());的底層調(diào)用;
  3. 底層使用clone()創(chuàng)造了一個(gè)新進(jìn)程去處理連接socket,該進(jìn)程的pid為31168,因此JDK8的線(xiàn)程在底層其實(shí)就是輕量級(jí)進(jìn)程;
  4. 回到poll()函數(shù)繼續(xù)阻塞等待新客戶(hù)端連接。

由于創(chuàng)建了一個(gè)新的進(jìn)程,因此在目錄下對(duì)多出一個(gè)out.31168的文件,我們看一下該文件的內(nèi)容:

圖片

發(fā)現(xiàn)子進(jìn)程阻塞在了recvfrom()這個(gè)系統(tǒng)調(diào)用上,對(duì)應(yīng)的Java源碼就是bufferedReader.readLine();,直到客戶(hù)端主動(dòng)給服務(wù)端發(fā)送消息,阻塞才會(huì)結(jié)束。

3.7 BIO總結(jié)

到此為止,我們就通過(guò)底層的系統(tǒng)調(diào)用證明了BIO在accept()以及readLine()上的阻塞。最后用一張圖來(lái)結(jié)束B(niǎo)IO之旅。

圖片

BIO模型

BIO之所以是BIO,是因?yàn)橄到y(tǒng)底層調(diào)用是阻塞的,上圖中的進(jìn)程調(diào)用recv,其系統(tǒng)調(diào)用直到數(shù)據(jù)包準(zhǔn)備好并且被復(fù)制到應(yīng)用程序的緩沖區(qū)或者發(fā)生錯(cuò)誤為止才會(huì)返回,在此整個(gè)期間,進(jìn)程是被阻塞的,啥也干不了。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Socket
    +關(guān)注

    關(guān)注

    0

    文章

    188

    瀏覽量

    34611
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4264

    瀏覽量

    62254
  • Redis
    +關(guān)注

    關(guān)注

    0

    文章

    370

    瀏覽量

    10815
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    小米5的那顆核心,究竟有多強(qiáng)

    小米5的那顆核心,究竟有多強(qiáng)自從小米在2011年崛起后,高通的驍龍系列處理器就逐漸成為了市面上旗艦手機(jī)的主流處理器,從蝎子核心到環(huán)蛇核心,再到現(xiàn)在的驍龍810,高通一直在進(jìn)步。那么,這款驍龍810
    發(fā)表于 06-01 19:35

    Redis Stream應(yīng)用案例

    今天介紹的主角——Redis Stream,本身就是起源于IRC中一個(gè)用戶(hù)的idea。IRC的模型如下,在某個(gè)IRC頻道的用戶(hù),既可以向所有的其他用戶(hù)自由的發(fā)送消息,也可以接收其他所有用戶(hù)發(fā)送
    發(fā)表于 06-26 17:15

    液晶PC與液晶電視究竟有什么區(qū)別?

    為什么要選擇液晶?液晶PC與液晶電視究竟有什么區(qū)別?如何選擇液晶PC與液晶電視?
    發(fā)表于 06-07 06:13

    請(qǐng)問(wèn)一下RFID與NFC究竟有什么關(guān)系?

    RFID與NFC究竟有什么關(guān)系?
    發(fā)表于 06-15 07:06

    面向列的HBase存儲(chǔ)結(jié)構(gòu)究竟有什么樣的不同之處呢?

    HBase是什么?HBase的存儲(chǔ)結(jié)構(gòu)究竟是怎樣的呢?面向列的HBase存儲(chǔ)結(jié)構(gòu)究竟有什么樣的不同之處呢?
    發(fā)表于 06-16 06:52

    請(qǐng)問(wèn)一下芯片制造究竟有多難?

    請(qǐng)問(wèn)一下芯片制造究竟有多難?
    發(fā)表于 06-18 06:53

    PCI-E4.0究竟有什么優(yōu)勢(shì)?

    PCI-E4.0究竟有什么優(yōu)勢(shì)?PCI-E究竟指的是什么呢?
    發(fā)表于 06-18 06:54

    內(nèi)存時(shí)序究竟有多重要呢?究竟該如何去選擇內(nèi)存條呢?

    內(nèi)存時(shí)序究竟有多重要呢?究竟該如何去選擇內(nèi)存條呢?DDR內(nèi)存時(shí)序是高一些好還是低一些好?
    發(fā)表于 06-18 08:20

    定時(shí)器中斷類(lèi)型探究 精選資料分享

     一直在用的stm32定時(shí)器的中斷都是TIM_IT_Update更新中斷,也沒(méi)問(wèn)為什么,直到碰到有人使用TIM_IT_CC1斷,才想到這定時(shí)器的中斷類(lèi)型究竟有什么區(qū)別,都怪當(dāng)時(shí)學(xué)習(xí)stm32的時(shí)候
    發(fā)表于 08-13 06:28

    OpenPLC開(kāi)源工業(yè)控制器究竟有何用處

    OpenPLC開(kāi)源工業(yè)控制器有哪些優(yōu)點(diǎn)?OpenPLC開(kāi)源工業(yè)控制器有哪些功能?OpenPLC開(kāi)源工業(yè)控制器究竟有何用處?
    發(fā)表于 09-02 07:42

    華為榮耀Magic今日發(fā)布:“未來(lái)”手機(jī)究竟有多強(qiáng)?

    華為榮耀即將在12月16日發(fā)布最新的“未來(lái)”手機(jī)magic,關(guān)于這款手機(jī)的爆料在今日已經(jīng)鋪天蓋地,今天,小編將為大家整理一下,給大家一個(gè)榮耀Magic的基本判斷,看看這款旗艦究竟有多強(qiáng)力!
    發(fā)表于 12-16 09:34 ?3310次閱讀

    ibm的2nm芯片究竟有多強(qiáng) 2nm芯片對(duì)續(xù)航的影響

    全球首顆2nm芯片的問(wèn)世對(duì)半導(dǎo)體行業(yè)影響重大,IBM通過(guò)與AMD、三星及GlobalFoundries等多家公司的合作,最終抵達(dá)了2nm芯片制程的節(jié)點(diǎn),推出了2nm的測(cè)試芯片。那么這顆芯片究竟有多強(qiáng)呢?它對(duì)續(xù)航的影響又有多大呢?
    的頭像 發(fā)表于 06-23 09:35 ?2038次閱讀

    Molex莫仕連接器的功能究竟有多強(qiáng)大?看他們的行業(yè)應(yīng)用你就知道了!

    KOYUELEC光與電子:Molex莫仕連接器的功能究竟有多強(qiáng)大?看他們的行業(yè)應(yīng)用你就知道了!
    的頭像 發(fā)表于 12-31 12:30 ?1w次閱讀

    探究Redis網(wǎng)絡(luò)模型究竟有多強(qiáng)大(上)

    本文將從BIO開(kāi)始介紹,經(jīng)過(guò)NIO、多路復(fù)用,最終說(shuō)回Redis的Reactor模型,力求詳盡。本文與其他文章的不同點(diǎn)主要在于:
    的頭像 發(fā)表于 03-03 09:46 ?413次閱讀
    <b class='flag-5'>探究</b><b class='flag-5'>Redis</b><b class='flag-5'>網(wǎng)絡(luò)</b><b class='flag-5'>模型</b><b class='flag-5'>究竟有多強(qiáng)大</b>(上)

    探究Redis網(wǎng)絡(luò)模型究竟有多強(qiáng)大(下)

    接下來(lái)的非阻塞IO我們只抓主要矛盾,其余參考BIO即可。 如果你看過(guò)其他介紹非阻塞IO的文
    的頭像 發(fā)表于 03-03 09:50 ?371次閱讀
    <b class='flag-5'>探究</b><b class='flag-5'>Redis</b><b class='flag-5'>網(wǎng)絡(luò)</b><b class='flag-5'>模型</b><b class='flag-5'>究竟有多強(qiáng)大</b>(下)