日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人工智能 > ChatGpt >内容正文

ChatGpt

java基础巩固-宇宙第一AiYWM:为了维持生计,四大基础之OS_Part_2整起~IO们那些事【包括五种IO模型:(BIO、NIO、IO多路复用、信号驱动、AIO);零拷贝、事件处理及并发等模型】

發(fā)布時(shí)間:2024/3/13 ChatGpt 43 豆豆

PART0.前情提要:

  • 通常用戶進(jìn)程的一個(gè)完整的IO分為兩個(gè)階段(IO有內(nèi)存IO、網(wǎng)絡(luò)IO和磁盤IO三種,通常我們說(shuō)的IO指的是后兩者!):【操作系統(tǒng)和驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,兩者不能使用指針傳遞數(shù)據(jù),因?yàn)長(zhǎng)inux使用的虛擬內(nèi)存機(jī)制,必須通過(guò)系統(tǒng)調(diào)用請(qǐng)求內(nèi)核來(lái)完成IO動(dòng)作。】
    • 內(nèi)存IO:
    • 磁盤IO:
      • 和磁盤打交道就是費(fèi)事,但也沒(méi)辦法,咱們?nèi)比萘垦健T蹅冊(cè)?Java 中 IO 流分為輸入流和輸出流,根據(jù)數(shù)據(jù)的處理方式又分為字節(jié)流和字符流。根據(jù)擒賊先擒王的手法,咱們把Java IO 流的 40 多個(gè)類的如下 4 個(gè)抽象類基類先抓住,【Java IO 流的 40 多個(gè)類都是從如下 4 個(gè)抽象類基類中派生出來(lái)的】。
        • InputStream/Reader: 所有的輸入流的基類,InputStream是字節(jié)輸入流【用于從源頭(通常是文件)讀取數(shù)據(jù)(字節(jié)信息)到內(nèi)存中,java.io.InputStream抽象類是所有字節(jié)輸入流的父類。】,Reader是字符輸入流【不管是文件讀寫還是網(wǎng)絡(luò)發(fā)送接收,信息的最小存儲(chǔ)單元都是字節(jié)。 那為什么 I/O 流操作要分為字節(jié)流操作和字符流操作呢?原因主要是有時(shí)候如果我們不知道編碼類型就很容易出現(xiàn)亂碼問(wèn)題,I/O 流就干脆提供了一個(gè)直接操作字符的接口,方便我們平時(shí)對(duì)字符進(jìn)行流操作。如果音頻文件、圖片等媒體文件用字節(jié)流比較好,如果涉及到字符的話使用字符流比較好。字符流默認(rèn)采用的是 Unicode 編碼,我們可以通過(guò)構(gòu)造方法自定義編碼。utf8 :英文占 1 字節(jié),中文占 3 字節(jié),unicode:任何字符都占 2 個(gè)字節(jié),gbk:英文占 1 字節(jié),中文占 2 字節(jié)
          • InputStream
            • InputStream 常用方法 :
              • 通過(guò) readAllBytes() 讀取輸入流所有字節(jié)并將其直接賦值給一個(gè) String 對(duì)象// 新建一個(gè) BufferedInputStream 對(duì)象 BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt")); // 讀取文件的內(nèi)容并復(fù)制到 String 對(duì)象中 String result = new String(bufferedInputStream.readAllBytes()); System.out.println(result);
            • 一般我們是不會(huì)直接單獨(dú)使用 FileInputStream ,通常會(huì)配合 BufferedInputStream字節(jié)緩沖輸入流
            • DataInputStream 用于讀取指定類型數(shù)據(jù),不能單獨(dú)使用,必須結(jié)合 FileInputStream
            • ObjectInputStream 用于從輸入流中讀取 Java 對(duì)象(反序列化,用于序列化和反序列化的類必須實(shí)現(xiàn) Serializable 接口,對(duì)象中如果有屬性不想被序列化,使用 transient 修飾),ObjectOutputStream 用于將對(duì)象寫入到輸出流(序列化)
          • Reader:Reader 用于讀取文本, InputStream 用于讀取原始字節(jié)。
            • Reader常用方法:
            • InputStreamReader 是字節(jié)流轉(zhuǎn)換為字符流的橋梁,其子類 FileReader 是基于該基礎(chǔ)上的封裝,可以直接操作字符文件
        • OutputStream/Writer: 所有輸出流的基類,OutputStream是字節(jié)輸出流【OutputStream用于將數(shù)據(jù)(字節(jié)信息)寫入到目的地(通常是文件),java.io.OutputStream抽象類是所有字節(jié)輸出流的父類。】,Writer是字符輸出流
          • FileOutputStream
            • FileOutputStream 是最常用的字節(jié)輸出流對(duì)象,可直接指定文件路徑,可以直接輸出單字節(jié)數(shù)據(jù),也可以輸出指定的字節(jié)數(shù)組。FileOutputStream 通常也會(huì)配合 BufferedOutputStream字節(jié)緩沖輸出流
            • DataOutputStream 用于寫入指定類型數(shù)據(jù),不能單獨(dú)使用,必須結(jié)合 FileOutputStream
          • Writer:
            • OutputStreamWriter 是字符流轉(zhuǎn)換為字節(jié)流的橋梁,其子類 FileWriter 是基于該基礎(chǔ)上的封裝,可以直接將字符寫入到文件
        • 字節(jié)緩沖流:IO 操作是很消耗性能的,所以緩沖流用來(lái)將數(shù)據(jù)加載至緩沖區(qū),一次性讀取/寫入多個(gè)字節(jié),從而避免頻繁的 IO 操作,提高流的傳輸效率
          • 字節(jié)緩沖流采用了裝飾器模式 來(lái)增強(qiáng) InputStream 和OutputStream子類對(duì)象的功能。【字節(jié)流和字節(jié)緩沖流的性能差別主要體現(xiàn)在我們使用兩者的時(shí)候都是調(diào)用 write(int b) 和 read() 這兩個(gè)一次只讀取一個(gè)字節(jié)的方法的時(shí)候。由于字節(jié)緩沖流內(nèi)部有緩沖區(qū)(字節(jié)數(shù)組),因此,字節(jié)緩沖流會(huì)先將讀取到的字節(jié)存放在緩存區(qū),大幅減少 IO 次數(shù),提高讀取效率。】
          • 如果是調(diào)用 read(byte b[]) 和 write(byte b[], int off, int len) 這兩個(gè)寫入一個(gè)字節(jié)數(shù)組的方法的話,只要字節(jié)數(shù)組的大小合適,兩者的性能差距其實(shí)不大,基本可以忽略。
          • BufferedInputStream(字節(jié)緩沖輸入流):
            • BufferedInputStream 內(nèi)部維護(hù)了一個(gè)緩沖區(qū),這個(gè)緩沖區(qū)實(shí)際就是一個(gè)字節(jié)數(shù)組
          • BufferedOutputStream(字節(jié)緩沖輸入流):
            • BufferedOutputStream 內(nèi)部也維護(hù)了一個(gè)緩沖區(qū),并且,這個(gè)緩存區(qū)的大小也是 8192 字節(jié)
        • 字符緩沖流:
          • BufferedReader (字符緩沖輸入流)和 BufferedWriter(字符緩沖輸出流)類似于 BufferedInputStream(字節(jié)緩沖輸入流)和BufferedOutputStream(字節(jié)緩沖輸入流),內(nèi)部都維護(hù)了一個(gè)字節(jié)數(shù)組作為緩沖區(qū)。不過(guò),前者主要是用來(lái)操作字符信息
        • 打印流:
          • System.out.println(“Hello!”);System.out 實(shí)際是用于獲取一個(gè) PrintStream 對(duì)象,print方法實(shí)際調(diào)用的是 PrintStream 對(duì)象的 write 方法。PrintStream 屬于字節(jié)打印流,與之對(duì)應(yīng)的是 PrintWriter (字符打印流)。PrintStream 是 OutputStream 的子類,PrintWriter 是 Writer 的子類。
        • 隨機(jī)訪問(wèn)流:隨機(jī)訪問(wèn)流指的是 支持隨意跳轉(zhuǎn)到文件的任意位置進(jìn)行讀寫的 RandomAccessFile
          • 文件內(nèi)容指的是文件中實(shí)際保存的數(shù)據(jù),元數(shù)據(jù)則是用來(lái)描述文件屬性比如文件的大小信息、創(chuàng)建和修改時(shí)間。
          • RandomAccessFile 中 有一個(gè)文件指針 用來(lái)表示 下一個(gè)將要被寫入或者讀取的字節(jié)所處的位置。我們可以通過(guò) RandomAccessFile 的 seek(long pos) 方法來(lái)設(shè)置文件指針的偏移量(距文件開(kāi)頭 pos 個(gè)字節(jié)處)。如果想要獲取文件指針當(dāng)前的位置的話,可以使用 getFilePointer() 方法。
          • RandomAccessFile 比較常見(jiàn)的一個(gè)應(yīng)用就是實(shí)現(xiàn)大文件的 斷點(diǎn)續(xù)傳 。何謂斷點(diǎn)續(xù)傳?簡(jiǎn)單來(lái)說(shuō)就是上傳文件中途暫停或失敗(比如遇到網(wǎng)絡(luò)問(wèn)題)之后,不需要重新上傳,只需要上傳那些未成功上傳的文件分片即可。分片(先將文件切分成多個(gè)文件分片)上傳是斷點(diǎn)續(xù)傳的基礎(chǔ)
    • 網(wǎng)絡(luò)IO:
      • 客戶端和服務(wù)器能在網(wǎng)絡(luò)中通信,那必須得使用 Socket 編程來(lái)支持跨主機(jī)間通信。Socket這個(gè)貨叫插口,其實(shí)我覺(jué)得就是個(gè),比如咱們自己家里有兩臺(tái)路由器要通信,你不能拿一條線直接粘到路由器屁股上吧,肯定是兩臺(tái)路由器屁股上都有口,然后一條線這邊插好那邊插好,然后就可以跨主機(jī)通信了唄,路由器屁股上開(kāi)的口我覺(jué)得就可以看作是Socket。
        • 創(chuàng)建 Socket 的時(shí)候,可以指定網(wǎng)絡(luò)層使用的是 IPv4 還是 IPv6,傳輸層使用的是 TCP 還是 UDP。 不管是那種,反正肯定是 服務(wù)器的程序要先跑起來(lái),然后等待客戶端的連接和數(shù)據(jù)
          • 我們先來(lái)看看服務(wù)端的 Socket 編程過(guò)程是怎樣的。或者叫**TCP Socket**。它基本只能一對(duì)一通信,因?yàn)槭褂玫氖峭阶枞姆绞?#xff0c;當(dāng)服務(wù)端在還沒(méi)處理完一個(gè)客戶端的網(wǎng)絡(luò) I/O 時(shí),或者 讀寫操作發(fā)生阻塞時(shí),其他客戶端是無(wú)法與服務(wù)端連接的。可如果我們服務(wù)器只能服務(wù)一個(gè)客戶,那這樣就太浪費(fèi)資源了,于是我們要改進(jìn)這個(gè)網(wǎng)絡(luò) I/O 模型,以支持更多的客戶端。
          • 或者說(shuō),先看看 客戶端和服務(wù)端的基于 TCP 的通信流程
            • 服務(wù)端的偽代碼:
            • 在 LInux 中一切皆文件,socket 也不例外,每個(gè)打開(kāi)的文件都有讀寫緩沖區(qū),對(duì)文件執(zhí)行read()、write()時(shí)的具體流程如下:
          • 可以看到 傳統(tǒng)的 socket 通信會(huì)阻塞在 connect,accept,read/write 這幾個(gè)操作上,這樣的話如果 server 是單進(jìn)程/線程的話,只要 server 阻塞,就不能再接收其他 client 的處理了,由此可知傳統(tǒng)的 socket 無(wú)法支持 C10K
            • 針對(duì)傳統(tǒng) IO 模型缺陷的改進(jìn),主要有兩種:
              • 多進(jìn)程/線程模型
              • IO 多路程復(fù)用:下面有
            • 高并發(fā)即我們所說(shuō)的 C10K(一個(gè)server 服務(wù) 1w 個(gè) client),C10M。高并發(fā)架構(gòu)其實(shí)有一些很通用的架構(gòu)設(shè)計(jì),如無(wú)鎖化,緩存等
            • 經(jīng)典的 C10K 問(wèn)題:如果服務(wù)器的內(nèi)存只有 2 GB,網(wǎng)卡是千兆的,能支持并發(fā) 1 萬(wàn)請(qǐng)求嗎【單機(jī)同時(shí)處理 1 萬(wàn)個(gè)請(qǐng)求的問(wèn)題。】從硬件資源角度看,對(duì)于 2GB 內(nèi)存千兆網(wǎng)卡的服務(wù)器,如果每個(gè)請(qǐng)求處理占用不到 200KB 的內(nèi)存和 100Kbit 的網(wǎng)絡(luò)帶寬就可以滿足并發(fā) 1 萬(wàn)個(gè)請(qǐng)求。不過(guò),要想真正實(shí)現(xiàn) C10K 的服務(wù)器,要考慮的地方在于服務(wù)器的網(wǎng)絡(luò) I/O 模型,效率低的模型,會(huì)加重系統(tǒng)開(kāi)銷基于進(jìn)程或者線程模型的,其實(shí)還是有問(wèn)題的。新到來(lái)一個(gè) TCP 連接,就需要分配一個(gè)進(jìn)程或者線程,那么如果要達(dá)到 C10K,意味著要一臺(tái)機(jī)器維護(hù) 1 萬(wàn)個(gè)連接,相當(dāng)于要維護(hù) 1 萬(wàn)個(gè)進(jìn)程/線程,操作系統(tǒng)就算死扛也是扛不住的
        • 服務(wù)器單機(jī)理論最大能連接多少個(gè)客戶端?
          • TCP 連接是由四元組唯一確認(rèn)的,這個(gè)四元組就是:本機(jī)IP, 本機(jī)端口, 對(duì)端IP, 對(duì)端端口。服務(wù)器作為服務(wù)方,通常會(huì)在本地固定監(jiān)聽(tīng)一個(gè)端口,等待客戶端的連接。 因此服務(wù)器的本地 IP 和端口是固定的,于是對(duì)于服務(wù)端 TCP 連接的四元組只有對(duì)端 IP 和端口是會(huì)變化的,所以最大 TCP 連接數(shù) = 客戶端 IP 數(shù)×客戶端端口數(shù)。對(duì)于 IPv4,客戶端的 IP 數(shù)最多為 2 的 32 次方,客戶端的端口數(shù)最多為 2 的 16 次方,也就是服務(wù)端單機(jī)最大 TCP 連接數(shù)約為 2 的 48 次方。但是服務(wù)器肯定承載不了那么大的連接數(shù),主要會(huì)受兩個(gè)方面的限制:
            • fd(文件描述符):Socket 實(shí)際上是一個(gè)文件,也就會(huì)對(duì)應(yīng)一個(gè)文件描述符。在 Linux 下,單個(gè)進(jìn)程打開(kāi)的文件描述符數(shù)是有限制的,沒(méi)有經(jīng)過(guò)修改的值一般都是 1024,不過(guò)我們可以通過(guò) ulimit 增大文件描述符的數(shù)目;
              • 在 Linux 中無(wú)論是文件,socket,還是管道,設(shè)備等,一切皆文件,Linux 抽象出了一個(gè) VFS(virtual file system) 層,屏蔽了所有的具體的文件,VFS 提供了統(tǒng)一的接口給上層調(diào)用,這樣應(yīng)用層只與 VFS 打交道,極大地方便了用戶的開(kāi)發(fā),仔細(xì)對(duì)比你會(huì)發(fā)現(xiàn),這和 Java 中的面向接口編程很類似【fd 的值從 0 開(kāi)始,其中 0,1,2 是固定的,分別指向標(biāo)準(zhǔn)輸入(指向鍵盤),標(biāo)準(zhǔn)輸出/標(biāo)準(zhǔn)錯(cuò)誤(指向顯示器),之后每打開(kāi)一個(gè)文件,fd 都會(huì)從 3 開(kāi)始遞增,但需要注意的是 fd 并不一定都是遞增的,如果關(guān)閉了文件,之前的 fd 是可以被回收利用的】
            • 系統(tǒng)內(nèi)存,每個(gè) TCP 連接在內(nèi)核中都有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),意味著每個(gè)連接都是會(huì)占用一定內(nèi)存的;
      • 基于 Linux 一切皆文件的理念,在內(nèi)核中 Socket 也是以文件的形式存在的,也是有對(duì)應(yīng)的文件描述符,每個(gè)打開(kāi)的文件也都有讀寫緩沖區(qū)。每個(gè)文件都有一個(gè) inode,Socket 文件的 inode 指向了內(nèi)核中的 Socket 結(jié)構(gòu),在這個(gè)結(jié)構(gòu)體里有兩個(gè)隊(duì)列,分別是發(fā)送隊(duì)列和接收隊(duì)列,這個(gè)兩個(gè)隊(duì)列里面保存的是一個(gè)個(gè) struct sk_buff,用鏈表的組織形式串起來(lái)。sk_buff 可以表示各個(gè)層的數(shù)據(jù)包,在應(yīng)用層數(shù)據(jù)包叫 data,在 TCP 層我們稱為 segment,在 IP 層我們叫 packet,在數(shù)據(jù)鏈路層稱為 frame,這不就和計(jì)算機(jī)網(wǎng)絡(luò)穿起來(lái)了嘛。協(xié)議棧采用的是分層結(jié)構(gòu),上層向下層傳遞數(shù)據(jù)時(shí)需要增加包頭,下層向上層數(shù)據(jù)時(shí)又需要去掉包頭,如果每一層都用一個(gè)結(jié)構(gòu)體,那在層之間傳遞數(shù)據(jù)的時(shí)候,就要發(fā)生多次拷貝,這將大大降低 CPU 效率。于是,為了在層級(jí)之間傳遞數(shù)據(jù)時(shí),不發(fā)生拷貝,只用 sk_buff 一個(gè)結(jié)構(gòu)體來(lái)描述所有的網(wǎng)絡(luò)包,那它是如何做到的呢?是通過(guò)調(diào)整 sk_buff 中 data 的指針。【當(dāng)接收?qǐng)?bào)文時(shí),從網(wǎng)卡驅(qū)動(dòng)開(kāi)始,通過(guò)協(xié)議棧層層往上傳送數(shù)據(jù)報(bào),通過(guò)增加 skb->data 的值,來(lái)逐步剝離協(xié)議首部+++++當(dāng)要發(fā)送報(bào)文時(shí),創(chuàng)建 sk_buff 結(jié)構(gòu)體,數(shù)據(jù)緩存區(qū)的頭部預(yù)留足夠的空間,用來(lái)填充各層首部,在經(jīng)過(guò)各下層協(xié)議時(shí),通過(guò)減少 skb->data 的值來(lái)增加協(xié)議首部。】

PART1:Unix 常見(jiàn)的五種IO模型:網(wǎng)絡(luò)編程中的五個(gè) I/O 模型:同步阻塞 I/O(BIO)、同步非阻塞 I/O(NIO)、 I/O 多路復(fù)用、信號(hào)驅(qū)動(dòng)、異步非阻塞 I/O(AIO))【只有 AIO 為異步 IO,其他都是同步 IO】,最常用的就是同步阻塞BIO 和 IO 多路復(fù)用

  • Unix 常見(jiàn)的IO模型:對(duì)于一次IO訪問(wèn)(以read舉例),數(shù)據(jù)會(huì)先被 拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后 才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間

    • 無(wú)論是阻塞 I/O、非阻塞 I/O,還是基于非阻塞 I/O 的多路復(fù)用以及信號(hào)驅(qū)動(dòng)都是同步調(diào)用。因?yàn)?它們?cè)?read 調(diào)用時(shí),內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到應(yīng)用程序空間,過(guò)程都是需要等待的,也就是說(shuō)這個(gè)過(guò)程是同步的,如果內(nèi)核實(shí)現(xiàn)的拷貝效率不高,read 調(diào)用就會(huì)在這個(gè)同步過(guò)程中等待比較長(zhǎng)的時(shí)間
      • 5中I/O中,I/O 阻塞、I/O非阻塞、I/O復(fù)用、SIGIO 都會(huì)在不同程度上阻塞應(yīng)用程序,而只有異步I/O模型在整個(gè)操作期間都不會(huì)阻塞應(yīng)用程序。】
        • 真正的異步 I/O 是「內(nèi)核數(shù)據(jù)準(zhǔn)備好」和「數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)」這兩個(gè)過(guò)程都不用等待
        • POSIX 規(guī)范中定義了同步I/O 和異步I/O的術(shù)語(yǔ):
          • 同步I/O : 需要進(jìn)程去真正的去操作I/O
          • 異步I/O:內(nèi)核在I/O操作完成后再通知應(yīng)用進(jìn)程操作結(jié)果
      • 但是如果使用同步的方式來(lái)通信的話,所有的操作都在一個(gè)線程內(nèi)順序執(zhí)行完成,這么做缺點(diǎn)是很明顯的:因?yàn)橥降耐ㄐ挪僮鲿?huì)阻塞同一個(gè)線程的其他任何操作,只有這個(gè)操作完成了之后,后續(xù)的操作才可以完成,所以出現(xiàn)了同步阻塞+多線程(每個(gè)Socket都創(chuàng)建一個(gè)線程對(duì)應(yīng)),但是系統(tǒng)內(nèi)線程數(shù)量是有限制的,同時(shí)線程切換很浪費(fèi)時(shí)間,適合Socket少的情況,因該需要出現(xiàn)IO模型
    • 阻塞 IO 和 IO 多路復(fù)用最為常用,原因如下:
      • 在系統(tǒng)內(nèi)核的支持上,現(xiàn)在大多數(shù)系統(tǒng)內(nèi)核都會(huì)支持阻塞 IO、非阻塞 IO 和 IO 多路復(fù)用,但像信號(hào)驅(qū)動(dòng) IO、異步 IO,只有高版本的 Linux 系統(tǒng)內(nèi)核才會(huì)支持。
      • 在編程語(yǔ)言上,無(wú)論 C++ 還是 Java,在高性能的網(wǎng)絡(luò)編程框架的編寫上,大多數(shù)都是基于 Reactor 模式,其中最為典型的便是 Java 的 Netty 框架,而 Reactor 模式是基于 IO 多路復(fù)用的。當(dāng)然,在非高并發(fā)場(chǎng)景下,同步阻塞 IO 是最為常見(jiàn)的
    • 當(dāng)一個(gè)read操作發(fā)生時(shí),會(huì)經(jīng)歷兩個(gè)階段:或者說(shuō)I/O 是分為兩個(gè)過(guò)程的【或者說(shuō)當(dāng)應(yīng)用程序發(fā)起 I/O 調(diào)用后,會(huì)經(jīng)歷兩個(gè)步驟:】



      • 過(guò)程一:內(nèi)核等待數(shù)據(jù)準(zhǔn)備就緒 (Waiting for the data to be ready)【內(nèi)核程序要從磁盤、網(wǎng)卡等讀取數(shù)據(jù)到內(nèi)核空間緩存區(qū);】
        • 傳統(tǒng)的IO流程,包括read和write的過(guò)程:
          • read:把數(shù)據(jù)從磁盤讀取到內(nèi)核緩沖區(qū),再?gòu)膬?nèi)核緩沖區(qū)拷貝到用戶緩沖區(qū)
          • write:先把數(shù)據(jù)寫入到socket緩沖區(qū),最后寫入網(wǎng)卡設(shè)備。
      • 過(guò)程二:內(nèi)核或者說(shuō)用戶程序?qū)?strong>數(shù)據(jù)從內(nèi)核空間緩存拷貝到用戶空間的進(jìn)程中 (Copying the data from the kernel to the process)【大多數(shù)文件系統(tǒng)的默認(rèn) IO 操作都是緩存 IO。緩存 IO 的缺點(diǎn):數(shù)據(jù)在傳輸過(guò)程中需要在應(yīng)用程序地址空間和內(nèi)核空間進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來(lái)的CPU以及內(nèi)存開(kāi)銷非常大。】
    • 正式因?yàn)檫@兩個(gè)階段,linux系統(tǒng)產(chǎn)生了下面五種網(wǎng)絡(luò)模式的方案:【阻塞 I/O 會(huì)阻塞在過(guò)程 1 和過(guò)程 2,非阻塞 I/O 和基于非阻塞 I/O 的多路復(fù)用只會(huì)阻塞在過(guò)程 2,所以這三個(gè)都可以認(rèn)為是同步 I/O;異步 I/O 則不同,過(guò)程 1 和過(guò)程 2 都不會(huì)阻塞。】【主要原因在于socket.accept()、socket.read()、socket.write()三個(gè)主要函數(shù)都是同步阻塞的。當(dāng)一個(gè)連接在處理I/O的時(shí)候,系統(tǒng)是阻塞的,如果是單線程的話必然就阻塞,但CPU是被釋放出來(lái)的,開(kāi)啟多線程,就可以讓CPU去處理更多的事情。利用多核,當(dāng)I/O阻塞時(shí),但CPU空閑的時(shí)候,可以利用多線程使用CPU資源。
      • Unix 常見(jiàn)的五種IO模型之一:同步阻塞式IO模型BIO(blocking IO model):在linux中,默認(rèn)情況下所有的IO操作都是blocking

        • 計(jì)算機(jī)有內(nèi)核,內(nèi)核可以接收客戶端來(lái)的連接【客戶端的所有連接是先到達(dá)內(nèi)核】,建立連接就會(huì)產(chǎn)生文件描述符,原來(lái)內(nèi)核中有read函數(shù)可以讀文件描述符,這個(gè)read操作是在一個(gè)線程或者進(jìn)程中讀,而來(lái)一個(gè)客戶端請(qǐng)求后服務(wù)端就會(huì)new一個(gè)新線程,一個(gè)線程對(duì)應(yīng)一個(gè)連接,利用CPU的時(shí)間片輪轉(zhuǎn)去處理當(dāng)前的用戶讀寫操作,但是線程資源畢竟是有限的。socket在這個(gè)時(shí)期是block的,數(shù)據(jù)包不能返回一直在阻塞,這就是對(duì)應(yīng)的早期的BIO。這樣就浪費(fèi)了計(jì)算機(jī)硬件。NIO此時(shí)出來(lái)了,內(nèi)核此時(shí)發(fā)生變化了,內(nèi)核升級(jí)
          • yum install man man-pages:這條命令可以看linux中命令的實(shí)現(xiàn)原理。利用man 2 socket可以看到
          • 到了NIO之后,此時(shí)說(shuō)明文件描述符可以是nonblock。然后由于是非阻塞的,我就可以單線程或者單進(jìn)程,在這個(gè)單個(gè)里面寫一個(gè)while循環(huán),read fd1,有沒(méi)有數(shù)據(jù),fd1說(shuō)額沒(méi)有。好,那我就繼續(xù)read fd2,fd2說(shuō)有數(shù)據(jù),拿著有的東西進(jìn)行處理,處理完之后繼續(xù)去用戶空間輪詢r(jià)ead fdx【輪詢操作發(fā)生在用戶空間哦】,但是拿出來(lái)處理都由我自己來(lái)弄,就叫做同步+非阻塞,
          • 那現(xiàn)在如果有10000個(gè)fd,在用戶空間內(nèi)的用戶進(jìn)程需要輪詢調(diào)用10000次kernel,這么多次系統(tǒng)調(diào)用,太浪費(fèi)了,所以內(nèi)核進(jìn)行升級(jí),增加一個(gè)系統(tǒng)調(diào)用,select
          • 然后,繼續(xù)可以man 2 select看一下這個(gè)系統(tǒng)調(diào)用
          • 雖然select是,比如1000個(gè)客戶端連接請(qǐng)求,你告訴我里面50個(gè)準(zhǔn)備好數(shù)據(jù)了,我去挨個(gè)讀這50個(gè),節(jié)省了用戶態(tài)到內(nèi)核態(tài)的切換。但是select歸根到底有缺點(diǎn),往下看咯
        • 通常把阻塞的文件描述符(file descriptor,fd)稱之為阻塞I/O。默認(rèn)條件下,創(chuàng)建的socket fd是阻塞的,針對(duì)阻塞I/O調(diào)用系統(tǒng)接口,可能因?yàn)榈却氖录](méi)有到達(dá)而被系統(tǒng)掛起,直到等待的事件觸發(fā)調(diào)用接口才返回,例如,tcp socket的connect調(diào)用會(huì)阻塞至第三次握手成功(不考慮socket 出錯(cuò)或系統(tǒng)中斷)
        • 在JDK1.4推出JavaNIO之前,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),這種一請(qǐng)求一應(yīng)答的通信模型簡(jiǎn)化了上層的應(yīng)用開(kāi)發(fā),但是BIO在性能和可靠性方面卻存在著巨大的瓶頸。因此,在很長(zhǎng)一段時(shí)間里,大型的應(yīng)用服務(wù)器都采用C或者C++語(yǔ)言開(kāi)發(fā),因?yàn)樗鼈兛梢灾苯邮褂貌僮飨到y(tǒng)提供的異步I/O或者AIO能力。當(dāng)并發(fā)訪問(wèn)量增大、響應(yīng)時(shí)間延遲增大之后,采用JavaBIO開(kāi)發(fā)的服務(wù)端軟件只有通過(guò)硬件的不斷擴(kuò)容來(lái)滿足高并發(fā)和低時(shí)延,它極大地增加了企業(yè)的成本,并且隨著集群規(guī)模的不斷膨脹,系統(tǒng)的可維護(hù)性也面臨巨大的挑戰(zhàn),只能通過(guò)采購(gòu)性能更高的硬件服務(wù)器來(lái)解決問(wèn)題,這會(huì)導(dǎo)致惡性循環(huán)。正是由于Java傳統(tǒng)BIO的拙劣表現(xiàn),才使得Java支持非阻塞I/O的呼聲日漸高漲,最終,JDK1.4版本提供了新的NIO類庫(kù),Java 終于也可以支持非阻塞I/O 了。
        • 一個(gè)典型的讀操作流程大概是這樣:
          • 當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用,kernel就開(kāi)始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)(對(duì)于網(wǎng)絡(luò)IO來(lái)說(shuō),很多時(shí)候數(shù)據(jù)在一開(kāi)始還沒(méi)有到達(dá)。比如,還沒(méi)有收到一個(gè)完整的UDP包。這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來(lái)),而數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個(gè)過(guò)程的,這個(gè)過(guò)程需要等待。而在用戶進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞(當(dāng)然,是進(jìn)程自己選擇的阻塞)。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶空間的緩沖區(qū)以后,然后kernel返回結(jié)果,用戶進(jìn)程才解除block的狀態(tài),重新運(yùn)行起來(lái)。
            • 同步阻塞 BIO 模型中,應(yīng)用程序發(fā)起 read 調(diào)用后,會(huì)一直阻塞,直到內(nèi)核把數(shù)據(jù)拷貝到用戶空間
        • blocking IO的特點(diǎn)就是在IO執(zhí)行的下兩個(gè)階段的時(shí)候都被block了。BIO 模型有兩處阻塞的地方
          • 等待數(shù)據(jù)準(zhǔn)備就緒 (Waiting for the data to be ready) 阻塞 (服務(wù)端阻塞等待客戶端發(fā)起連接。也就是通過(guò) serverSocket.accept()方法服務(wù)端等待用戶發(fā)連接請(qǐng)求過(guò)來(lái)。)
          • 將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process) 阻塞(連接成功后,工作線程阻塞讀取客戶端 Socket 發(fā)送數(shù)據(jù)。也就是服務(wù)端通過(guò) in.readLine() 從網(wǎng)絡(luò)中讀客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù),這個(gè)地方也會(huì)阻塞。如果客戶端已經(jīng)和服務(wù)端建立了一個(gè)連接,但客戶端遲遲不發(fā)送數(shù)據(jù),那么服務(wù)端的 readLine() 操作會(huì)一直阻塞,造成資源浪費(fèi)。)
        • BIO模型的特點(diǎn):或者說(shuō)BIO模型的缺點(diǎn):
          • Socket 連接數(shù)量受限,不適用于高并發(fā)場(chǎng)景)缺乏彈性伸縮能力,當(dāng)客戶端并發(fā)訪問(wèn)量增加后,服務(wù)端的線程個(gè)數(shù)和客戶端并發(fā)訪問(wèn)數(shù)呈1 : 1的正比關(guān)系,由于線程是Java虛擬機(jī)非常寶貴的系統(tǒng)資源,當(dāng)線程數(shù)膨脹之后,系統(tǒng)的性能將急劇下降,隨著并發(fā)訪問(wèn)量的繼續(xù)增大,系統(tǒng)會(huì)發(fā)生線程堆棧溢出、創(chuàng)建新線程失敗等問(wèn)題,并最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對(duì)外提供服務(wù)。顯而易見(jiàn),如果我們要構(gòu)建高性能、低時(shí)延、支持大并發(fā)的應(yīng)用系統(tǒng),使用同步阻塞I/O模型是無(wú)法滿足性能線性增長(zhǎng)和可靠性的。當(dāng)面對(duì)十萬(wàn)甚至百萬(wàn)級(jí)連接的時(shí)候,傳統(tǒng)的BIO模型是無(wú)能為力的。隨著移動(dòng)端應(yīng)用的興起和各種網(wǎng)絡(luò)游戲的盛行,百萬(wàn)級(jí)長(zhǎng)連接日趨普遍,此時(shí),必然需要一種更高效的I/O處理模型NIO
          • 有兩處阻塞,分別是等待用戶發(fā)起連接,和等待用戶發(fā)送數(shù)據(jù)。這不是個(gè)好事情,你想呀,你去買東西,別人給你取盒煙讓你等了一個(gè)小時(shí),然后你掃碼時(shí)網(wǎng)不太好,又讓你等了半小時(shí),你不得不付現(xiàn)金,他給你找零錢又讓你等了一小時(shí)…。所以肯定得解決這個(gè)兩個(gè)地方阻塞的問(wèn)題。用NIO網(wǎng)絡(luò)模型(NIO網(wǎng)絡(luò)模型操作上是用一個(gè)線程處理多個(gè)連接,使得每一個(gè)工作線程都可以處理多個(gè)客戶端的 Socket 請(qǐng)求,這樣工作線程的利用率就能得到提升,所需的工作線程數(shù)量也隨之減少。此時(shí) NIO 的線程模型就變?yōu)?1 個(gè)工作線程對(duì)應(yīng)多個(gè)客戶端 Socket 的請(qǐng)求,這就是所謂的 I/O多路復(fù)用。)來(lái)解決
          • 另外補(bǔ)充一點(diǎn),網(wǎng)絡(luò)編程中,通常把可能永遠(yuǎn)阻塞的系統(tǒng)API調(diào)用 稱為慢系統(tǒng)調(diào)用,典型的如 accept、recv、select等。慢系統(tǒng)調(diào)用在阻塞期間可能被信號(hào)中斷而返回錯(cuò)誤,相應(yīng)的errno 被設(shè)置為EINTR,我們需要處理這種錯(cuò)誤,解決辦法有:
            • 重啟系統(tǒng)調(diào)用:
            • 信號(hào)處理
        • 先理一理哈,在計(jì)算機(jī)網(wǎng)絡(luò)這篇中提到了 應(yīng)用程序建立連接后通過(guò)OS實(shí)現(xiàn)的TCP協(xié)議的socket接口給服務(wù)器發(fā)了數(shù)據(jù)(假設(shè)咱們服務(wù)器上用的服務(wù)器軟件是Tomcat),服務(wù)器會(huì)利用自己體內(nèi)的endPoint的實(shí)現(xiàn)去socket(OS實(shí)現(xiàn)的TCP協(xié)議的socket接口)中拿到數(shù)據(jù),然后再解析成為一個(gè)又一個(gè)請(qǐng)求,再交給tomcat去處理,處理完響應(yīng)給客戶端。
        • BI/O 模型典型的Java實(shí)現(xiàn): 基于 BIO 的文件復(fù)制程序:字節(jié)流方式、字符流方式、字符緩沖,按行讀取、隨機(jī)讀寫(RandomAccessFile)
          public class BIOSever { //在服務(wù)端創(chuàng)建一個(gè) ServerSocket 對(duì)象ServerSocket ss = new ServerSocket();// 綁定端口 9090,然后啟動(dòng)運(yùn)行服務(wù)端,然后阻塞等待客戶端發(fā)起連接請(qǐng)求,直到有客戶端的連接發(fā)送過(guò)來(lái)之后。當(dāng)有客戶端的連接請(qǐng)求后,服務(wù)端會(huì)啟動(dòng)一個(gè)新線程 ServerTaskThread,用新創(chuàng)建的線程去處理當(dāng)前用戶的讀寫操作。ss.bind(new InetSocketAddress("localhost", 9090));System.out.println("server started listening " + PORT);try {Socket s = null;while (true) {// 阻塞等待客戶端發(fā)送連接請(qǐng)求,直到有客戶端的連接發(fā)送過(guò)來(lái)之后,accept() 方法返回Socket s = ss.accept();new Thread(new ServerTaskThread(s)).start();}} catch (Exception e) {...} finally {if (ss != null) {ss.close();ss = null;} } /* *當(dāng)有客戶端的連接請(qǐng)求后,服務(wù)端會(huì)啟動(dòng)一個(gè)新線程 ServerTaskThread,用新創(chuàng)建的線程去處理當(dāng)前用戶的讀寫操作。 */ public class ServerTaskThread implements Runnable {...while (true) {// 阻塞等待客戶端發(fā)請(qǐng)求過(guò)來(lái)String readLine = in.readLine();if (readLine == null) {break;}...}... }
          • 除了上面這種寫法,RPC這里也可以對(duì)Socket網(wǎng)絡(luò)通信代碼進(jìn)行封裝,體現(xiàn)咱們的封裝性
        • 使用 Java NIO 包組成一個(gè)簡(jiǎn)單的客戶端-服務(wù)端網(wǎng)絡(luò)通訊所需要的 ServerSocketChannel、SocketChannel 和 Buffer,一個(gè)完整的可運(yùn)行的例子:(例子來(lái)自javadoop老師)
          • SocketHandler:【來(lái)一個(gè)新的連接,我們就新開(kāi)一個(gè)線程來(lái)處理這個(gè)連接,之后的操作全部由那個(gè)線程來(lái)完成。】
          • 客戶端 SocketChannel 的使用:
        • 上面這個(gè)例子的性能瓶頸或者說(shuō)問(wèn)題:非阻塞 IO應(yīng)運(yùn)而生
      • Unix 常見(jiàn)的五種IO模型之二:同步非阻塞式IO模型(noblocking IO model)NIO一般很少直接使用這種模型,而是在其他 I/O 模型中使用非阻塞 I/O 這一特性。這種方式對(duì)單個(gè) I/O 請(qǐng)求意義不大,但給 I/O 多路復(fù)用鋪平了道路):linux下,可以通過(guò)設(shè)置socket使其變?yōu)閚on-blocking。NIO 的日常操作,這個(gè)文章有例子:文件復(fù)制、文件復(fù)制—映射方式、文件復(fù)制—零拷貝方式、
        • 把非阻塞的文件描述符稱為非阻塞I/O。可以通過(guò)設(shè)置SOCK_NONBLOCK標(biāo)記創(chuàng)建非阻塞的socket fd,或者使用fcntl將fd設(shè)置為非阻塞。
          • 對(duì)非阻塞fd調(diào)用系統(tǒng)接口時(shí),不需要等待事件發(fā)生而立即返回,事件沒(méi)有發(fā)生,接口返回-1,此時(shí)需要通過(guò)errno的值來(lái)區(qū)分是否出錯(cuò)。不同的接口,立即返回時(shí)的errno值不盡相同,如,recv、send、accept errno通常被設(shè)置為EAGIN 或者EWOULDBLOCK,connect 則為EINPRO- GRESS 。
        • NIO 也稱新 IO 或者非阻塞 IO(Non-Blocking IO)【NIO 中的 N 可以理解為 Non-blocking,不單純是 New。】。Java 中的 NIO 于 Java 1.4 中引入,對(duì)應(yīng) java.nio 包,提供了 Channel , Selector,Buffer 等抽象傳統(tǒng) IO 是面向輸入/輸出流編程的,而 NIO 是面向通道編程的,或者說(shuō)它是支持面向緩沖的,基于通道的 I/O 操作方法。 對(duì)于高負(fù)載、高并發(fā)的(網(wǎng)絡(luò))應(yīng)用,應(yīng)使用 NIO 。
        • NIO 的 3 個(gè)核心概念或者說(shuō)Java NIO 中三大組件 Buffer、Channel、Selector:Channel、Buffer、Selector:
          • Channel(通道):
            • 所有的 NIO 操作始于通道,通道是數(shù)據(jù)來(lái)源或數(shù)據(jù)寫入的目的地,主要地,我們將關(guān)心 java.nio 包中實(shí)現(xiàn)的以下幾個(gè) Channel:
              • FileChannel:
                • FileChannel 是不支持非阻塞的。
              • SocketChannel:
                • 可以將 SocketChannel 理解成一個(gè) TCP 客戶端。雖然這么理解有點(diǎn)狹隘,因?yàn)槲覀冊(cè)诮榻B ServerSocketChannel 的時(shí)候會(huì)看到另一種使用方式。【SocketChannel 了,它不僅僅是 TCP 客戶端,它代表的是一個(gè)網(wǎng)絡(luò)通道,可讀可寫。】//打開(kāi)一個(gè) TCP 連接: SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("http://aiminhuqaqq.fun/", 80)); /**上面的這行代碼等價(jià)于下面的兩行: *打開(kāi)一個(gè)通道:SocketChannel socketChannel = SocketChannel.open(); *發(fā)起連接:socketChannel.connect(new InetSocketAddress("http://aiminhuqaqq.fun/", 80)); */
                • SocketChannel 的讀寫和 FileChannel 沒(méi)什么區(qū)別,就是操作緩沖區(qū)。
              • ServerSocketChannel:
                • SocketChannel 是 TCP 客戶端,這里說(shuō)的 ServerSocketChannel 就是對(duì)應(yīng)的服務(wù)端
                • ServerSocketChannel 用于監(jiān)聽(tīng)機(jī)器端口,管理從這個(gè)端口進(jìn)來(lái)的 TCP 連接
                • ServerSocketChannel 不和 Buffer 打交道了,因?yàn)樗⒉粚?shí)際處理數(shù)據(jù),它一旦接收到請(qǐng)求后,實(shí)例化 SocketChannel,之后在這個(gè)連接通道上的數(shù)據(jù)傳遞它就不管了,因?yàn)樗枰^續(xù)監(jiān)聽(tīng)端口,等待下一個(gè)連接
              • DatagramChannel:
                • UDP 和 TCP 不一樣,DatagramChannel 一個(gè)類處理了服務(wù)端和客戶端。
                • 監(jiān)聽(tīng)端口:
                • 發(fā)送數(shù)據(jù):
            • Channel 與Buffer 打交道,讀操作的時(shí)候?qū)?Channel 中的數(shù)據(jù)填充到 Buffer 中,而寫操作時(shí)將 Buffer 中的數(shù)據(jù)寫入到 Channel 中
              • channel 實(shí)例的兩個(gè)方法:
                • channel.read(buffer);
                • channel.write(buffer);
            • Channel 是對(duì) IO 輸入/輸出系統(tǒng)的抽象,是 IO 源與目標(biāo)之間的連接通道,NIO 的通道類似于傳統(tǒng) IO 中的各種“流”,用于讀取和寫入。與 InputStream 和 OutputStream 不同的是,Channel 是雙向的,既可以讀,也可以寫,且支持異步操作。這契合了操作系統(tǒng)的特性,比如 linux 底層通道就是雙向的。此外 Channel 還提供了 map() 方法,通過(guò)該方法可以將“一塊”數(shù)據(jù)直接映射到內(nèi)存中。因此也有人說(shuō),NIO 是面向塊處理的,而傳統(tǒng) I/O 是面向流處理的
          • Buffer(緩沖):
            • Buffer 本質(zhì)上就是一個(gè)容器,其底層持有了一個(gè)具體類型的數(shù)組來(lái)存放具體數(shù)據(jù)。或者說(shuō)一個(gè) Buffer 本質(zhì)上是內(nèi)存中的一塊,我們可以將數(shù)據(jù)寫入這塊內(nèi)存,之后從這塊內(nèi)存獲取數(shù)據(jù)。。從 Channel 中取數(shù)據(jù)或者向 Channel 中寫數(shù)據(jù)都需要通過(guò) Buffer。在 Java 中 Buffer 是一個(gè)抽象類,除 boolean 之外的基本數(shù)據(jù)類型都提供了對(duì)應(yīng)的 Buffer 實(shí)現(xiàn)類。比較常用的是 ByteBuffer 和 CharBuffer
            • java.nio定義的幾個(gè)Buffer的實(shí)現(xiàn):
            • Buffer 中的幾個(gè)重要屬性和幾個(gè)重要方法:就像數(shù)組有數(shù)組容量,每次訪問(wèn)元素要指定下標(biāo),Buffer 中也有幾個(gè)重要屬性:position、limit、capacity。
              • position:position 的初始值是 0,每往 Buffer 中寫入一個(gè)值,position 就自動(dòng)加 1,代表下一次的寫入位置,所以 position 最后會(huì)指向最后一次寫入的位置的后面一個(gè),如果 Buffer 寫滿了,那么 position 等于 capacity(position 從 0 開(kāi)始)。讀操作的時(shí)候也是類似的,每讀一個(gè)值,position 就自動(dòng)加 1
                • 寫操作模式到讀操作模式切換的時(shí)候(flip()),position 都會(huì)歸零,這樣就可以從頭開(kāi)始讀寫了
                • rewind():會(huì)重置 position 為 0,通常用于重新從頭讀寫 Buffer。和rewind相近的有:
                  • clear():有點(diǎn)重置 Buffer 的意思,相當(dāng)于重新實(shí)例化了一樣。clear() 方法會(huì)重置幾個(gè)屬性,但是我們要看到,clear() 方法并不會(huì)將 Buffer 中的數(shù)據(jù)清空,只不過(guò)后續(xù)的寫入會(huì)覆蓋掉原來(lái)的數(shù)據(jù),也就相當(dāng)于清空了數(shù)據(jù)了。
                  • compact():和 clear() 一樣的是,它們都是在準(zhǔn)備往 Buffer 填充新的數(shù)據(jù)之前調(diào)用。compact() 方法有點(diǎn)不一樣,調(diào)用這個(gè)方法以后,會(huì)先處理還沒(méi)有讀取的數(shù)據(jù),也就是 position 到 limit 之間的數(shù)據(jù)(還沒(méi)有讀過(guò)的數(shù)據(jù)),先將這些數(shù)據(jù)移到左邊,然后在這個(gè)基礎(chǔ)上再開(kāi)始寫入。很明顯,此時(shí) limit 還是等于 capacity,position 指向原來(lái)數(shù)據(jù)的右邊
              • limit:寫操作模式下,limit 代表的是最大能寫入的數(shù)據(jù),這個(gè)時(shí)候 limit 等于 capacity。寫結(jié)束后,切換到讀模式,此時(shí)的 limit 等于 Buffer 中實(shí)際的數(shù)據(jù)大小,因?yàn)?Buffer 不一定被寫滿了
              • capacity:
              • mark:除了 position、limit、capacity 這三個(gè)基本的屬性外,還有一個(gè)常用的屬性就是 mark。
                • mark 用于臨時(shí)保存 position 的值,每次調(diào)用 mark() 方法都會(huì)將 mark 設(shè)值為當(dāng)前的 position,便于后續(xù)需要的時(shí)候使用。

            • 初始化 Buffer:
              • 每個(gè) Buffer 實(shí)現(xiàn)類都提供了一個(gè)靜態(tài)方法 allocate(int capacity) 幫助我們快速實(shí)例化一個(gè) Buffer
              • 另外,我們經(jīng)常使用 wrap 方法來(lái)初始化一個(gè) Buffer。
            • 填充 Buffer:
              • 各個(gè) Buffer 類都提供了一些 put 方法用于將數(shù)據(jù)填充到 Buffer 中,如 ByteBuffer 中的幾個(gè) put 方法:
              • 對(duì)于 Buffer 來(lái)說(shuō),另一個(gè)常見(jiàn)的操作中就是,我們要將來(lái)自 Channel 的數(shù)據(jù)填充到 Buffer 中,在系統(tǒng)層面上,這個(gè)操作我們稱為讀操作,因?yàn)閿?shù)據(jù)是從外部(文件或網(wǎng)絡(luò)等)讀到內(nèi)存中
              • 提取 Buffer 中的值:
                • 每往 Buffer 中寫入一個(gè)值,position 就自動(dòng)加 1,代表下一次的寫入位置,所以 position 最后會(huì)指向最后一次寫入的位置的后面一個(gè),如果 Buffer 寫滿了,那么 position 等于 capacity(position 從 0 開(kāi)始)
                • 如果要讀 Buffer 中的值,需要切換模式,從寫入模式切換到讀出模式。注意,通常在說(shuō) NIO 的讀操作的時(shí)候,我們說(shuō)的是從 Channel 中讀數(shù)據(jù)到 Buffer 中,對(duì)應(yīng)的是對(duì) Buffer 的寫入操作

          • Selector:JDK1.4開(kāi)始引入了NIO類庫(kù),主要是使用Selector多路復(fù)用器來(lái)實(shí)現(xiàn)。Selector在Linux等主流操作系統(tǒng)上是通過(guò)IO復(fù)用Epoll實(shí)現(xiàn)的。通過(guò)Selector多路復(fù)用器,只需要一個(gè)線程便可以管理多個(gè)客戶端連接【非阻塞 IO 的核心在于使用一個(gè) Selector 來(lái)管理多個(gè)通道,可以是 SocketChannel,也可以是 ServerSocketChannel,將各個(gè)通道注冊(cè)到 Selector 上,指定監(jiān)聽(tīng)的事件。之后可以只用一個(gè)線程來(lái)輪詢這個(gè) Selector,看看上面是否有通道是準(zhǔn)備好的,當(dāng)通道準(zhǔn)備好可讀或可寫,然后才去開(kāi)始真正的讀寫,這樣速度就很快了。我們就完全沒(méi)有必要給每個(gè)通道都起一個(gè)線程。】。主要點(diǎn)見(jiàn)下述內(nèi)容。
            • Selector 建立在非阻塞的基礎(chǔ)之上,大家經(jīng)常聽(tīng)到的 多路復(fù)用 在 Java 世界中指的就是Selector,用于實(shí)現(xiàn)一個(gè)線程管理多個(gè) Channel
            • NIO 中 Selector 是對(duì)底層操作系統(tǒng)實(shí)現(xiàn)的一個(gè)抽象,管理通道狀態(tài)其實(shí)都是底層系統(tǒng)實(shí)現(xiàn)的
              • select:上世紀(jì) 80 年代就實(shí)現(xiàn)了,它支持注冊(cè) FD_SETSIZE(1024) 個(gè) socket,在那個(gè)年代肯定是夠用的,不過(guò)現(xiàn)在嘛,肯定是不行了
              • poll:1997 年,出現(xiàn)了 poll 作為 select 的替代者,最大的區(qū)別就是,poll 不再限制 socket 數(shù)量
                • select 和 poll 都有一個(gè)共同的問(wèn)題,那就是它們都只會(huì)告訴你有幾個(gè)通道準(zhǔn)備好了,但是不會(huì)告訴你具體是哪幾個(gè)通道。所以,一旦知道有通道準(zhǔn)備好以后,自己還是需要進(jìn)行一次掃描,顯然這個(gè)不太好,通道少的時(shí)候還行,一旦通道的數(shù)量是幾十萬(wàn)個(gè)以上的時(shí)候,掃描一次的時(shí)間都很可觀了,時(shí)間復(fù)雜度 O(n)。所以,后來(lái)才催生了以下epoll實(shí)現(xiàn)。
              • epoll:2002 年隨 Linux 內(nèi)核 2.5.44 發(fā)布,epoll 能直接返回具體的準(zhǔn)備好的通道,時(shí)間復(fù)雜度 O(1)
                • 除了 Linux 中的 epoll,2000 年 FreeBSD 出現(xiàn)了 Kqueue,還有就是,Solaris 中有 /dev/poll。Windows 平臺(tái)的非阻塞 IO 使用 select,我們也不必覺(jué)得 Windows 很落后,在 Windows 中 IOCP 提供的異步 IO 是比較強(qiáng)大的
            • Selector一些基本的接口操作:
              • 首先,我們 開(kāi)啟一個(gè) Selector選擇器或者叫多路復(fù)用器:Selector selector = Selector.open();
              • 將 Channel 注冊(cè)到 Selector 上。Selector 建立在非阻塞模式之上,所以注冊(cè)到 Selector 的 Channel 必須要支持非阻塞模式,FileChannel 不支持非阻塞,我們這里討論最常見(jiàn)的 SocketChannel 和 ServerSocketChannel
            • Selector常用的幾個(gè)方法:
        • NIO 網(wǎng)絡(luò)模型,非阻塞IO,操作上是用一個(gè)線程處理多個(gè)連接,使得每一個(gè)工作線程都可以處理多個(gè)客戶端的 Socket 請(qǐng)求,這樣工作線程的利用率就能得到提升,所需的工作線程數(shù)量也隨之減少。此時(shí) NIO 的線程模型就變?yōu)?1 個(gè)工作線程對(duì)應(yīng)多個(gè)客戶端 Socket 的請(qǐng)求,這就是所謂的 I/O多路復(fù)用。使用 I/O 多路復(fù)用時(shí),最好搭配非阻塞 I/O 一起使用【多路復(fù)用 API 返回的事件并不一定可讀寫的,如果使用阻塞 I/O, 那么在調(diào)用 read/write 時(shí)則會(huì)發(fā)生程序阻塞,因此最好搭配非阻塞 I/O,以便應(yīng)對(duì)極少數(shù)的特殊情況】
        • JDK1.4開(kāi)始引入了NIO類庫(kù),主要是使用Selector多路復(fù)用器來(lái)實(shí)現(xiàn)。Selector在Linux等主流操作系統(tǒng)上是通過(guò)IO復(fù)用Epoll實(shí)現(xiàn)的
          • 通過(guò)Selector多路復(fù)用器,只需要一個(gè)線程便可以管理多個(gè)客戶端連接。當(dāng)客戶端數(shù)據(jù)到了之后,才會(huì)為其服務(wù)。
          • NIO 比 BIO 提高了服務(wù)端工作線程的利用率,并增加了一個(gè)調(diào)度者,來(lái)實(shí)現(xiàn) Socket 連接與 Socket 數(shù)據(jù)讀寫之間的分離。
          • JDK 1.4提供了對(duì)非阻塞I/O (NIO)的支持,JDK1.5_ update10版本使用epoll替代了傳統(tǒng)的select/poll,極大地提升了NIO通信的性能。與Socket類和ServerSocket類相對(duì)應(yīng),NIO也提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道實(shí)現(xiàn),這兩種新增的通道都支持阻塞和非阻塞兩種模式。阻塞模式使用非常簡(jiǎn)單,但是性能和可靠性都不好,非阻塞模式則正好相反。開(kāi)發(fā)人員一般可以根據(jù)自己的需要來(lái)選擇合適的模式,一般來(lái)說(shuō),低負(fù)載、低并發(fā)的應(yīng)用程序可以選擇同步阻塞I/O以降低編程復(fù)雜度,但是對(duì)于高負(fù)載、高并發(fā)的網(wǎng)絡(luò)應(yīng)用,需要使用NIO的非阻塞模式進(jìn)行開(kāi)發(fā)
            • NIO采用多路復(fù)用技術(shù),一個(gè)多路復(fù)用器Selector可以同時(shí)輪詢多個(gè)Channel,由于JDK使用了epoll(0代替?zhèn)鹘y(tǒng)的select實(shí)現(xiàn),所以它并沒(méi)有最大連接句柄1024/2048的限制這也就意味著只需要一個(gè)線程負(fù)責(zé)Selector的輪詢,就可以接入成千上萬(wàn)的客戶端,這確實(shí)是個(gè)非常巨大的進(jìn)步。
          • NIO的實(shí)現(xiàn)流程,類似于Select:【新事件到來(lái)的時(shí)候,會(huì)在Selector上注冊(cè)標(biāo)記位,標(biāo)示可讀、可寫或者有連接到來(lái)。NIO由原來(lái)的阻塞讀寫(占用線程)變成了單線程輪詢事件,找到可以進(jìn)行讀寫的網(wǎng)絡(luò)描述符進(jìn)行讀寫。除了事件的輪詢是阻塞的(沒(méi)有可干的事情必須要阻塞),剩余的I/O操作都是純CPU操作,沒(méi)有必要開(kāi)啟多線程。并且由于線程的節(jié)約,連接數(shù)大的時(shí)候因?yàn)榫€程切換帶來(lái)的問(wèn)題也隨之解決,進(jìn)而為處理海量連接提供了可能。】
            • 創(chuàng)建ServerSocketChannel監(jiān)聽(tīng)客戶端連接并綁定監(jiān)聽(tīng)端口,設(shè)置為非阻塞模式
            • 創(chuàng)建Reactor線程,創(chuàng)建多路復(fù)用器(Selector)并啟動(dòng)線程
            • 將ServerSocketChannel注冊(cè)到Reactor線程的Selector上,監(jiān)聽(tīng)Accept事件
            • Selector在線程run方法中無(wú)線循環(huán)輪詢準(zhǔn)備就緒的Key
            • Selector監(jiān)聽(tīng)到新的客戶端接入,處理新的請(qǐng)求,完成TCP三次握手,建立物理連接
            • 將新的客戶端連接注冊(cè)到Selector上,監(jiān)聽(tīng)讀操作,讀取客戶端發(fā)送的網(wǎng)絡(luò)消息
            • 客戶端發(fā)送的數(shù)據(jù)就緒則讀取客戶端請(qǐng)求,進(jìn)行處理
          • 既然服務(wù)端的工作線程可以服務(wù)于多個(gè)客戶端的連接請(qǐng)求,那么具體由哪個(gè)工作線程服務(wù)于哪個(gè)客戶端請(qǐng)求呢
            • 這時(shí)就需要一個(gè)調(diào)度者去監(jiān)控所有的客戶端連接,比如當(dāng)圖中的客戶端 A 的輸入已經(jīng)準(zhǔn)備好后,就由這個(gè)調(diào)度者 ,也就是Selector 選擇器去通知服務(wù)端的工作線程,告訴它們由工作線程 1 去服務(wù)于客戶端 A 的請(qǐng)求。這種思路就是 NIO 編程模型的基本原理,調(diào)度者就是 Selector 選擇器【selector的作用就是配合一個(gè)線程來(lái)管理多個(gè)channel,獲取這些channel.上發(fā)生的事件,這些channel工作在非阻塞模式下,不會(huì)讓線程吊死在一個(gè)channel上。適合連接數(shù)特別多,但流量低的場(chǎng)景(low traffic)】
              • 升級(jí)為線程池版,解決上面問(wèn)題,阻塞式I/O
              • 線程升級(jí)為線程池版,線程池再升級(jí)為selector版:selector的作用就是配合一個(gè)線程來(lái)管理多個(gè)channel,獲取這些channel.上發(fā)生的事件,這些channel工作在非阻塞模式下,不會(huì)讓線程吊死在一個(gè)channel上。所以用了selector后防止了線程吊死在同一顆樹(shù)上。適合連接數(shù)特別多,但流量低的場(chǎng)景(low traffic)】
        • socket設(shè)置為 NONBLOCK(非阻塞)就是告訴內(nèi)核,當(dāng)所請(qǐng)求的I/O操作無(wú)法完成時(shí),不要將進(jìn)程睡眠,而是返回一個(gè)錯(cuò)誤碼(EWOULDBLOCK) ,這樣請(qǐng)求就不會(huì)阻塞
          • 當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用,如果kernel中的數(shù)據(jù)還沒(méi)有準(zhǔn)備好,那么它并不會(huì)block用戶進(jìn)程,而是立刻返回一個(gè)EWOULDBLOCK error。從用戶進(jìn)程角度講 ,它發(fā)起一個(gè)read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。用戶進(jìn)程判斷結(jié)果是一個(gè)EWOULDBLOCK error時(shí),它就知道數(shù)據(jù)還沒(méi)有準(zhǔn)備好,于是它 可以再次發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶空間緩沖區(qū),然后返回。可以看到,I/O 操作函數(shù)將不斷的測(cè)試數(shù)據(jù)是否已經(jīng)準(zhǔn)備好,如果沒(méi)有準(zhǔn)備好,繼續(xù)輪詢,直到數(shù)據(jù)準(zhǔn)備好為止。整個(gè) I/O 請(qǐng)求的過(guò)程中,雖然用戶線程每次發(fā)起 I/O 請(qǐng)求后可以立即返回,但是為了等到數(shù)據(jù),仍需要不斷地輪詢、重復(fù)請(qǐng)求,消耗了大量的 CPU 的資源
        • non blocking IO的特點(diǎn)是用戶進(jìn)程需要不斷的主動(dòng)詢問(wèn)kernel數(shù)據(jù)好了沒(méi)有:
          • 等待數(shù)據(jù)準(zhǔn)備就緒 (Waiting for the data to be ready) 「這一步是非阻塞的
          • 將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process) 「這一步是阻塞的
        • 將BIO那個(gè)例子改裝為NIO的。
          • 客戶端 SocketChannel 的使用:
      • Unix 常見(jiàn)的五種IO模型之三:IO復(fù)用式IO模型(IO multiplexing model):也叫,I/O 多路復(fù)用( IO multiplexing):NIO不停問(wèn)問(wèn)問(wèn),給人煩壞了,把CPU資源也消耗的差不多了,然后神獸繼續(xù)究極進(jìn)化,從BIO—>NIO—>多路復(fù)用


        • 在I/O編程過(guò)程中,當(dāng)需要同時(shí)處理多個(gè)客戶端接入請(qǐng)求時(shí),可以利用多線程或者I/O多路復(fù)用技術(shù)進(jìn)行處理I/O多路復(fù)用技術(shù)通過(guò)把多個(gè)I/O的阻塞復(fù)用到同一個(gè)select的阻塞上,從而使得系統(tǒng)在單線程的情況下可以同時(shí)處理多個(gè)客戶端請(qǐng)求
          • 與傳統(tǒng)的多線程/多進(jìn)程模型相比,I/O 多路復(fù)用的最大優(yōu)勢(shì)是系統(tǒng)開(kāi)銷小,系統(tǒng)不需要?jiǎng)?chuàng)建新的額外進(jìn)程或者線程,也不需要維護(hù)這些進(jìn)程和線程的運(yùn)行,降低了系統(tǒng)的維護(hù)工作量,節(jié)省了系統(tǒng)資源。
        • IO 多路復(fù)用模型中,線程首先發(fā)起 select 調(diào)用,詢問(wèn)內(nèi)核數(shù)據(jù)是否準(zhǔn)備就緒,等內(nèi)核把數(shù)據(jù)準(zhǔn)備好了,用戶線程再發(fā)起 read 調(diào)用,相當(dāng)于IO 多路復(fù)用模型,通過(guò)減少無(wú)效的系統(tǒng)調(diào)用,減少了對(duì) CPU 資源的消耗。read 調(diào)用的過(guò)程(數(shù)據(jù)從內(nèi)核空間 -> 用戶空間)還是阻塞的。【相當(dāng)于 IO復(fù)用模型核心思路:系統(tǒng)給我們提供一類函數(shù)(如我們耳濡目染的select、poll、epoll函數(shù)),它們可以同時(shí)監(jiān)控多個(gè)fd的操作,任何一個(gè)返回內(nèi)核數(shù)據(jù)就緒,應(yīng)用進(jìn)程再發(fā)起recvfrom系統(tǒng)調(diào)用
        • IO多路復(fù)用是指 通過(guò)一種機(jī)制監(jiān)視多個(gè)文件描述符fd【文件描述符在形式上是一個(gè)非負(fù)整數(shù),實(shí)際上,它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開(kāi)文件的記錄表。當(dāng)程序打開(kāi)一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符】,一旦某個(gè)文件描述符fd就緒(一般是讀就緒或者寫就緒),或者說(shuō)內(nèi)核一旦發(fā)現(xiàn)進(jìn)程指定的一個(gè)或者多個(gè)IO條件準(zhǔn)備讀取,它就通知該進(jìn)程,進(jìn)行相應(yīng)的讀寫操作
          • IO多路復(fù)用模型指的是:使用 單個(gè)進(jìn)程同時(shí)處理多個(gè)網(wǎng)絡(luò)連接IO,他的原理就是select、poll、epoll 不斷輪詢所負(fù)責(zé)的所有 socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。該模型的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快,而是在于能處理更多的連接。)
            • 多路指的是多個(gè) Socket 連接,就是指多個(gè)通道,也就是多個(gè)網(wǎng)絡(luò)連接的 IO
            • 復(fù)用指的是復(fù)用一個(gè)線程,多個(gè)通道或者多個(gè)網(wǎng)絡(luò)連接的IO可以注冊(cè)到或這說(shuō)復(fù)用在一個(gè)復(fù)用器上
        • I/O多路復(fù)用有兩種事件觸發(fā)模式,分別是 邊緣觸發(fā)(edge-triggered,ET)水平觸發(fā)(level-triggered,LT)。邊緣觸發(fā)的效率比水平觸發(fā)的效率要高,因?yàn)檫吘売|發(fā)可以減少 epoll_wait 的系統(tǒng)調(diào)用次數(shù),系統(tǒng)調(diào)用也是有一定的開(kāi)銷的的,畢竟也存在上下文的切換。【可以看看騰訊云社區(qū)的范蠡老師的epoll LT 模式和 ET 模式詳解】
          • 使用邊緣觸發(fā)模式時(shí),當(dāng)被監(jiān)控的 Socket 描述符上有可讀事件發(fā)生時(shí),服務(wù)器端只會(huì)從 epoll_wait 中蘇醒一次,即使進(jìn)程沒(méi)有調(diào)用 read 函數(shù)從內(nèi)核讀取數(shù)據(jù),也依然只蘇醒一次,因此 我們程序要保證一次性將內(nèi)核緩沖區(qū)的數(shù)據(jù)讀取完【驛站只發(fā)一條短信不會(huì)發(fā)第二條第三條讓你去取快遞的模式就叫邊緣觸發(fā)】。
            • 如果使用邊緣觸發(fā)模式,I/O 事件發(fā)生時(shí)只會(huì)通知一次,而且我們不知道到底能讀寫多少數(shù)據(jù),所以在收到通知后應(yīng)盡可能地讀寫數(shù)據(jù),以免錯(cuò)失讀寫的機(jī)會(huì)。因此,我們會(huì)循環(huán)從文件描述符讀寫數(shù)據(jù),那么如果文件描述符是阻塞的,沒(méi)有數(shù)據(jù)可讀寫時(shí),進(jìn)程會(huì)阻塞在讀寫函數(shù)那里,程序就沒(méi)辦法繼續(xù)往下執(zhí)行。所以,邊緣觸發(fā)模式一般和非阻塞 I/O 搭配使用,程序會(huì)一直執(zhí)行 I/O 操作,直到系統(tǒng)調(diào)用(如 read 和 write)返回錯(cuò)誤,錯(cuò)誤類型為 EAGAIN 或 EWOULDBLOCK。
          • 使用水平觸發(fā)模式時(shí),當(dāng)被監(jiān)控的 Socket 上有可讀事件發(fā)生時(shí),服務(wù)器端不斷地從 epoll_wait 中蘇醒,直到內(nèi)核緩沖區(qū)數(shù)據(jù)被 read 函數(shù)讀完才結(jié)束,目的是告訴我們有數(shù)據(jù)需要讀取。如果使用水平觸發(fā)模式,當(dāng)內(nèi)核通知文件描述符可讀寫時(shí),接下來(lái)還可以繼續(xù)去檢測(cè)它的狀態(tài),看它是否依然可讀或可寫。所以在收到通知后,沒(méi)必要一次執(zhí)行盡可能多的讀寫操作【如果快遞箱發(fā)現(xiàn)你的快遞沒(méi)有被取出,它就會(huì)不停地發(fā)短信通知你,直到你取出了快遞,它才消停,這個(gè)就是水平觸發(fā)的方式】
        • Linux 系統(tǒng)中的 select、poll、epoll等系統(tǒng)調(diào)用都是 I/O 多路復(fù)用的機(jī)制。(IO multiplexing就是我們常說(shuō)的select,poll,epoll,有些地方也稱這種IO方式為event driven IO。)
          • select/poll/epol獲取網(wǎng)絡(luò)事件的過(guò)程:在獲取事件時(shí),先把我們要關(guān)心的連接傳給內(nèi)核,再由內(nèi)核檢測(cè)
            • 如果沒(méi)有事件發(fā)生,線程只需阻塞在這個(gè)系統(tǒng)調(diào)用,而無(wú)需像線程池那樣輪訓(xùn)調(diào)用 read 操作來(lái)判斷是否有數(shù)據(jù)
            • 如果有事件發(fā)生,內(nèi)核會(huì)返回產(chǎn)生了事件的連接,線程就會(huì)從阻塞狀態(tài)返回,然后在用戶態(tài)中再處理這些連接對(duì)應(yīng)的業(yè)務(wù)即可
          • select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。它的基本原理就是select,poll,epoll這些個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程
            • 目前支持 IO 多路復(fù)用的系統(tǒng)調(diào)用有 select,epoll 等等。select 系統(tǒng)調(diào)用,目前幾乎在所有的操作系統(tǒng)上都有支持。
              • select 調(diào)用 :內(nèi)核提供的系統(tǒng)調(diào)用,select 它支持一次查詢多個(gè)系統(tǒng)調(diào)用的可用狀態(tài)。幾乎所有的操作系統(tǒng)都支持
                • 應(yīng)用進(jìn)程通過(guò)調(diào)用select函數(shù),可以同時(shí)監(jiān)控多個(gè)fd,在select函數(shù)監(jiān)控的fd中,只要有任何一個(gè)數(shù)據(jù)狀態(tài)準(zhǔn)備就緒了,select函數(shù)就會(huì)返回可讀狀態(tài),這時(shí)應(yīng)用進(jìn)程再發(fā)起recvfrom請(qǐng)求去讀取數(shù)據(jù)
              • epoll 調(diào)用 :linux 2.6 內(nèi)核,epoll 屬于 select 調(diào)用的增強(qiáng)版本,優(yōu)化了 IO 的執(zhí)行效率
          • select、poll 和 epoll 之間的區(qū)別:(select,poll,epoll 都是 IO 多路復(fù)用的機(jī)制, select,poll,epoll 本質(zhì)上都是同步 I/O,因?yàn)樗麄兌?strong>需要在讀寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫,也就是說(shuō)這個(gè)讀寫過(guò)程是阻塞的,而異步 I/O 則無(wú)需自己負(fù)責(zé)進(jìn)行讀寫,異步 I/O 的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。)
            • select:【多個(gè)網(wǎng)絡(luò)連接的 IO 可以注冊(cè)到一個(gè)復(fù)用器(select)上,當(dāng)用戶進(jìn)程調(diào)用了 select復(fù)用器,那么整個(gè)進(jìn)程會(huì)被阻塞。同時(shí),內(nèi)核會(huì)“監(jiān)視”所有 select復(fù)用器 負(fù)責(zé)的 socket,當(dāng)任何一個(gè) socket 中的數(shù)據(jù)準(zhǔn)備好了,select 復(fù)用器就會(huì)返回再?gòu)膬?nèi)核中拿數(shù)據(jù)。這個(gè)時(shí)候用戶進(jìn)程再調(diào)用 read 操作,將數(shù)據(jù)從內(nèi)核中拷貝到用戶進(jìn)程。用戶可以注冊(cè)多個(gè) socket,然后不斷地調(diào)用 select 讀取被激活的 socket,即可達(dá)到在同一個(gè)線程內(nèi)同時(shí)處理多個(gè) IO 請(qǐng)求的目的。而在同步阻塞模型中,必須通過(guò)多線程的方式才能達(dá)到這個(gè)目的。好比我們?nèi)ゲ蛷d吃飯,這次我們是幾個(gè)人一起去的,我們專門留了一個(gè)人在餐廳排號(hào)等位,其他人就去逛街了,等排號(hào)的朋友通知我們可以吃飯了,我們就直接去享用了。】
              • 時(shí)間復(fù)雜度 O(n)。
              • select/poll 只有水平觸發(fā)模式
              • select 僅僅知道有 I/O 事件發(fā)生,但 并不知道是哪幾個(gè)流,所以 只能無(wú)差別輪詢所有流,找出能讀出數(shù)據(jù)或者寫入數(shù)據(jù)的流,并對(duì)其進(jìn)行操作。所以 select 具有 O(n) 的無(wú)差別輪詢復(fù)雜度,同時(shí)處理的流越多,無(wú)差別輪詢時(shí)間就越長(zhǎng)。除了這個(gè),select還有幾個(gè)缺點(diǎn):
            • poll:因?yàn)榇嬖谶B接數(shù)限制,所以后來(lái)又提出了poll。與select相比,poll解決了連接數(shù)限制問(wèn)題。但是呢,select和poll一樣,還是需要通過(guò)遍歷文件描述符來(lái)獲取已經(jīng)就緒的socket。如果同時(shí)連接的大量客戶端,在一時(shí)刻可能只有極少處于就緒狀態(tài),伴隨著監(jiān)視的描述符數(shù)量的增長(zhǎng),效率也會(huì)線性下降
              • 時(shí)間復(fù)雜度 O(n)
              • select/poll 只有水平觸發(fā)模式
              • poll 本質(zhì)上和 select 沒(méi)有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后**查詢每個(gè) fd 對(duì)應(yīng)的設(shè)備狀態(tài), 但是它沒(méi)有最大連接數(shù)的限制**,原因是它是基于鏈表來(lái)存儲(chǔ)的
            • epoll:為了解決select/poll存在的問(wèn)題,多路復(fù)用模型epoll誕生,epoll采用事件驅(qū)動(dòng)來(lái)實(shí)現(xiàn)【epoll先通過(guò)epoll_ctl()來(lái)注冊(cè)一個(gè)fd(文件描述符),一旦基于某個(gè)fd就緒時(shí),內(nèi)核會(huì)采用回調(diào)機(jī)制,迅速激活這個(gè)fd,當(dāng)進(jìn)程調(diào)用epoll_wait()時(shí)便得到通知。這里去掉了遍歷文件描述符的坑爹操作,而是采用監(jiān)聽(tīng)事件回調(diào)的機(jī)制。這就是epoll的亮點(diǎn)。】int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...); listen(s, ...)int epfd = epoll_create(...);//先用e poll_create 創(chuàng)建一個(gè) epoll對(duì)象 epfd epoll_ctl(epfd, ...); //再通過(guò) epoll_ctl 將所有需要監(jiān)聽(tīng)的socket添加到epfd中。epoll 在內(nèi)核里使用紅黑樹(shù)來(lái)跟蹤進(jìn)程所有待檢測(cè)的文件描述字,把需要監(jiān)控的 socket 通過(guò) epoll_ctl() 函數(shù)加入內(nèi)核中的紅黑樹(shù)里while(1) {int n = epoll_wait(...);//最后調(diào)用 epoll_wait 等待數(shù)據(jù)。epoll 使用事件驅(qū)動(dòng)的機(jī)制,內(nèi)核里維護(hù)了一個(gè)鏈表來(lái)記錄就緒事件,當(dāng)某個(gè) socket 有事件發(fā)生時(shí),通過(guò)回調(diào)函數(shù)內(nèi)核會(huì)將其加入到這個(gè)就緒事件列表中,當(dāng)用戶調(diào)用 epoll_wait() 函數(shù)時(shí),只會(huì)返回有事件發(fā)生的文件描述符的個(gè)數(shù),不需要像 select/poll 那樣輪詢掃描整個(gè) socket 集合,大大提高了檢測(cè)的效率。for(接收到數(shù)據(jù)的socket){//處理} }
              • 時(shí)間復(fù)雜度 O(1)
              • epoll 可以理解為 event poll,不同于忙輪詢和無(wú)差別輪詢epoll 會(huì)把哪個(gè)流發(fā)生了怎樣的 I/O 事件通知我們。所以說(shuō) epoll 實(shí)際上是事件驅(qū)動(dòng)(每個(gè)事件關(guān)聯(lián)上 fd)的
              • epoll 默認(rèn)的觸發(fā)模式是水平觸發(fā),但是可以根據(jù)應(yīng)用場(chǎng)景設(shè)置為邊緣觸發(fā)模式。
              • 經(jīng)典的 C10K 問(wèn)題:如果服務(wù)器的內(nèi)存只有 2 GB,網(wǎng)卡是千兆的,能支持并發(fā) 1 萬(wàn)請(qǐng)求嗎【單機(jī)同時(shí)處理 1 萬(wàn)個(gè)請(qǐng)求的問(wèn)題。】從硬件資源角度看,對(duì)于 2GB 內(nèi)存千兆網(wǎng)卡的服務(wù)器,如果每個(gè)請(qǐng)求處理占用不到 200KB 的內(nèi)存和 100Kbit 的網(wǎng)絡(luò)帶寬就可以滿足并發(fā) 1 萬(wàn)個(gè)請(qǐng)求。不過(guò),要想真正實(shí)現(xiàn) C10K 的服務(wù)器,要考慮的地方在于服務(wù)器的網(wǎng)絡(luò) I/O 模型,效率低的模型,會(huì)加重系統(tǒng)開(kāi)銷。基于進(jìn)程或者線程模型的,其實(shí)還是有問(wèn)題的。新到來(lái)一個(gè) TCP 連接,就需要分配一個(gè)進(jìn)程或者線程,那么如果要達(dá)到 C10K,意味著要一臺(tái)機(jī)器維護(hù) 1 萬(wàn)個(gè)連接,相當(dāng)于要維護(hù) 1 萬(wàn)個(gè)進(jìn)程/線程,操作系統(tǒng)就算死扛也是扛不住的
        • IO多路復(fù)用適用如下場(chǎng)合:
          • 多路復(fù)用 IO 是在高并發(fā)場(chǎng)景中使用最為廣泛的一種 IO 模型,如 Java 的 NIO、Redis、Nginx 的底層實(shí)現(xiàn)就是此類 IO 模型的應(yīng)用,經(jīng)典的 Reactor 模式也是基于此類 IO 模型
          • 最常用的I/O事件通知機(jī)制就是I/O復(fù)用(I/O multiplexing)。Linux 環(huán)境中使用select/poll/epoll 實(shí)現(xiàn)I/O復(fù)用,I/O復(fù)用接口本身是阻塞的在應(yīng)用程序中通過(guò)I/O復(fù)用接口向內(nèi)核注冊(cè)fd所關(guān)注的事件,當(dāng)關(guān)注事件觸發(fā)時(shí),通過(guò)I/O復(fù)用接口的返回值通知到應(yīng)用程序
            • 以recv為例。I/O復(fù)用接口可以同時(shí)監(jiān)聽(tīng)多個(gè)I/O事件以提高事件處理效率。
          • 當(dāng)客戶處理多個(gè)描述字時(shí)(一般是交互式輸入和網(wǎng)絡(luò)套接口),必須使用I/O復(fù)用
          • 當(dāng)一個(gè)客戶同時(shí)處理多個(gè)套接口時(shí),而這種情況是可能的,但很少出現(xiàn)
          • 如果一個(gè)TCP服務(wù)器既要處理監(jiān)聽(tīng)套接口,又要處理已連接套接口,一般也要用到I/O復(fù)用
          • 如果一個(gè)服務(wù)器即要處理TCP,又要處理UDP,一般要使用I/O復(fù)用
          • 如果一個(gè)服務(wù)器要處理多個(gè)服務(wù)或多個(gè)協(xié)議,一般要使用I/O復(fù)用
          • 與多進(jìn)程和多線程技術(shù)相比,I/O多路復(fù)用技術(shù)的最大優(yōu)勢(shì)是系統(tǒng)開(kāi)銷小,系統(tǒng)不必創(chuàng)建進(jìn)程/線程,也不必維護(hù)這些進(jìn)程/線程,從而大大減小了系統(tǒng)的開(kāi)銷
            • 當(dāng)用戶進(jìn)程調(diào)用了select,那么整個(gè)進(jìn)程會(huì)被block,而同時(shí),kernel會(huì)“監(jiān)視”所有select負(fù)責(zé)的socket,當(dāng)任何一個(gè)socket中的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回。這個(gè)時(shí)候用戶進(jìn)程再調(diào)用read操作,將數(shù)據(jù)從kernel拷貝到用戶進(jìn)程。所以,I/O 多路復(fù)用的特點(diǎn)是通過(guò)一種機(jī)制一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符,而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài),select()函數(shù)就可以返回。這個(gè)圖和blocking IO的圖其實(shí)并沒(méi)有太大的不同,事實(shí)上因?yàn)镮O多路復(fù)用多了添加監(jiān)視 socket,以及調(diào)用 select 函數(shù)的額外操作,效率更差。還更差一些。因?yàn)檫@里需要使用兩個(gè)system call (select 和 recvfrom),而blocking IO只調(diào)用了一個(gè)system call (recvfrom)。但是,使用 select 以后最大的優(yōu)勢(shì)是用戶可以在一個(gè)線程內(nèi)同時(shí)處理多個(gè) socket 的 I/O 請(qǐng)求。用戶可以注冊(cè)多個(gè) socket,然后不斷地調(diào)用 select 讀取被激活的 socket,即可達(dá)到在同一個(gè)線程內(nèi)同時(shí)處理多個(gè) I/O 請(qǐng)求的目的。而在同步阻塞模型中,必須通過(guò)多線程的方式才能達(dá)到這個(gè)目的。所以,如果處理的連接數(shù)不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快,而是在于能處理更多的連接。)在IO multiplexing Model中,實(shí)際中,對(duì)于每一個(gè)socket,一般都設(shè)置成為non-blocking,但是,如上圖所示,整個(gè)用戶的process其實(shí)是一直被block的。只不過(guò)process是被select這個(gè)函數(shù)block,而不是被socket IO給block。
            • 因此對(duì)于IO多路復(fù)用模型來(lái)說(shuō):
              • 等待數(shù)據(jù)準(zhǔn)備就緒 (Waiting for the data to be ready) 「阻塞」
              • 將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process) 「阻塞」
      • Unix 常見(jiàn)的五種IO模型之四:異步非阻塞 I/O(asynchronous IO)AIO:linux下的asynchronous IO的流程



        • POSIX規(guī)范定義了一組異步操作I/O的接口,不用關(guān)心fd 是阻塞還是非阻塞,異步I/O是由內(nèi)核接管應(yīng)用層對(duì)fd的I/O操作。異步I/O向應(yīng)用層通知I/O操作完成的事件,這與前面介紹的I/O 復(fù)用模型、SIGIO模型通知事件就緒的方式明顯不同。以aio_read 實(shí)現(xiàn)異步讀取IO數(shù)據(jù)為例,在等待I/O操作完成期間,不會(huì)阻塞應(yīng)用程序
          • 異步其實(shí)之前咱們就接觸過(guò):通常,我們會(huì)有一個(gè)線程池用于執(zhí)行異步任務(wù),提交任務(wù)的線程將任務(wù)提交到線程池就可以立馬返回,不必等到任務(wù)真正完成。如果想要知道任務(wù)的執(zhí)行結(jié)果,通常是通過(guò)傳遞一個(gè)回調(diào)函數(shù)的方式,任務(wù)結(jié)束后去調(diào)用這個(gè)函數(shù)。同樣的原理,Java 中的異步 IO 也是一樣的,都是由一個(gè)線程池來(lái)負(fù)責(zé)執(zhí)行任務(wù),然后使用回調(diào)或自己去查詢結(jié)果異步 IO 主要是為了控制線程數(shù)量,減少過(guò)多的線程帶來(lái)的內(nèi)存消耗和 CPU 在線程調(diào)度上的開(kāi)銷
            • 異步 IO 一定存在一個(gè)線程池,這個(gè)線程池負(fù)責(zé)接收任務(wù)、處理 IO 事件、回調(diào)等。這個(gè)線程池就在 group 內(nèi)部【AsynchronousChannelGroup 這個(gè)類】,group 一旦關(guān)閉,那么相應(yīng)的線程池就會(huì)關(guān)閉。AsynchronousServerSocketChannels 和 AsynchronousSocketChannels 是屬于 group 的,當(dāng)我們調(diào)用 AsynchronousServerSocketChannel 或 AsynchronousSocketChannel 的 open() 方法的時(shí)候,相應(yīng)的 channel 就屬于默認(rèn)的 group,這個(gè) group 由 JVM 自動(dòng)構(gòu)造并管理
              • 配置這個(gè)默認(rèn)的 group,可以在 JVM 啟動(dòng)參數(shù)中指定以下系統(tǒng)變量:
              • 使用自己定義的 group,這樣可以對(duì)其中的線程進(jìn)行更多的控制,使用以下幾個(gè)方法:
              • group 的使用:
            • AsynchronousFileChannels 不屬于 group。但是它們也是關(guān)聯(lián)到一個(gè)線程池的,如果不指定,會(huì)使用系統(tǒng)默認(rèn)的線程池,如果想要使用指定的線程池,可以在實(shí)例化的時(shí)候使用以下方法:
        • AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改進(jìn)版 NIO 2,它是異步 IO 模型。【JDK 7 對(duì)原有的 NIO 進(jìn)行了改進(jìn)。第一個(gè)改進(jìn)是提供了全面的文件 I/O 相關(guān) API第二個(gè)改進(jìn)是增加了異步的基于 Channel 的 IO 機(jī)制
          • 異步 IO 是基于事件和回調(diào)機(jī)制實(shí)現(xiàn)的,也就是應(yīng)用操作之后會(huì)直接返回,不會(huì)堵塞在那里,當(dāng)后臺(tái)處理完成,操作系統(tǒng)會(huì)通知相應(yīng)的線程進(jìn)行后續(xù)的操作。
        • 用戶進(jìn)程發(fā)起aio_read調(diào)用之后,立刻就可以開(kāi)始去做其它的事。而另一方面,從kernel的角度,當(dāng)它發(fā)現(xiàn)一個(gè)asynchronous read之后,首先它會(huì)立刻返回,所以不會(huì)對(duì)用戶進(jìn)程產(chǎn)生任何block。然后,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)signal,告訴它read操作完成了
          • 異步 I/O 模型使用了 Proactor 設(shè)計(jì)模式實(shí)現(xiàn)了這一機(jī)制。因此對(duì)異步IO模型來(lái)說(shuō):
            • 等待數(shù)據(jù)準(zhǔn)備就緒 (Waiting for the data to be ready) 「非阻塞」
            • 將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process) 「非阻塞」
        • AIO:JDK1.7引入NIO2.0,提供了異步文件通道和異步套接字通道的實(shí)現(xiàn)。其底層在Windows上是通過(guò)IOCP實(shí)現(xiàn),在Linux上是通過(guò)IO復(fù)用Epoll來(lái)模擬實(shí)現(xiàn)的。在JAVA NIO框架中,Selector它負(fù)責(zé)代替應(yīng)用查詢中所有已注冊(cè)的通道到操作系統(tǒng)中進(jìn)行IO事件輪詢、管理當(dāng)前注冊(cè)的通道集合,定位發(fā)生事件的通道等操作。但是在JAVA AIO框架中,由于應(yīng)用程序不是輪詢方式,而是訂閱-通知方式,所以不再需要Selector(選擇器)了,改由Channel通道直接到操作系統(tǒng)注冊(cè)監(jiān)聽(tīng) 。【More New IO,或稱 NIO.2,隨 JDK 1.7 發(fā)布,包括了引入異步 IO 接口和 Paths 等文件訪問(wèn)接口。】
          • JAVA AIO框架中,只實(shí)現(xiàn)了兩種網(wǎng)絡(luò)IO通道:
            • AsynchronousServerSocketChannel(服務(wù)器監(jiān)聽(tīng)通道)
              • 這個(gè)類對(duì)應(yīng)的是非阻塞 IO 的 ServerSocketChannel。
              • 用回調(diào)函數(shù)的方式寫一個(gè)簡(jiǎn)單的服務(wù)端:
                • ChannelHandler 類
                • 自定義的 Attachment 類:
                • 接下來(lái)可以接收客戶端請(qǐng)求了
            • AsynchronousSocketChannel(Socket套接字通道)
              • 使用 AsynchronousSocketChannel 的方式和非阻塞 IO 基本類似。
            • AsynchronousFileChannel:異步的文件 IO
              • 文件 IO 在所有的操作系統(tǒng)中都不支持非阻塞模式,但是我們可以對(duì)文件 IO 采用異步的方式來(lái)提高性能
              • AsynchronousFileChannel 里面的一些重要的接口:


          • Java 異步 IO 提供了兩種使用方式,分別是 返回 java.util.concurrent.Future 實(shí)例和使用CompletionHandler 回調(diào)函數(shù)
            • 返回 java.util.concurrent.Future 實(shí)例:JDK 線程池就是這么使用的
              • Future 接口的幾個(gè)方法語(yǔ)義:
            • 提供 CompletionHandler 回調(diào)函數(shù):
              • java.nio.channels.CompletionHandler 接口定義:
        • 異步 I/O 并沒(méi)有涉及到 PageCache,所以使用異步 I/O 就意味著要繞開(kāi) PageCache。繞開(kāi) PageCache 的 I/O 叫 直接 I/O使用 PageCache 的 I/O 則叫緩存 I/O。通常,對(duì)于磁盤,異步 I/O 只支持直接 I/O在高并發(fā)的場(chǎng)景下,針對(duì)大文件的傳輸?shù)姆绞?#xff0c;應(yīng)該使用「異步 I/O + 直接 I/O」來(lái)替代零拷貝技術(shù)就可以無(wú)阻塞地讀取文件了
          • 傳輸文件的時(shí)候,我們要根據(jù)文件的大小來(lái)使用不同的方式:
            • 傳輸 大文件 的時(shí)候,使用「異步 I/O + 直接 I/O
            • 傳輸小文件的時(shí)候,則使用「零拷貝技術(shù)」
      • Unix 常見(jiàn)的五種IO模型之五:信號(hào)驅(qū)動(dòng)式IO模型(signal-driven IO model)
        • 除了I/O復(fù)用方式通知I/O事件,還可以通過(guò)SIGIO信號(hào)來(lái)通知I/O事件。兩者不同的是,在等待數(shù)據(jù)達(dá)到期間,I/O復(fù)用是會(huì)阻塞應(yīng)用程序,而SIGIO方式是不會(huì)阻塞應(yīng)用程序的
        • 首先我們?cè)试S socket 進(jìn)行信號(hào)驅(qū)動(dòng) I/O,并安裝一個(gè)信號(hào)處理函數(shù),進(jìn)程繼續(xù)運(yùn)行并不阻塞。當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí),進(jìn)程會(huì)收到一個(gè)SIGIO信號(hào),可以在信號(hào)處理函數(shù)中調(diào)用 I/O 操作函數(shù)處理數(shù)據(jù)。
  • 零拷貝技術(shù):上面咱們看到了 應(yīng)用進(jìn)程的一次完整的讀寫操作,都需要在用戶空間與內(nèi)核空間中來(lái)回拷貝,并且每一次拷貝,都需要 CPU 進(jìn)行一次上下文切換(由用戶進(jìn)程切換到系統(tǒng)內(nèi)核,或由系統(tǒng)內(nèi)核切換到用戶進(jìn)程),這樣是不是很浪費(fèi) CPU 和性能呢?那有沒(méi)有什么方式,可以減少進(jìn)程間的數(shù)據(jù)拷貝,提高數(shù)據(jù)傳輸?shù)男誓?/strong>?========零拷貝技術(shù)零拷貝是指計(jì)算機(jī)執(zhí)行IO操作時(shí),CPU不需要將數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域復(fù)制到另一個(gè)存儲(chǔ)區(qū)域,從而可以減少上下文切換以及CPU的拷貝時(shí)間。它是一種I/O操作優(yōu)化技術(shù)。取消用戶空間與內(nèi)核空間之間的數(shù)據(jù)拷貝操作,應(yīng)用進(jìn)程每一次的讀寫操作,都可以通過(guò)一種方式,讓應(yīng)用進(jìn)程直接向用戶空間寫入或者讀取數(shù)據(jù),(效果就如同直接向內(nèi)核空間寫入或者讀取數(shù)據(jù)一樣,然后再通過(guò) DMA 將內(nèi)核中的數(shù)據(jù)拷貝到網(wǎng)卡,或?qū)⒕W(wǎng)卡中的數(shù)據(jù) copy 到內(nèi)核)】

    • 傳統(tǒng)IO的讀寫流程,包括了4次上下文切換(4次用戶態(tài)和內(nèi)核態(tài)的切換),4次數(shù)據(jù)拷貝(兩次CPU拷貝以及兩次的DMA拷貝)。零拷貝只是減少了用戶態(tài)/內(nèi)核態(tài)的切換次數(shù)以及CPU拷貝的次數(shù)
      • 升級(jí)到epoll后,為了溝通有沒(méi)有數(shù)據(jù)還是得用戶態(tài)內(nèi)核態(tài)把fd相關(guān)數(shù)據(jù)拷來(lái)拷去,所以為了減少拷貝的次數(shù),并且用mmap這個(gè)系統(tǒng)調(diào)用實(shí)現(xiàn)了一個(gè)用戶態(tài)和內(nèi)核態(tài)共享的空間,
    • 是不是用戶空間與內(nèi)核空間都將數(shù)據(jù)寫到一個(gè)地方,就不需要拷貝了?此時(shí)有沒(méi)有想到虛擬內(nèi)存?零拷貝有兩種解決方式,分別是 mmap+write 方式和 sendfile 方式,mmap+write 方式的核心原理就是通過(guò)虛擬內(nèi)存來(lái)解決的
    • 實(shí)現(xiàn)零拷貝的兩種方式:
      • mmap + write:
        • mmap就是用了虛擬內(nèi)存這個(gè)特點(diǎn),mmap將內(nèi)核中的讀緩沖區(qū)與用戶空間的緩沖區(qū)進(jìn)行映射,以減少數(shù)據(jù)拷貝次數(shù)!

          • mmap+write實(shí)現(xiàn)的零拷貝,I/O發(fā)生了4次用戶空間與內(nèi)核空間的上下文切換,以及3次數(shù)據(jù)拷貝(包括了2次DMA拷貝和1次CPU拷貝)
        • read() 系統(tǒng)調(diào)用的過(guò)程中會(huì)把內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到用戶的緩沖區(qū)里,于是為了減少這一步開(kāi)銷,我們可以用 mmap() 替換 read() 系統(tǒng)調(diào)用函數(shù)。mmap() 系統(tǒng)調(diào)用函數(shù)會(huì)直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)映射到用戶空間,這樣,操作系統(tǒng)內(nèi)核與用戶空間就不需要再進(jìn)行任何的數(shù)據(jù)拷貝操作
      • sendfile
        • 在 Linux 內(nèi)核版本 2.1 中,提供了一個(gè)專門發(fā)送文件的系統(tǒng)調(diào)用函數(shù) sendfile():ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);它的前兩個(gè)參數(shù)分別是目的端和源端的文件描述符,后面兩個(gè)參數(shù)是源端的偏移量和復(fù)制數(shù)據(jù)的長(zhǎng)度,返回值是實(shí)際復(fù)制數(shù)據(jù)的長(zhǎng)度。首先,sendfile可以替代前面的 read() 和 write() 這兩個(gè)系統(tǒng)調(diào)用,這樣就可以減少一次系統(tǒng)調(diào)用,也就減少了 2 次上下文切換的開(kāi)銷。其次,該sendfile系統(tǒng)調(diào)用和mmap一樣,可以直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,不再拷貝到用戶態(tài),這樣就只有 2 次上下文切換,和 3 次數(shù)據(jù)拷貝
          • sendfile實(shí)現(xiàn)的零拷貝,I/O發(fā)生了2次用戶空間與內(nèi)核空間的上下文切換,以及3次數(shù)據(jù)拷貝。其中3次數(shù)據(jù)拷貝中和mmap+write一樣,包括了2次DMA拷貝和1次CPU拷貝。
          • 帶有DMA收集拷貝功能的sendfile:但是這還不是真正的零拷貝技術(shù),如果網(wǎng)卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術(shù)(和普通的 DMA 有所不同),我們可以進(jìn)一步減少通過(guò) CPU 把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)的過(guò)程。【linux 2.4版本之后,對(duì)sendfile做了優(yōu)化升級(jí),引入SG-DMA技術(shù),SG-DMA技術(shù)其實(shí)就是對(duì)DMA拷貝加入了scatter/gather操作,它可以直接從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)讀取到網(wǎng)卡。使用這個(gè)特點(diǎn)搞零拷貝,即還可以多省去一次CPU拷貝。】
            • sendfile+DMA scatter/gather實(shí)現(xiàn)的零拷貝,I/O發(fā)生了2次用戶空間與內(nèi)核空間的上下文切換,以及2次數(shù)據(jù)拷貝。其中2次數(shù)據(jù)拷貝都是包DMA拷貝。這就是真正的 零拷貝(Zero-copy) 技術(shù),全程都沒(méi)有通過(guò)CPU來(lái)搬運(yùn)數(shù)據(jù),所有的數(shù)據(jù)都是通過(guò)DMA來(lái)進(jìn)行傳輸?shù)?/strong>。
            • 從 Linux 內(nèi)核 2.4 版本開(kāi)始起,對(duì)于支持網(wǎng)卡支持 SG-DMA 技術(shù)的情況下, sendfile() 系統(tǒng)調(diào)用的過(guò)程發(fā)生了點(diǎn)變化:采用了零拷貝
        • Kafka 這個(gè)開(kāi)源項(xiàng)目,就利用了零拷貝技術(shù),從而大幅提升了 I/O 的吞吐率,這也是 Kafka 在處理海量數(shù)據(jù)為什么這么快的原因之一,它調(diào)用了 Java NIO 庫(kù)里的 transferTo 方法。如果 Linux 系統(tǒng)支持 sendfile() 系統(tǒng)調(diào)用,那么 transferTo() 實(shí)際上最后就會(huì)使用到 sendfile() 系統(tǒng)調(diào)用函數(shù)。
        • Nginx 也支持零拷貝技術(shù),一般默認(rèn)是開(kāi)啟零拷貝技術(shù),這樣有利于提高文件傳輸?shù)男?/li>
    • 零拷貝更細(xì)致的理論點(diǎn)這里,然后ctrl+F,搜零拷貝
      • 這個(gè)零拷貝是操作系統(tǒng)層面上的零拷貝,主要目標(biāo)是避免用戶空間與內(nèi)核空間之間的數(shù)據(jù)拷貝操作,可以提升 CPU 的利用率。
      • Netty 相關(guān)的零拷貝技術(shù)
    • RPC 框架在網(wǎng)絡(luò)通信框架的選型上,我們最優(yōu)的選擇是基于 Reactor 模式實(shí)現(xiàn)的框架,如 Java 語(yǔ)言,首選的便是 Netty 框架
      • Netty 的零拷貝則不大一樣,他完全站在了用戶空間上,也就是 JVM 上,Netty 的零拷貝主要是偏向于數(shù)據(jù)操作的優(yōu)化上
        • 在傳輸過(guò)程中,RPC 并不會(huì)把請(qǐng)求參數(shù)的所有二進(jìn)制數(shù)據(jù)整體一下子發(fā)送到對(duì)端機(jī)器上,中間可能會(huì)拆分成好幾個(gè)數(shù)據(jù)包,也可能會(huì)合并其他請(qǐng)求的數(shù)據(jù)包,所以消息都需要有邊界。那么一端的機(jī)器收到消息之后,就需要對(duì)數(shù)據(jù)包進(jìn)行處理,根據(jù)邊界對(duì)數(shù)據(jù)包進(jìn)行分割和合并,最終獲得一條完整的消息。收到消息后,對(duì)數(shù)據(jù)包的分割和合并是在用戶空間【因?yàn)閷?duì)數(shù)據(jù)包的處理工作都是由應(yīng)用程序來(lái)處理的】。這里也是會(huì)存在拷貝操作的,但是 不是在用戶空間與內(nèi)核空間之間的拷貝,是用戶空間內(nèi)部?jī)?nèi)存中的拷貝處理操作。Netty 的零拷貝就是為了解決這個(gè)問(wèn)題,在用戶空間對(duì)數(shù)據(jù)操作進(jìn)行優(yōu)化
    • Netty 是怎么對(duì)數(shù)據(jù)操作進(jìn)行優(yōu)化的呢?
      • Netty 提供了 CompositeByteBuf 類,它可以 將多個(gè) ByteBuf 合并為一個(gè)邏輯上的 ByteBuf,避免了各個(gè) ByteBuf 之間的拷貝【ByteBuf 支持 slice 操作,因此可以將 ByteBuf 分解為多個(gè)共享同一個(gè)存儲(chǔ)區(qū)域的 ByteBuf,避免了內(nèi)存的拷貝】
      • 通過(guò) wrap 操作,我們可以將 byte[] 數(shù)組、ByteBuf、ByteBuffer 等包裝成一個(gè) Netty ByteBuf 對(duì)象, 進(jìn)而避免拷貝操作。
    • Netty 框架中很多內(nèi)部的 ChannelHandler 實(shí)現(xiàn)類,都是通過(guò) CompositeByteBuf、slice、wrap 操作來(lái)處理 TCP 傳輸中的拆包與粘包問(wèn)題的。etty 的 ByteBuffer 可以采用 Direct Buffers,使用堆外直接內(nèi)存進(jìn)行 Socket 的讀寫操作,最終的效果與虛擬內(nèi)存所實(shí)現(xiàn)的效果是一樣的。Netty 還提供 FileRegion 中包裝 NIO 的 FileChannel.transferTo() 方法實(shí)現(xiàn)了零拷貝,這與 Linux 中的 sendfile 方式在原理上也是一樣的
  • 除了上面基本的I/O模型之外,還有如下集中模型:

    • 事件處理模型Reactor 和Proactor兩種事件處理模型
      • 網(wǎng)絡(luò)設(shè)計(jì)模式中,如何處理各種I/O事件是其非常重要的一部分,Reactor 和Proactor兩種事件處理模型應(yīng)運(yùn)而生。上面提到將I/O分為同步I/O 和 異步I/O,可以使用同步I/O實(shí)現(xiàn)Reactor模型,使用異步I/O實(shí)現(xiàn)Proactor模型
      • Reactor事件處理模型:Reactor模型是同步I/O事件處理的一種常見(jiàn)模型
        • 一個(gè)典型的Reactor模型類圖結(jié)構(gòu)
        • Reactor的核心思想:將關(guān)注的I/O事件注冊(cè)到多路復(fù)用器上,一旦有I/O事件觸發(fā),將事件分發(fā)到事件處理器中、執(zhí)行就緒I/O事件對(duì)應(yīng)的處理函數(shù)中。模型中有三個(gè)重要的組件:
          • 多路復(fù)用器:由操作系統(tǒng)提供接口,Linux提供的I/O復(fù)用接口有select、poll、epoll;
          • 事件分離器:將多路復(fù)用器返回的就緒事件分發(fā)到事件處理器中;
          • 事件處理器:處理就緒事件處理函數(shù)
        • Reactor模型工作的簡(jiǎn)化流程:
      • Proactor事件處理模型:
        • 與Reactor不同的是,Proactor使用異步I/O系統(tǒng)接口將I/O操作托管給操作系統(tǒng)Proactor模型中分發(fā)處理異步I/O完成事件,并調(diào)用相應(yīng)的事件處理接口來(lái)處理業(yè)務(wù)邏輯
        • Proactor類結(jié)構(gòu):
        • Proactor模型的簡(jiǎn)化的工作流程:
        • 同步I/O模擬Proactor:
    • 并發(fā)模式:多線程、多進(jìn)程的編程的模式【多進(jìn)程/線程模型】
      • 在I/O密集型的程序,采用并發(fā)方式可以提高CPU的使用率可采用多進(jìn)程和多線程兩種方式實(shí)現(xiàn)并發(fā)。當(dāng)前有高效的兩種并發(fā)模式,半同步/半異步模式、Follower/Leader模式
        • 多進(jìn)程/多線程模型:
          • 通過(guò)這種方式確實(shí)解決了單進(jìn)程 server 阻塞無(wú)法處理其他 client 請(qǐng)求的問(wèn)題,但眾所周知 fork 創(chuàng)建子進(jìn)程是非常耗時(shí)的,包括頁(yè)表的復(fù)制,進(jìn)程切換時(shí)頁(yè)表的切換等都非常耗時(shí),每來(lái)一個(gè)請(qǐng)求就創(chuàng)建一個(gè)進(jìn)程顯然是無(wú)法接受的。為了節(jié)省進(jìn)程創(chuàng)建的開(kāi)銷,于是有人提出把多進(jìn)程改成多線程,創(chuàng)建線程(使用 pthread_create)的開(kāi)銷確實(shí)小了很多,但同樣的,線程與進(jìn)程一樣,都需要占用堆棧等資源,而且碰到阻塞,喚醒等都涉及到用戶態(tài),內(nèi)核態(tài)的切換,這些都極大地消耗了性能
      • 半同步/半異步模式:
        • 并發(fā)模式中的“同步”、“異步”與 I/O模型中的“同步”、“異步”是兩個(gè)不同的概念:
          • 并發(fā)模式中,“同步”指程序按照代碼順序執(zhí)行,“異步”指程序依賴事件驅(qū)動(dòng),如圖12 所示并發(fā)模式的“同步”執(zhí)行和“異步”執(zhí)行的讀操作;
            • 同步讀操作示意圖
            • 異步讀操作示意圖
          • I/O模型中,“同步”、“異步”用來(lái)區(qū)分I/O操作的方式是主動(dòng)通過(guò)I/O操作拿到結(jié)果,還是由內(nèi)核異步的返回操作結(jié)果
        • 半同步/半異步工作流程
        • 半同步/半反應(yīng)堆模式
          • 考慮將兩種事件處理模型,即Reactor和Proactor,與幾種I/O模型結(jié)合在一起,那么半同步/半異步模式就演變?yōu)?strong>半同步/半反應(yīng)堆模式。
          • 使用Reactor的方式:
            • 工作流程:
          • 將Reactor替換為Proactor
            • 工作流程:
          • 半同步/半反應(yīng)堆模式有明顯的缺點(diǎn):
          • 半同步/半反應(yīng)堆模式的演變模式:
      • Follower/Leader模式
        • Follower/Leader是多個(gè)工作線程輪流進(jìn)行事件監(jiān)聽(tīng)、事件分發(fā)、處理事件的模式。在Follower/Leader模式工作的任何一個(gè)時(shí)間點(diǎn),只有一個(gè)工作線程處理成為L(zhǎng)eader ,負(fù)責(zé)I/O事件監(jiān)聽(tīng),而其他線程都是Follower,并等待成為L(zhǎng)eader

        • Leader/Follow模式的工作線程的三種狀態(tài)的轉(zhuǎn)移關(guān)系
    • Swoole異步網(wǎng)絡(luò)模型分析

巨人的肩膀:
Linux網(wǎng)絡(luò)編程
B站OS課程各位老師
操作系統(tǒng)概論
https://xiaolincoding.com/
很好的一篇文章,既講了Java 中 IO 相關(guān)的理論知識(shí),并通過(guò)多個(gè)代碼案例加深了理解。很贊
https://learn.lianglianglee.com/%E6%96%87%E7%AB%A0/Java%20NIO%E6%B5%85%E6%9E%90.md
Javadoop
javaGuide
CS-Note
清華大學(xué)OS課
在 Windows 操作系統(tǒng)中,提供了一個(gè)叫做 I/O Completion Ports 的方案,通常簡(jiǎn)稱為 IOCP,操作系統(tǒng)負(fù)責(zé)管理線程池,其性能非常優(yōu)異,所以在 Windows 中 JDK 直接采用了 IOCP 的支持,使用系統(tǒng)支持,把更多的操作信息暴露給操作系統(tǒng),也使得操作系統(tǒng)能夠?qū)ξ覀兊?IO 進(jìn)行一定程度的優(yōu)化。

程序員田螺老師的零拷貝詳解

總結(jié)

以上是生活随笔為你收集整理的java基础巩固-宇宙第一AiYWM:为了维持生计,四大基础之OS_Part_2整起~IO们那些事【包括五种IO模型:(BIO、NIO、IO多路复用、信号驱动、AIO);零拷贝、事件处理及并发等模型】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

婷婷四房综合激情五月 | 中文成人字幕 | 久久久久久久久福利 | 日韩欧美电影在线观看 | 狠狠干美女 | 91porny九色91啦中文 | 在线观看av片| 96av视频 | 国产精品一区二区精品视频免费看 | 欧美日韩1区 | 最新影院 | 免费看三级网站 | 最新av电影网址 | 国产免费又爽又刺激在线观看 | 日韩伦理一区二区三区av在线 | 一区二区理论片 | 免费观看v片在线观看 | 亚洲精品视频在线播放 | 日韩在线观 | 成年人免费在线 | 久久久久国产精品免费网站 | 91精品免费看 | 欧美日韩不卡在线观看 | 成人午夜影院在线观看 | 中文字幕免费观看 | 99色免费 | av福利资源 | 久久久久久综合网天天 | 欧美日本一二三 | 九九九视频在线 | 999成人精品 | 精品国产一区二区久久 | 97超碰人| 综合色中色| 国产福利91精品 | 黄a在线看| 国产精品久久久久久久7电影 | 亚洲精品高清一区二区三区四区 | 日韩久久精品一区二区 | 亚洲国产一区二区精品专区 | 欧美天天干 | 国产高清av在线播放 | 在线观看aa | 视频 国产区 | 91视频专区| 久久成人综合 | 日韩超碰| 欧美日韩国产成人 | 天天操操| 天天干夜夜爽 | 亚洲一区日韩在线 | 特级黄色电影 | 91精品国产一区二区三区 | 国产精品午夜8888 | 婷婷深爱五月 | 国产精品一区二区三区在线看 | 中文字幕丰满人伦在线 | 日韩av中文在线观看 | 五月天欧美精品 | 亚洲欧美视频在线 | 亚洲aaa级| 国产精品永久免费 | 色免费在线 | 九九九热 | 国产激情久久久 | 欧美成人999 | 国产白浆在线观看 | 一级一片免费视频 | 亚洲成 人精品 | 中中文字幕av在线 | 国内精自线一二区永久 | 99久久婷婷国产综合精品 | 超碰在线中文字幕 | 日本中文在线 | 国产亚洲成av人片在线观看桃 | 国产精品久久久一区二区三区网站 | 91中文字幕网 | 午夜 在线 | 天天综合网在线 | 亚洲欧美婷婷六月色综合 | 中文字幕 国产视频 | 国产视频手机在线 | 高清不卡一区二区在线 | 欧美成人精品在线 | 免费特级黄毛片 | 亚洲欧美视频在线观看 | 精品国产乱码久久久久 | 日本性视频 | 精品久久久久一区二区国产 | 99久久夜色精品国产亚洲 | 超碰官网 | 久久久久欧美精品 | 五月婷婷一区二区三区 | 在线三级av | 狠狠狠狠狠狠操 | 欧美日性视频 | 中文字幕第一页在线播放 | 亚洲午夜激情网 | 黄色小说在线观看视频 | 亚洲黄色网络 | 中文字幕在线字幕中文 | 天堂va欧美va亚洲va老司机 | 亚洲精品在线观看的 | 久久久久久视频 | 五月婷婷丁香综合 | 国产超碰在线观看 | 91久久爱热色涩涩 | 精品久久久久久国产偷窥 | 久久国产精品第一页 | 成人av手机在线 | 五月天婷婷在线视频 | 私人av| 国产乱码精品一区二区蜜臀 | 亚洲久草网 | 激情久久综合 | 美女视频黄网站 | 久久国产一区二区 | 成人在线观看资源 | 亚洲精品视频免费在线 | 成人a级免费视频 | www色片| 干干干操操操 | 808电影| 91看国产| 免费观看www小视频的软件 | 国产第一二区 | 91精品天码美女少妇 | 免费看三级 | 日色在线视频 | 中文字幕中文字幕在线中文字幕三区 | 少妇bbw搡bbbb搡bbbb | 色久综合| 五月婷婷丁香综合 | 97国产超碰| 欧美午夜精品久久久久久孕妇 | 国产美女免费观看 | 成人资源在线播放 | 国产精品网站一区二区三区 | 涩涩爱夜夜爱 | 成人在线视频观看 | 最新av在线播放 | 91在线一区 | 天天插天天爽 | 国产美女精品视频 | 中文字幕乱在线伦视频中文字幕乱码在线 | 久久久久国产一区二区 | 91香蕉视频在线 | 波多野结衣电影一区二区 | 92精品国产成人观看免费 | 98精品国产自产在线观看 | 国产午夜精品一区二区三区嫩草 | 麻豆国产视频 | 国产精品一区二区美女视频免费看 | 天天色官网 | 能在线观看的日韩av | 日韩免费区| 欧美一区二区三区在线观看 | 成 人 黄 色 免费播放 | 亚洲永久av | av福利在线免费观看 | 亚洲精品理论 | 成年人视频免费在线播放 | 97看片吧 | 欧美日韩一区二区三区免费视频 | 97香蕉久久国产在线观看 | 欧美精品免费在线观看 | 国产精品九九久久99视频 | 国产手机在线观看视频 | 久久久一本精品99久久精品 | 国产成人精品一区二区三区福利 | 午夜三级理论 | 亚洲国产网站 | 欧美精品免费在线观看 | 国产亚洲精品成人av久久ww | 国内精品美女在线观看 | 999在线视频 | 日本精品视频免费观看 | 91网页版在线观看 | 欧美日韩性 | 国产精品手机在线观看 | 中文字幕中文字幕在线一区 | 在线之家官网 | a黄在线观看 | 国产精品日韩 | 免费视频在线观看网站 | 国产成人精品在线观看 | 高清国产午夜精品久久久久久 | 日本中文字幕在线电影 | 私人av | 国偷自产中文字幕亚洲手机在线 | 国产美女精品在线 | 国产精品网址在线观看 | 国产第一页精品 | 美女很黄免费网站 | 欧美精品视 | 久久香蕉电影 | 欧美日韩观看 | 亚洲h视频在线 | 香蕉影院在线播放 | 五月婷婷伊人网 | 在线日韩精品视频 | 欧美精品乱码久久久久 | 天天曰夜夜操 | 精品国产1区 | 欧美91精品| 日韩av网站在线播放 | 伊人五月在线 | 97视频免费在线看 | 国产精品区在线观看 | 狠狠干综合| 久久不卡日韩美女 | 四虎永久免费在线观看 | 成 人 黄 色视频免费播放 | 麻豆视频www| 久久精品综合视频 | 国产精品久久一卡二卡 | 91看片一区二区三区 | 99免费观看视频 | 久久精品视频3 | 狠狠综合久久av | 久久久久国产精品厨房 | 天天撸夜夜操 | 日日摸日日添夜夜爽97 | 久久99久久久久久 | 日韩中文久久 | 黄色三级视频片 | 亚洲精品视频在线看 | 五月天丁香综合 | 久久激情视频免费观看 | 色婷婷激情网 | 99久久er热在这里只有精品66 | 亚洲精品中文字幕视频 | 久久综合欧美精品亚洲一区 | www.狠狠操.com | 国产h片在线观看 | 91在线你懂的 | 17videosex性欧美 | 97国产视频 | 国偷自产中文字幕亚洲手机在线 | 天天射天 | 国产精品一区免费看8c0m | 国产亚洲日 | 少妇高潮流白浆在线观看 | 国产91免费观看 | 激情久久五月 | 欧美极品一区二区三区 | 福利视频网址 | 色视频在线免费 | 综合色在线观看 | 亚洲国产精品第一区二区 | 免费观看9x视频网站在线观看 | 亚洲九九爱 | 亚洲激情一区二区三区 | 亚洲免费专区 | 亚洲成人免费观看 | 免费国产一区二区 | 一区二区三区 亚洲 | 国产精品入口麻豆 | 成人免费共享视频 | 一级一片免费视频 | 婷婷六月综合网 | 欧美 日韩 国产 成人 在线 | 成片免费观看视频大全 | 国产高清永久免费 | 国产精品久久久久久久久久妇女 | 少妇精品久久久一区二区免费 | 97超级碰 | 黄网站www| 国产精品国产三级国产不产一地 | 丁香六月婷婷激情 | 亚洲人人网| 国产在线播放一区二区 | 久久久www免费电影网 | 99久久精品国产一区二区三区 | 久久99久久99精品免费看小说 | 免费在线观看国产精品 | 黄色网www | 日韩毛片一区 | 欧美日韩不卡一区 | av免费网站在线观看 | 日本性久久 | 91在线操| 男女激情网址 | 在线看av网址 | av激情五月| 黄色免费大片 | 中文字幕一区在线 | 免费观看完整版无人区 | 亚洲精品在线国产 | 在线视频 你懂得 | 国产精品久久亚洲 | 在线国产精品视频 | 久久综合五月婷婷 | 毛片一二区 | 日日躁夜夜躁xxxxaaaa | 一级久久精品 | 日韩网站一区二区 | 香蕉在线观看视频 | 激情久久五月 | 国产成人av福利 | 国产精品久久久久免费 | av大全免费在线观看 | 亚洲欧美日韩在线看 | 毛片99| 丁香花在线观看视频在线 | aaa日本高清在线播放免费观看 | 91精品办公室少妇高潮对白 | 91视频高清 | 精品专区一区二区 | 国产极品尤物在线 | 久久激情小说 | 高清在线观看av | 国产在线播放一区二区 | 色com网 | 91久久偷偷做嫩草影院 | 久久久久久久福利 | 日日干干夜夜 | 久久99精品久久久久久久久久久久 | 婷婷色影院 | 日韩欧美一区二区三区在线观看 | 日韩av黄| 极品嫩模被强到高潮呻吟91 | 99国产在线视频 | 天天综合网~永久入口 | 天天干干 | 日韩成人免费在线电影 | 久久综合中文字幕 | 国产丝袜在线 | 就要干b | 色网站中文字幕 | 国产黄色电影 | 日韩.com | 免费av在线播放 | 97国产一区二区 | 久久成人精品电影 | 狠狠色噜噜狠狠 | 久久成人综合视频 | 亚洲精品国产精品乱码在线观看 | 婷婷五月在线视频 | 天天天干天天天操 | 日本午夜在线观看 | 亚洲春色奇米影视 | 国产视频在线观看一区 | 日韩精品一区不卡 | 99在线精品视频在线观看 | 高清色免费 | 一区二区视频电影在线观看 | 日韩中文字幕免费视频 | 国产护士hd高朝护士1 | 中文字幕在线字幕中文 | 天天综合网~永久入口 | 国产午夜激情视频 | 日韩av网址在线 | 成人久久亚洲 | 久久综合欧美精品亚洲一区 | 久久国产精品99久久久久久丝袜 | 国产一级三级 | 99国产精品久久久久老师 | 丝袜美腿在线播放 | 国产麻豆精品免费视频 | 亚洲精品视频观看 | 成人在线网站观看 | 免费视频久久 | 最新亚洲视频 | 在线色视频小说 | 成人午夜久久 | 久久私人影院 | 色欧美成人精品a∨在线观看 | 午夜国产福利在线观看 | 久久久人人爽 | 精品久久久久久久久久 | 色噜噜在线观看 | 国产精品手机在线 | 日本三级不卡 | 99精品在线观看 | 久久久久国产一区二区三区 | 久草com| 国产精品视频999 | 蜜臀av麻豆 | 午夜色场 | 亚洲精品视频在线观看免费 | 国内外成人在线视频 | 国内精品久久久精品电影院 | 国产99久久 | av一区二区三区在线观看 | 国产精品一区二区在线 | 韩日三级在线 | 亚洲免费观看视频 | 亚洲精品av在线 | 欧美国产高清 | 九九热国产视频 | 午夜久久久影院 | 99视频精品免费视频 | 成人av在线播放网站 | 91视频 - x99av | 日韩有码第一页 | 国产精品国产三级国产专区53 | 天天射天天操天天色 | 国内精品亚洲 | 午夜精品导航 | 国产一二三四在线视频 | 色综合中文综合网 | 成年人视频在线免费 | 亚洲精品欧美专区 | 超碰在线免费福利 | 日韩视频一区二区在线观看 | 日韩激情第一页 | 成人中文字幕在线 | 日本精品久久久久 | 亚洲精品在线观看av | 在线观看中文字幕 | 国产精品免费久久久 | 国产中文字幕在线免费观看 | 国产欧美精品一区二区三区 | 在线观看日韩精品 | 黄色在线视频网址 | 国产精品无 | 精品一区二区电影 | 99精品欧美一区二区 | 992tv又爽又黄的免费视频 | 免费看黄色小说的网站 | 最近中文字幕免费av | 久草网在线观看 | av看片网址 | av超碰在线 | 91精品国产麻豆国产自产影视 | 欧洲色综合 | 九九视频网 | 丁香婷婷综合网 | 青春草国产视频 | 久久美女免费视频 | 国产精品亚洲成人 | 丁香婷婷色综合亚洲电影 | 国产一区在线免费 | 免费视频久久久久 | av在线短片| 8x成人在线 | 日韩一区二区三区观看 | 亚洲精品一区二区在线观看 | 美女国产在线 | 麻豆成人精品 | 欧美无极色 | 午夜色性片| 97人人澡人人添人人爽超碰 | 在线观看av中文字幕 | 中文字幕黄色 | 中文字幕高清有码 | 久久无码av一区二区三区电影网 | 右手影院亚洲欧美 | 91视频免费看片 | 福利视频 | 国产精品九九热 | 97av影院| 久草免费新视频 | 久草视频精品 | 免费网站观看www在线观看 | 欧美激情视频在线免费观看 | 久久69精品 | 国产精品99久久久久久久久 | 福利片视频区 | 免费av观看网站 | 一区二区视频免费在线观看 | 日本黄色一级电影 | 日韩激情影院 | 日韩一级黄色av | 欧美日韩中文另类 | 日本久久精品 | 亚洲成人软件 | 天天干天天摸天天操 | 黄色成人毛片 | 国产视频 亚洲精品 | www狠狠操| 成人欧美一区二区三区黑人麻豆 | 操操日日 | 国产成人精品一区二区三区在线观看 | 在线99视频 | 色婷婷免费视频 | 国产精品网红直播 | 国产精品国产三级国产aⅴ9色 | 最近中文字幕免费观看 | av成人在线网站 | 国产在线播放不卡 | 91九色视频 | 激情婷婷六月 | 免费情趣视频 | 久久久久免费精品视频 | 91香蕉亚洲精品 | 91精品一区二区三区蜜桃 | 亚洲精品白浆高清久久久久久 | 天天插天天狠 | 日韩精品久久久免费观看夜色 | 国产精品videoxxxx | 日韩成人中文字幕 | 日韩一区视频在线 | 黄色午夜网站 | 国产精品永久免费视频 | 国产成人精品999在线观看 | www.狠狠色 | 亚洲国产精品视频在线观看 | 中文字幕色播 | 日韩久久精品一区二区 | 精品一区二区av | 最新动作电影 | 国产剧情在线一区 | 国产精品免费久久久久 | 97超级碰碰碰视频在线观看 | 99热精品在线观看 | 中文字幕在 | 99成人精品 | 日韩在线观看一区 | 久章草在线 | 国产一线天在线观看 | 福利片免费看 | av理论电影| 欧美一二在线 | 久久看视频 | 亚洲激情p | 人人爽夜夜爽 | 成年人视频在线免费观看 | 人人爽人人干 | 欧美日一级片 | 狠色在线 | 中文字幕在线有码 | 四虎影视成人精品国库在线观看 | 91最新视频在线观看 | 欧美日韩在线看 | 一本一本久久a久久精品综合小说 | 中文字幕人成不卡一区 | 久草国产视频 | 激情综合网天天干 | 日日操日日插 | 婷婷精品国产欧美精品亚洲人人爽 | 免费成人在线观看 | 天天综合日 | 久草9视频 | 91亚洲狠狠婷婷综合久久久 | 日韩视频一区二区在线 | 激情久久久久久久久久久久久久久久 | 国产黄a三级三级 | 中文字幕日韩免费视频 | 一区二区三区高清在线 | 亚洲精品国产区 | 狠狠综合久久 | 制服丝袜成人在线 | 高潮毛片无遮挡高清免费 | 国产精品久久久久久一二三四五 | 亚洲国产中文在线 | 97精品国产91久久久久久久 | 欧美大片第1页 | 国产一级片毛片 | av免费看在线 | 99久久精品免费看国产一区二区三区 | 午夜精品婷婷 | 国产亚洲精品久久久久久移动网络 | 99视频精品 | 日韩成人在线一区二区 | 国产一级一级国产 | 日韩一区二区三区视频在线 | 亚洲色视频 | 在线观看黄av | 手机看片1042 | 精品国产电影一区 | 婷婷丁香社区 | 天天干天天射天天插 | 999久久久| 精品黄色在线观看 | 视频国产一区二区三区 | 91丨九色丨首页 | 日韩精品免费在线播放 | 久久中文网 | 中文字幕成人一区 | av在线电影免费观看 | 91人人澡人人爽人人精品 | 99免费在线播放99久久免费 | 一性一交视频 | 91视频免费网址 | 国产高清在线 | 91完整版 | av一区二区在线观看中文字幕 | 国产日韩欧美在线一区 | 一区二区毛片 | 国产精品一区二区在线 | 色婷婷视频 | 99在线热播精品免费99热 | 日本成人黄色片 | 久草热视频 | 啪啪精品 | 岛国av在线不卡 | 在线观看视频福利 | 五月婷婷一区二区三区 | 又黄又爽又色无遮挡免费 | 少妇精品久久久一区二区免费 | 国产精品一区在线观看 | 91精品秘密在线观看 | 久久成人国产精品入口 | 中文一区在线 | 国产手机av在线 | 久久久久久久久久久黄色 | 国产在线更新 | 一区二区 精品 | 国产精品麻豆欧美日韩ww | 日韩精品首页 | 三级黄色片子 | 在线观看网站黄 | 日本在线视频网址 | 激情开心站 | 国产午夜激情视频 | 日韩小视频 | 九九热av| 毛片网站观看 | 欧美一级片免费 | 91av视频在线观看 | 日韩免费播放 | 久久 一区 | 久草在线一免费新视频 | 91亚洲成人 | 国内精品免费 | 久久久久亚洲天堂 | 99爱在线观看 | 天天干天天操天天射 | 免费的成人av| 日本丶国产丶欧美色综合 | 中文字幕久久精品亚洲乱码 | 日本久久久影视 | 亚洲国内精品在线 | 日日夜夜干 | 国产一区高清在线 | 亚洲精品视频第一页 | 中文在线免费看视频 | 91精品国产综合久久婷婷香蕉 | 色先锋资源网 | 国产高清视频在线播放 | 中文字幕高清在线播放 | 亚洲精品乱码久久久久久蜜桃91 | 三级黄色大片在线观看 | 久久99亚洲热视 | 欧美日韩一区二区视频在线观看 | 一级特黄aaa大片在线观看 | 久久亚洲影院 | 久久国产成人午夜av影院潦草 | 99在线精品视频 | 91网免费看 | 天天综合成人 | 婷婷六月丁 | 一二区精品 | 草久久久久久 | 国产成年免费视频 | 国产99久久久国产精品免费看 | www.操.com| 黄色福利网 | 四虎天堂 | 国产九九精品视频 | 玖玖视频在线 | 亚洲国产中文字幕在线观看 | 麻豆超碰| 在线观看中文字幕第一页 | 99激情网 | 99在线热播精品免费99热 | 亚洲精品美女久久17c | 2020天天干天天操 | 97成人在线免费视频 | 一级黄色片网站 | 91精品视频网站 | 国产自偷自拍 | 在线观看av大片 | 国产91亚洲 | 一区二区三区高清在线 | 色五月成人 | 91精品伦理| 欧美精品久久久久久久免费 | 国产精品久久久一区二区 | 国产精品久久久久aaaa九色 | www.国产毛片 | 成年人在线免费视频观看 | 色综合久久66 | 一区二区理论片 | 欧美二区视频 | 久久久五月天 | 最近中文字幕高清字幕在线视频 | 中文字幕在线精品 | 午夜视频在线观看网站 | 日批视频在线播放 | 欧美成年网站 | 人人爽人人爽人人片av | 在线97| 国产亚洲精品无 | 久久精品欧美一区二区三区麻豆 | 男女免费视频观看 | jizzjizzjizz亚洲 | 日韩在线免费不卡 | 日韩一区二区免费视频 | 久久超级碰视频 | 最近中文字幕 | 456成人精品影院 | 久久九九精品 | 91爱爱免费观看 | 国产精品久久久久永久免费观看 | 久久国产精品视频观看 | 在线一区二区三区 | 四虎影视av | 在线免费av网 | 亚洲婷婷综合色高清在线 | 欧美在线日韩在线 | 九九热只有精品 | 中文字幕在线看视频国产中文版 | 色婷婷久久一区二区 | 91成人蝌蚪 | 日韩二级毛片 | 日韩大片免费在线观看 | 97人人澡人人爽人人模亚洲 | 免费在线观看av网站 | 久久毛片网 | 麻豆一区二区三区视频 | 久草在线国产 | 日韩毛片在线播放 | 午夜av影院 | 丝袜足交在线 | 免费福利视频导航 | 九色在线视频 | www.黄色片.com| 精品一区二区久久久久久久网站 | 欧美精品久久99 | 久久综合偷偷噜噜噜色 | 久久看片网站 | 欧美成人理伦片 | 91福利视频一区 | 欧美老少交 | 欧美黑吊大战白妞欧美 | 99热国产在线观看 | 国产精品第二页 | 国产精品专区一 | 欧美亚洲成人xxx | 在线精品播放 | 久久国产精品色av免费看 | 在线天堂8√ | 国产成人a亚洲精品 | 欧美一二三区播放 | 在线播放 一区 | 亚洲日本激情 | 亚洲爽爽网 | 国产黄色一级片在线 | 日产中文字幕 | av电影亚洲 | 国产黄色看片 | 玖玖在线精品 | 午夜黄色影院 | 国产精品乱码高清在线看 | 99久热在线精品视频成人一区 | 99久久国产免费,99久久国产免费大片 | 欧美午夜一区二区福利视频 | 亚洲激情久久 | 久久久黄视频 | 亚洲精品高清在线 | www.久草.com | 国产 精品 资源 | 成人午夜黄色 | 91大神精品视频在线观看 | 337p日本欧洲亚洲大胆裸体艺术 | 成人小视频在线 | 国产精品乱码久久久久久1区2区 | 欧洲亚洲激情 | 免费三级av| 青草草在线视频 | 亚洲人在线7777777精品 | 精品亚洲在线 | 国产黑丝一区二区三区 | 国产精品在线看 | 成人av片在线观看 | 日韩av午夜 | 国产日韩视频在线播放 | 在线成人性视频 | 国产精品你懂的在线观看 | 成人小视频免费在线观看 | 日韩精品一区二区三区丰满 | 人人草在线观看 | 免费黄色特级片 | 国产精品综合在线 | av网站有哪些 | 伊人看片 | 日韩v在线91成人自拍 | 中文字幕免费成人 | 波多野结衣在线中文字幕 | 婷婷激情欧美 | 18久久久| 草在线视频 | 六月天综合网 | 亚洲无在线 | 91成人欧美| 最近免费观看的电影完整版 | 91中文字幕在线观看 | 色国产精品一区在线观看 | 欧美日韩免费一区二区 | 欧洲精品亚洲精品 | 六月丁香婷婷网 | www国产亚洲 | 中文字幕不卡在线88 | 久久五月精品 | 夜色在线资源 | 一区二区精品国产 | 在线亚洲午夜片av大片 | 9免费视频 | 韩日成人av | 久久久久麻豆v国产 | 久草综合在线观看 | 婷婷色综合 | 国内精品视频在线播放 | 草久热| 久久男人视频 | 高清一区二区三区 | 日韩av有码在线 | 在线天堂中文www视软件 | 日韩电影在线观看中文字幕 | 极品美女被弄高潮视频网站 | 日韩字幕| 日本性xxx | 亚洲一区二区视频在线播放 | 午夜黄色一级片 | 日韩剧情| 国产精品成人在线观看 | 久久国产精品免费一区二区三区 | 国产精品一区二区久久精品 | 久久综合狠狠综合久久激情 | 亚洲综合丁香 | 色wwww| 成年人电影免费在线观看 | 欧美国产亚洲精品久久久8v | 91网免费观看 | 亚洲黄色片在线 | 夜夜操天天 | 久久久综合 | 久久96国产精品久久99软件 | 色全色在线资源网 | 在线 日韩 av | 在线精品在线 | 91精品视频在线免费观看 | 久久久久久久av | 欧美中文字幕第一页 | a级片久久 | 亚洲女在线 | 久久色在线播放 | 国产在线97 | 国产福利电影网址 | 精品视频国产一区 | 在线观看久久 | 中国一级片视频 | 久久久久久久久亚洲精品 | av免费在线看网站 | 97国产情侣爱久久免费观看 | 亚洲伦理一区 | 日韩专区视频 | 黄色影院在线播放 | 久久人网| 五月婷在线观看 | 日韩女同一区二区三区在线观看 | 国产精品黄色 | 911av视频| 国产一区麻豆 | 久久中文字幕导航 | 国产视频二区三区 | 天天翘av | 亚洲最大av网站 | 久久久久在线观看 | 免费av试看 | 国产天天综合 | 日韩一级网站 | 高清不卡一区二区在线 | 99久久久久久国产精品 | 97精品久久人人爽人人爽 | av片在线看 | 西西4444www大胆艺术 | 在线看的av网站 | 国产精品久久久久久久久久久杏吧 | 欧美激情精品久久久久久变态 | 美女网站一区 | freejavvideo日本免费 | 在线免费色视频 | 国产午夜精品视频 | 精品久久亚洲 | 欧美精品在线观看免费 | 激情五月开心 | 中文字幕中文 | 午夜av在线| 亚洲毛片在线观看. | 国产精品国内免费一区二区三区 | 亚洲视频一区二区三区在线观看 | 久久综合色播五月 | 韩日电影在线免费看 | 激情大尺度视频 | 国产成人精品一区二区三区在线观看 | 国产成人久久精品77777 | 久草网视频在线观看 | 久久tv视频 | av三级av| 国产高清免费在线观看 | 在线观看网站你懂的 | 欧美日韩在线网站 | 丝袜+亚洲+另类+欧美+变态 | 免费av视屏 | 美女网站视频免费都是黄 | 免费观看一级一片 | 99久久爱| 黄色特一级片 | av黄色国产 | 国产不卡一 | 成人在线观看影院 | 午夜视频在线观看一区二区 | 日韩在线观看视频在线 | 日韩免| 波多野结衣视频一区 | 伊人狠狠色丁香婷婷综合 | 亚洲精品在线一区二区三区 | 激情五月在线视频 | 超碰国产在线 | 久久久国产精品电影 | 亚洲国产日本 | 中文字幕在线观看播放 | 日韩久久精品一区二区 | 成人在线观看免费 | aaaaaa毛片 | 亚洲精品乱码白浆高清久久久久久 | 日韩伦理片一区二区三区 | 高清视频一区二区三区 | 麻豆一精品传二传媒短视频 | 99r在线精品 | 国产91国语对白在线 | 久青草电影 | 激情久久五月天 | 国产成人精品一区二区三区福利 | www黄com| 国产午夜麻豆影院在线观看 | 国产精品热 | www日日夜夜 | www.超碰97.com | 国产中文字幕网 | 色窝资源 | 丁香婷婷在线 | 免费中文字幕视频 | 香蕉91视频 | 天堂av免费| 九九亚洲精品 | 国产精品ⅴa有声小说 | 超碰在线人人草 | 精品国产1区2区3区 国产欧美精品在线观看 | 午夜美女福利 | 日韩免费在线视频观看 | 99国产一区二区三精品乱码 | 久久久午夜剧场 | www.在线观看视频 | 国产精品一级视频 | 久久综合精品国产一区二区三区 | 蜜臀av在线一区二区三区 | 六月丁香综合网 | 久久久www成人免费精品张筱雨 | 24小时日本在线www免费的 | 成人免费观看a | 97超碰人人网 | 在线播放精品一区二区三区 | 一区二区网 | 视频一区二区视频 | 四虎影视www| 草在线视频 | 久久精品一区二区三区国产主播 | 国产精品一区二区久久久 | 久久久久久久看片 | 亚洲精品欧洲精品 | www久久99 | 黄色在线成人 | 中文字幕欧美日韩va免费视频 | 成 人 黄 色 视频 免费观看 | 在线观看免费中文字幕 | 97精品国产97久久久久久免费 | 最近中文字幕免费大全 | 久久亚洲婷婷 | 国产精品 中文在线 | 欧美一级电影片 | 天天摸天天舔天天操 | 国产美女视频一区 | 欧美激情综合色综合啪啪五月 | 丁香婷婷久久久综合精品国产 | 中文字幕一区二区三区在线视频 | 黄色小说视频网站 | 欧美日韩精品久久久 | 91在线网址 | 日p视频在线观看 | 亚洲天天综合 | 日韩综合视频在线观看 | 国产精品一区在线观看 | 欧美一区二区三区激情视频 | 丁香五月亚洲综合在线 | 亚洲午夜不卡 | 四虎在线免费观看 | 亚洲黄色区 | 一区二区在线影院 | 高清免费在线视频 | 亚洲天堂网站视频 | 欧美在线视频精品 | 欧美精品一二三 | 日韩特黄一级欧美毛片特黄 |