Socket網(wǎng)絡(luò)編程:BIO女仰,NIO猜年,select抡锈,epoll

本文是觀看了B站的馬士兵的視頻后的總結(jié):
清華大牛權(quán)威講解nio,epoll,多路復(fù)用,更好的理解redis-netty-Kafka等熱門技術(shù)
和知乎的一篇文章:
看不懂來砍我乔外,epoll原理

理解Socket基礎(chǔ)1—計算機基礎(chǔ)

我們知道內(nèi)存是被分為內(nèi)核和用戶兩個部分的床三,內(nèi)核用于運行操作系統(tǒng)和硬件相關(guān)的底層驅(qū)動,由于系統(tǒng)的保護機制杨幼,用戶態(tài)的進程是無法直接訪問硬件的撇簿,比如網(wǎng)絡(luò)通信的硬件網(wǎng)卡;


  • 硬件設(shè)備接收事件(網(wǎng)卡接收數(shù)據(jù)幀差购,鍵盤接收輸入等)四瘫,當(dāng)有了事件后,硬件層會產(chǎn)生一個中斷歹撒,CPU會立刻停止當(dāng)前的工作(比如當(dāng)前正在執(zhí)行用戶進程)處理這個中斷莲组,處理的工作就是內(nèi)核去實現(xiàn),比如調(diào)用內(nèi)核中對應(yīng)硬件驅(qū)動的回調(diào)暖夭;

  • 用戶態(tài)的進行想要訪問硬件資源(和硬件交互)必須通過內(nèi)核锹杈,內(nèi)核會提供系統(tǒng)調(diào)用讓用戶態(tài)安全的訪問計算機;

拿Socket舉例迈着,網(wǎng)絡(luò)數(shù)據(jù)通過物理網(wǎng)線傳給網(wǎng)卡竭望,此時網(wǎng)卡會產(chǎn)生一個中斷,告訴CPU有網(wǎng)絡(luò)數(shù)據(jù)進入電腦了裕菠,這時會將數(shù)據(jù)交給內(nèi)核咬清,具體放在哪我也沒研究過,反正就是放在內(nèi)核里面奴潘,用戶態(tài)(Java)必須通過系統(tǒng)調(diào)用去拿到這個網(wǎng)絡(luò)數(shù)據(jù)

BIO

傳統(tǒng)的IO使用(偽代碼)

        //  客戶端
        Socket socket = new Socket("127.0.0.1",8090);
        socket.getOutputStream();
        socket.getInputStream();


        //  服務(wù)端
        ServerSocket serverSocket = new ServerSocket(8089);
        Socket socket = serverSocket.accept();
        socket.getOutputStream();
        socket.getInputStream();
客戶端:
  • 創(chuàng)建Socket對象旧烧,傳入服務(wù)端對應(yīng)的ip和端口,會自動連接
  • 獲取IO流通信
服務(wù)端:
  • 服務(wù)端創(chuàng)建ServerSocket對象画髓,綁定ip和port
  • ServerSocket調(diào)用accept()監(jiān)聽客戶端連接掘剪,練連接完成會返回客戶端對應(yīng)的Socket對象(這是一個阻塞方法,一般會在循環(huán)中開啟線程去執(zhí)行奈虾,即一個線程一個Socket連接)
  • 完事兒以后通過Socket獲取IO流進行數(shù)據(jù)的讀寫
這些是我們在java層做的事情夺谁,那么網(wǎng)絡(luò)通信是如何發(fā)生的呢?

首先java層是用戶態(tài)的一個進程肉微,他是無法直接讀取網(wǎng)卡的數(shù)據(jù)的匾鸥,必須通過系統(tǒng)調(diào)用到內(nèi)核中去獲取碉纳;系統(tǒng)調(diào)用是通過native層去實現(xiàn)的勿负;


BIO模型
BIO存在的問題:
  • accept()和IO的讀寫是阻塞方法,必須開啟多線程劳曹,每一個Socket連接建立一個線程
  • 很多Socket連接建立了并沒有通信笆环,會浪費大量的系統(tǒng)資源攒至;
NIO
NIO模型

為了解決線程浪費問題出現(xiàn)了NIO,將阻塞方法改為非阻塞方法躁劣,如果有連接迫吐,有數(shù)據(jù),就去處理账忘,沒有的話繼續(xù)執(zhí)行下面志膀,等待下次循環(huán);

NIO存在的問題:

NIO雖然解決了線程浪費的問題鳖擒,可是如果在大量網(wǎng)絡(luò)請求的情況下溉浙,當(dāng)前方案下的執(zhí)行效率會變得非常的低,因為Java層的循環(huán)變得非常的長蒋荚,并且每次循環(huán)都需要調(diào)用系統(tǒng)調(diào)用去詢問內(nèi)核這個請求有沒有用戳稽,這個連接有沒有數(shù)據(jù),大量的無效的系統(tǒng)調(diào)用也會影響性能期升;

Select:

Select模型

為了解決NIO在java層大量無效循環(huán)調(diào)用System call的情況惊奇,出現(xiàn)了一個select系統(tǒng)調(diào)用,Select的作用是將10000此循環(huán)全部通過一次SC交給內(nèi)核播赁,由內(nèi)核去循環(huán)颂郎,判斷哪些是有效的循環(huán),比如100次有效循環(huán)容为,那么我的java就可以有目的性的去調(diào)用100次有效的SC去進行數(shù)據(jù)讀寫乓序,Socket連接建立;

select缺點:
  • 需要將連接一次性傳遞給內(nèi)核
  • 雖然省去了大量的SC坎背,但是內(nèi)核需要去遍歷循環(huán)替劈,內(nèi)核的內(nèi)存壓力會增大

Epoll:

等待隊列紅黑樹

Epoll將所有的Socket連接都在內(nèi)核中保存了下來,就省去了Select一次性將所有的Socket連接發(fā)過來的這一步驟得滤;

就緒列表雙向鏈表

Select效率低的原因是因為需要遍歷所有的連接才能知道哪個連接有數(shù)據(jù)陨献,而epoll通過維護一個集合,存放所有的就緒連接耿戚,這樣就避免了遍歷的步驟湿故;當(dāng)有數(shù)據(jù)到達時阿趁,中斷程序會產(chǎn)生一個中斷將有數(shù)據(jù)的Socket添加到就緒列表膜蛔;

epoll將多路復(fù)用的實現(xiàn)拆分為三個步驟:
  • epoll_create:內(nèi)核會產(chǎn)生一個epoll 實例數(shù)據(jù)結(jié)構(gòu)并返回一個文件描述符,這個特殊的描述符就是epoll實例的句柄脖阵,后面的兩個接口都以它為中心
  • epoll_ctl:維護等待隊列將被監(jiān)聽的描述符添加到紅黑樹或從紅黑樹中刪除皂股,或者對監(jiān)聽事件進行修改
  • epoll_wait:阻塞進程,等待數(shù)據(jù)命黔,程序執(zhí)行到這一步時呜呐,如果就緒列表有數(shù)據(jù)就斤,就直接返回,如果沒有數(shù)據(jù)就會阻塞蘑辑;

NIO

NonBlocking IO特點:

  • 非阻塞IO洋机,沒有數(shù)據(jù)時不會阻塞,而是返回0
  • 單線程處理多任務(wù)

核心類:

  • channel
  • selector
  • buffer

channel:

channel通道類似流洋魂,既可以從流讀取數(shù)據(jù)绷旗,也可以寫入數(shù)據(jù)到流,流是單向的副砍,通道是雙向的衔肢;

channel的實現(xiàn):
  • FileChannel:從文件中讀寫數(shù)據(jù),無法設(shè)置為非阻塞式
  • DataGramChannel:從UDP讀寫網(wǎng)絡(luò)數(shù)據(jù)
  • SocketChannel:從TCP讀寫網(wǎng)絡(luò)數(shù)據(jù)
  • ServerSocketChannel:監(jiān)聽新進來的TCP連接,每一個新的TCP連接都會創(chuàng)建一個新的SocketChannel

buffer

NIO buffer 提供了一組方法豁翎,用來訪問緩沖區(qū)角骤,對于緩沖區(qū),本質(zhì)上是一塊可以寫入數(shù)據(jù)心剥,可以讀取數(shù)據(jù)的內(nèi)存邦尊;

buffer的使用:

1.channel寫入數(shù)據(jù)到buffer
2.調(diào)用buffer的flip()make buffer ready to read
3. 從buffer中讀取數(shù)據(jù)
4.調(diào)用buffer的clear()`make buffer ready to write`

buffer的工作原理:

buffer的重要屬性:capacity position limit

  • capacity:作為一個內(nèi)存塊,buffer有一個固定大小刘陶,capacity就是記錄buffer的大小

  • position:當(dāng)buffer寫入的時候position從0開始胳赌,放入一個數(shù)據(jù),position就后移一位匙隔;當(dāng)buffer讀取的時候疑苫,position從0開始,每讀一個數(shù)據(jù)纷责,后移一位捍掺;

  • limit:在寫入的時候,limit同capacity,表示可以寫入的大性偕拧挺勿;在讀取時,表示當(dāng)前可讀取的數(shù)量喂柒;

buffer的類型:
  • ByteBuffer:
  • CharBuffer:
  • DoubleBuffer:
  • FloatBuffer:
  • IntBuffer:
  • LongBuffer:
  • ShortBuffer:
buffer的創(chuàng)建(分配):
    //  分配了48字節(jié)大小的字符Buffer
    CharBuffer charBuffer = CharBuffer.allocate(48);

向buffer寫入數(shù)據(jù)
        //  1 直接用 put() 寫入
        charBuffer.put('1');

    //  2 channel寫入到buffer
    channel.read(buffer);

flip():將buffer從寫模式轉(zhuǎn)換成讀模式

從buffer讀取數(shù)據(jù)
        // 1 直接使用 get() 讀取
        char c = charBuffer.get();

    // 2 讀取到channel中
    channel.write(buffer);
  • rewind():將position重新設(shè)置為0不瓶,可以再次讀取buffer(limit保持不變)

  • clear():將buffer從讀模式轉(zhuǎn)為寫模式,clear不會保存原來的數(shù)據(jù)灾杰,

  • compact():compact會將未讀的數(shù)據(jù)拷貝到buffer的起始處蚊丐,并且將position移到最后一個數(shù)后面

  • mark() & reset() :通過mark 記錄position的值,再通過reset恢復(fù)到之前記錄的position

  • equals() :比較buffer內(nèi)的剩余元素艳吠,如果它們類型相等麦备,數(shù)量相等,元素值相等,那么兩個buffer 就相等

  • compareTo() :比較元素的數(shù)量和元素值的大辛莞荨黍匾;

分散和聚集(Scatter/Gather):

  • 分散:將channel的數(shù)據(jù)分散讀取到多個buffer中
    scatter read
        //  分散 , 一個channel的數(shù)據(jù)讀取到多個buffer
        ByteBuffer head = ByteBuffer.allocate(20);
        ByteBuffer body = ByteBuffer.allocate(480);
        ByteBuffer[] buffers = {head,body};
        try {
            // 從channel讀取數(shù)據(jù)
            channel.read(buffers);
        } catch (IOException e) {
            e.printStackTrace();
        }

  • 聚集:將多個buffer數(shù)據(jù)聚集寫入到一個channel中
    gather write
        //  聚集 呛梆, 多個buffer數(shù)據(jù)寫入channel
        ByteBuffer head = ByteBuffer.allocate(20);
        ByteBuffer body = ByteBuffer.allocate(480);
        ByteBuffer[] buffers = {head,body};
        try {
            // 寫入數(shù)據(jù)到channel
            channel.write(buffers);
        } catch (IOException e) {
            e.printStackTrace();
        }

Selector

選擇器锐涯,用于實現(xiàn)單線程管理多個channel,即管理多個網(wǎng)絡(luò)連接

1. selector的創(chuàng)建:
       try {
           Selector selector = Selector.open();
       } catch (IOException e) {
           e.printStackTrace();
       }
2. 向selector中注冊channel
            //  將channel設(shè)置為非阻塞式
            socketChannel.configureBlocking(false);
            //  注冊到selector上
            SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ);

注意, 如果一個 Channel 要注冊到 Selector 中, 那么這個 Channel 必須是非阻塞的, 即channel.configureBlocking(false); 因為 Channel 必須要是非阻塞的, 因此 FileChannel 是不能夠使用選擇器的, 因為 FileChannel 都是阻塞的
register()第二個參數(shù)用于指定selector對channel的什么事件感興趣,常見的事件有:

  • SelectionKey.OP_ACCEPT:確認(rèn)事件
  • SelectionKey.OP_CONNECT:連接事件填物,TCP連接
  • SelectionKey.OP_READ:讀出事件
  • SelectionKey.OP_WRITE:寫入事件
SelectionKey:

每次向Selector中注冊一個channel都會拿到一個SelectionKey對象全庸;通過selectionKey對綁定事件進行控制,SelectionKey重要的成員變量:

  • interest Set:感興趣事件的集合
  • ready Set:已準(zhǔn)備就緒的操作的集合
  • Channel:
  • Selector:
  • 附加對象:
            // 獲取 channel
            key.channel();
            //  獲取 selector
            key.selector();
            //  獲取 感興趣的事件
            key.interestOps();
            //  附加對象
            key.attach(new Object());

Selector.select():

調(diào)用該方法后會阻塞融痛,知道被注冊的channel有事件出現(xiàn)壶笼,或者出現(xiàn)新的channel注冊事件


selector 的工作流程
            Set keySet = selector.selectedKeys();
            Iterator iterator = keySet.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = (SelectionKey) iterator.next();
                // TODO: 通過 selectionKey 獲取channel 處理事件
                iterator.remove();  // 刪除當(dāng)前元素(key)
            }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雁刷,隨后出現(xiàn)的幾起案子覆劈,更是在濱河造成了極大的恐慌,老刑警劉巖沛励,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件责语,死亡現(xiàn)場離奇詭異,居然都是意外死亡目派,警方通過查閱死者的電腦和手機坤候,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來企蹭,“玉大人白筹,你說我怎么就攤上這事×律悖” “怎么了徒河?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長送漠。 經(jīng)常有香客問我顽照,道長,這世上最難降的妖魔是什么闽寡? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任代兵,我火速辦了婚禮,結(jié)果婚禮上爷狈,老公的妹妹穿的比我還像新娘植影。我一直安慰自己,他們只是感情好淆院,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布何乎。 她就那樣靜靜地躺著,像睡著了一般土辩。 火紅的嫁衣襯著肌膚如雪支救。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天拷淘,我揣著相機與錄音各墨,去河邊找鬼。 笑死启涯,一個胖子當(dāng)著我的面吹牛贬堵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播结洼,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼黎做,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了松忍?” 一聲冷哼從身側(cè)響起蒸殿,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸣峭,沒想到半個月后宏所,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡摊溶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年爬骤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莫换。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡霞玄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拉岁,到底是詐尸還是另有隱情溃列,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布膛薛,位于F島的核電站听隐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏哄啄。R本人自食惡果不足惜雅任,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咨跌。 院中可真熱鬧沪么,春花似錦、人聲如沸锌半。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至殉摔,卻和暖如春州胳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逸月。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工栓撞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碗硬。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓瓤湘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恩尾。 傳聞我的和親對象是個殘疾皇子弛说,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355