Java的BIO和NIO很難懂?用代碼實踐給你看,再不懂我轉行!

本文原題「從實踐角度從新理解BIO和NIO」,原文由Object分享,爲了更好的內容表現力,收錄時有改動。php

一、引言

這段時間本身在看一些Java中BIO和NIO之類的東西,也看了不少博客,發現各類關於NIO的理論概念說的天花亂墜頭頭是道,能夠說是很是的完整,可是整個看下來以後,發現本身對NIO仍是隻知其一;不知其二、一臉蒙逼的狀態(請原諒我太笨)。html

基於以上緣由,就有了寫本文的想法。本文不會提到不少Java NIO和Java BIO的理論概念(須要的話請參見本文的「相關文章」一節),而是站在編碼實踐的角度,經過代碼實例,總結了我本身對於Java NIO的看法。有了代碼實踐的過程後再從新回頭看理論概念,會有一個不同的理解視角,但願能助你吃透它們!linux

術語約定:本文所說的BIO即Java程序員常說的經典阻塞式IO,NIO是指Java 1.4版加入的NIO(即異步IO)。程序員

(本文同步發佈於:http://www.52im.net/thread-2846-1-1.html面試

二、關於做者

本文做者:Object編程

我的博客:http://blog.objectspace.cn/windows

三、相關文章

本文爲了不過多的闡述Java NIO、BIO的概念性內容,於是儘可能少的說起相關理論知識,若是你對Java NIO、BIO的理論知識原本就瞭解很少,建議仍是先讀一讀即時通信網整理一下文章,將有助於你更好地理解本文。數組

少囉嗦!一分鐘帶你讀懂Java的NIO和經典IO的區別》(* 推薦)
史上最強Java NIO入門:擔憂從入門到放棄的,請讀這篇!》(* 推薦)
高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型
高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型

四、先用經典的BIO來實現一個簡易的單線程網絡通訊程序

要講明白BIO和NIO,首先咱們應該本身實現一個簡易的服務器,不用太複雜,單線程便可。緩存

4.1 爲何使用單線程做爲演示

由於在單線程環境下能夠很好地對比出BIO和NIO的一個區別,固然我也會演示在實際環境中BIO的所謂一個請求對應一個線程的情況。安全

4.2 服務端代碼

public class Server {
        public static void main(String[] args) {
                byte[] buffer = new byte[1024];
                try{
                        ServerSocket serverSocket = newServerSocket(8080);
                        System.out.println("服務器已啓動並監聽8080端口");
                        while(true) {
                                System.out.println();
                                System.out.println("服務器正在等待鏈接...");
                                Socket socket = serverSocket.accept();
                                System.out.println("服務器已接收到鏈接請求...");
                                System.out.println();
                                System.out.println("服務器正在等待數據...");
                                socket.getInputStream().read(buffer);
                                System.out.println("服務器已經接收到數據");
                                System.out.println();
                                String content = newString(buffer);
                                System.out.println("接收到的數據:"+ content);
                        }
                } catch(IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

4.3 客戶端代碼

public class Consumer {
        public static void main(String[] args) {
                try{
                        Socket socket = newSocket("127.0.0.1",8080);
                        socket.getOutputStream().write("向服務器發數據".getBytes());
                        socket.close();
                } catch(IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

4.4 代碼解析

咱們首先建立了一個服務端類,在類中實現實例化了一個SocketServer並綁定了8080端口。以後調用accept方法來接收鏈接請求,而且調用read方法來接收客戶端發送的數據。最後將接收到的數據打印。

完成了服務端的設計後,咱們來實現一個客戶端,首先實例化Socket對象,而且綁定ip爲127.0.0.1(本機),端口號爲8080,調用write方法向服務器發送數據。

4.5 運行結果

當咱們啓動服務器,但客戶端尚未向服務器發起鏈接時,控制檯結果以下:

當客戶端啓動並向服務器發送數據後,控制檯結果以下:

4.6 結論

從上面的運行結果,首先咱們至少能夠看到,在服務器啓動後,客戶端尚未鏈接服務器時,服務器因爲調用了accept方法,將一直阻塞,直到有客戶端請求鏈接服務器。

五、對客戶端功能進行擴展

在上節中,咱們實現的客戶端的邏輯主要是:創建Socket –> 鏈接服務器 –> 發送數據,咱們的數據是在鏈接服務器以後就當即發送的,如今咱們來對客戶端進行一次擴展,當咱們鏈接服務器後,不當即發送數據,而是等待控制檯手動輸入數據後,再發送給服務端。(注意:本節中,服務端代碼保持不變)

5.1 改進後的代碼

public class Consumer {
        public static void main(String[] args) {
                try{
                        Socket socket = newSocket("127.0.0.1",8080);
                        String message = null;
                        Scanner sc = newScanner(System.in);
                        message = sc.next();
                        socket.getOutputStream().write(message.getBytes());
                        socket.close();
                        sc.close();
                } catch(IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

5.2 測試

當服務端啓動,客戶端尚未請求鏈接服務器時,控制檯結果以下:

當服務端啓動,客戶端鏈接服務端,但沒有發送數據時,控制檯結果以下:

當服務端啓動,客戶端鏈接服務端,而且發送數據時,控制檯結果以下:

5.3 結論

從上面的運行結果中咱們能夠看到,服務器端在啓動後:

1)首先須要等待客戶端的鏈接請求(第一次阻塞);
2)若是沒有客戶端鏈接,服務端將一直阻塞等待;
3)而後當客戶端鏈接後,服務器會等待客戶端發送數據(第二次阻塞);
4)若是客戶端沒有發送數據,那麼服務端將會一直阻塞等待客戶端發送數據。

服務端從啓動到收到客戶端數據的這個過程,將會有兩次阻塞的過程:

1)第一次在等待鏈接時阻塞;
2)第二次在等待數據時阻塞。

BIO會產生兩次阻塞,這就是BIO的很是重要的一個特色。

六、BIO

6.1 在單線程條件下BIO的弱點

在上兩節中,咱們用經典的Java BIO實現了一個簡易的網絡通訊程序,這個簡易的程序是以單線程運行的。

其實咱們不難看出:當咱們的服務器接收到一個鏈接後,而且沒有接收到客戶端發送的數據時,是會阻塞在read()方法中的,那麼此時若是再來一個客戶端的請求,服務端是沒法進行響應的。換言之:在不考慮多線程的狀況下,BIO是沒法處理多個客戶端請求的。

6.2 BIO如何處理併發

在上面的服務器實現中,咱們實現的是單線程版的BIO服務器,不難看出,單線程版的BIO並不能處理多個客戶端的請求,那麼如何能使BIO處理多個客戶端請求呢。

其實不難想到:咱們只須要在每個鏈接請求到來時,建立一個線程去執行這個鏈接請求,就能夠在BIO中處理多個客戶端請求了,這也就是爲何BIO的其中一條概念是服務器實現模式爲一個鏈接一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理。

6.3 多線程BIO服務器簡易實現

public class Server {
        public static void main(String[] args) {
                byte[] buffer = newbyte[1024];
                try{
                        ServerSocket serverSocket = newServerSocket(8080);
                        System.out.println("服務器已啓動並監聽8080端口");
                        while(true) {
                                System.out.println();
                                System.out.println("服務器正在等待鏈接...");
                                Socket socket = serverSocket.accept();
                                newThread(newRunnable() {
                                        @Override
                                        publicvoidrun() {
                                                System.out.println("服務器已接收到鏈接請求...");
                                                System.out.println();
                                                System.out.println("服務器正在等待數據...");
                                                try{
                                                        socket.getInputStream().read(buffer);
                                                } catch(IOException e) {
                                                        // TODO Auto-generated catch block
                                                        e.printStackTrace();
                                                }
                                                System.out.println("服務器已經接收到數據");
                                                System.out.println();
                                                String content = newString(buffer);
                                                System.out.println("接收到的數據:"+ content);
                                        }
                                }).start();
                        }
                } catch(IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

6.4 運行結果 

很明顯,如今咱們的服務器的狀態就是一個線程對應一個請求,換言之,服務器爲每個鏈接請求都建立了一個線程來處理。

6.5 多線程BIO服務器的弊端

多線程BIO服務器雖然解決了單線程BIO沒法處理併發的弱點,可是也帶來一個問題:若是有大量的請求鏈接到咱們的服務器上,可是卻不發送消息,那麼咱們的服務器也會爲這些不發送消息的請求建立一個單獨的線程,那麼若是鏈接數少還好,鏈接數一多就會對服務端形成極大的壓力。

因此:若是這種不活躍的線程比較多,咱們應該採起單線程的一個解決方案,可是單線程又沒法處理併發,這就陷入了一種很矛盾的狀態,因而就有了NIO。

七、NIO

題外話:若是你對Java的NIO理論知識瞭解的太少,建議優先讀一下這兩篇文章,《少囉嗦!一分鐘帶你讀懂Java的NIO和經典IO的區別》、《史上最強Java NIO入門:擔憂從入門到放棄的,請讀這篇!》。

7.1 NIO的引入

咱們先來看看單線程模式下BIO服務器的代碼,其實NIO須要解決的最根本的問題就是存在於BIO中的兩個阻塞,分別是等待鏈接時的阻塞和等待數據時的阻塞。

public class Server {
        public static void main(String[] args) {
                byte[] buffer = new byte[1024];
                try{
                        ServerSocket serverSocket = newServerSocket(8080);
                        System.out.println("服務器已啓動並監聽8080端口");
                        while(true) {
                                System.out.println();
                                System.out.println("服務器正在等待鏈接...");
                                //阻塞1:等待鏈接時阻塞
                                Socket socket = serverSocket.accept();
                                System.out.println("服務器已接收到鏈接請求...");
                                System.out.println();
                                System.out.println("服務器正在等待數據...");
                                //阻塞2:等待數據時阻塞
                                socket.getInputStream().read(buffer);
                                System.out.println("服務器已經接收到數據");
                                System.out.println();
                                String content = new String(buffer);
                                System.out.println("接收到的數據:"+ content);
                        }
                } catch(IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

咱們須要再老調重談的一點是,若是單線程服務器在等待數據時阻塞,那麼第二個鏈接請求到來時,服務器是沒法響應的。若是是多線程服務器,那麼又會有爲大量空閒請求產生新線程從而形成線程佔用系統資源,線程浪費的狀況。

那麼咱們的問題就轉移到,如何讓單線程服務器在等待客戶端數據到來時,依舊能夠接收新的客戶端鏈接請求。

7.2 模擬NIO解決方案

若是要解決上文中提到的單線程服務器接收數據時阻塞,而沒法接收新請求的問題,那麼其實可讓服務器在等待數據時不進入阻塞狀態,問題不就迎刃而解了嗎?

【第一種解決方案(等待鏈接時和等待數據時不阻塞)】:

public class Server {
        public static void main(String[] args) throws InterruptedException {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                try{
                        //Java爲非阻塞設置的類
                        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                        serverSocketChannel.bind(newInetSocketAddress(8080));
                        //設置爲非阻塞
                        serverSocketChannel.configureBlocking(false);
                        while(true) {
                                SocketChannel socketChannel = serverSocketChannel.accept();
                                if(socketChannel==null) {
                                        //表示沒人鏈接
                                        System.out.println("正在等待客戶端請求鏈接...");
                                        Thread.sleep(5000);
                                }else{
                                        System.out.println("當前接收到客戶端請求鏈接...");
                                }
                                if(socketChannel!=null) {
                    //設置爲非阻塞
                                        socketChannel.configureBlocking(false);
                                        byteBuffer.flip();//切換模式  寫-->讀
                                        int effective = socketChannel.read(byteBuffer);
                                        if(effective!=0) {
                                                String content = Charset.forName("utf-8").decode(byteBuffer).toString();
                                                System.out.println(content);
                                        }else{
                                                System.out.println("當前未收到客戶端消息");
                                        }
                                }
                        }
                } catch(IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

運行結果: 

代碼解析:

不難看出,在這種解決方案下,雖然在接收客戶端消息時不會阻塞,可是又開始從新接收服務器請求,用戶根原本不及輸入消息,服務器就轉向接收別的客戶端請求了,換言之,服務器弄丟了當前客戶端的請求。

【解決方案二(緩存Socket,輪詢數據是否準備好)】:

public class Server {
        public static void main(String[] args) throws InterruptedException {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                List<SocketChannel> socketList = newArrayList<SocketChannel>();
                try{
                        //Java爲非阻塞設置的類
                        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                        serverSocketChannel.bind(newInetSocketAddress(8080));
                        //設置爲非阻塞
                        serverSocketChannel.configureBlocking(false);
                        while(true) {
                                SocketChannel socketChannel = serverSocketChannel.accept();
                                if(socketChannel==null) {
                                        //表示沒人鏈接
                                        System.out.println("正在等待客戶端請求鏈接...");
                                        Thread.sleep(5000);
                                }else{
                                        System.out.println("當前接收到客戶端請求鏈接...");
                                        socketList.add(socketChannel);
                                }
                                for(SocketChannel socket:socketList) {
                                        socket.configureBlocking(false);
                                        int effective = socket.read(byteBuffer);
                                        if(effective!=0) {
                                                byteBuffer.flip();//切換模式  寫-->讀
                                                String content = Charset.forName("UTF-8").decode(byteBuffer).toString();
                                                System.out.println("接收到消息:"+content);
                                                byteBuffer.clear();
                                        }else{
                                                System.out.println("當前未收到客戶端消息");
                                        }
                                }
                        }
                } catch(IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
}

運行結果:

代碼解析:

在解決方案一中,咱們採用了非阻塞方式,可是發現一旦非阻塞,等待客戶端發送消息時就不會再阻塞了,而是直接從新去獲取新客戶端的鏈接請求,這就會形成客戶端鏈接丟失。

而在解決方案二中,咱們將鏈接存儲在一個list集合中,每次等待客戶端消息時都去輪詢,看看消息是否準備好,若是準備好則直接打印消息。

能夠看到,從頭至尾咱們一直沒有開啓第二個線程,而是一直採用單線程來處理多個客戶端的鏈接,這樣的一個模式能夠很完美地解決BIO在單線程模式下沒法處理多客戶端請求的問題,而且解決了非阻塞狀態下鏈接丟失的問題。

7.3 存在的問題(解決方案二)

從剛纔的運行結果中其實能夠看出,消息沒有丟失,程序也沒有阻塞。

可是,在接收消息的方式上可能有些許不妥,咱們採用了一個輪詢的方式來接收消息,每次都輪詢全部的鏈接,看消息是否準備好,測試用例中只是三個鏈接,因此看不出什麼問題來,可是咱們假設有1000萬鏈接,甚至更多,採用這種輪詢的方式效率是極低的。

另外,1000萬鏈接中,咱們可能只會有100萬會有消息,剩下的900萬並不會發送任何消息,那麼這些鏈接程序依舊要每次都去輪詢,這顯然是不合適的。

7.4 真實NIO中如何解決

在真實NIO中,並不會在Java層上來進行一個輪詢,而是將輪詢的這個步驟交給咱們的操做系統來進行,他將輪詢的那部分代碼改成操做系統級別的系統調用(select函數,在linux環境中爲epoll),在操做系統級別上調用select函數,主動地去感知有數據的socket。

這方面的知識,建議詳讀如下文章:

高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型
高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型

八、關於使用select/epoll和直接在應用層作輪詢的區別

咱們在以前實現了一個使用Java作多個客戶端鏈接輪詢的邏輯,可是在真正的NIO源碼中其實並非這麼實現的,NIO使用了操做系統底層的輪詢系統調用 select/epoll(windows:select,linux:epoll),那麼爲何不直接實現而要去調用系統來作輪詢呢?

8.1 select底層邏輯

假設有A、B、C、D、E五個鏈接同時鏈接服務器,那麼根據咱們上文中的設計,程序將會遍歷這五個鏈接,輪詢每一個鏈接,獲取各自數據準備狀況,那麼和咱們本身寫的程序有什麼區別呢?

首先:咱們寫的Java程序其本質在輪詢每一個Socket的時候也須要去調用系統函數,那麼輪詢一次調用一次,會形成沒必要要的上下文切換開銷。

而:Select會將五個請求從用戶態空間全量複製一份到內核態空間,在內核態空間來判斷每一個請求是否準備好數據,徹底避免頻繁的上下文切換。因此效率是比咱們直接在應用層寫輪詢要高的。

若是:select沒有查詢到到有數據的請求,那麼將會一直阻塞(是的,select是一個阻塞函數)。若是有一個或者多個請求已經準備好數據了,那麼select將會先將有數據的文件描述符置位,而後select返回。返回後經過遍歷查看哪一個請求有數據。

select的缺點:

1)底層存儲依賴bitmap,處理的請求是有上限的,爲1024;
2)文件描述符是會置位的,因此若是當被置位的文件描述符須要從新使用時,是須要從新賦空值的;
3)fd(文件描述符)從用戶態拷貝到內核態仍然有一筆開銷;
4)select返回後還要再次遍歷,來獲知是哪個請求有數據。

8.2 poll函數底層邏輯

poll的工做原理和select很像,先來看一段poll內部使用的一個結構體。

struct pollfd{
    int fd;
    short events;
    short revents;
}

poll一樣會將全部的請求拷貝到內核態,和select同樣,poll一樣是一個阻塞函數,當一個或多個請求有數據的時候,也一樣會進行置位,可是它置位的是結構體pollfd中的events或者revents置位,而不是對fd自己進行置位,因此在下一次使用的時候不須要再進行從新賦空值的操做。poll內部存儲不依賴bitmap,而是使用pollfd數組的這樣一個數據結構,數組的大小確定是大於1024的。解決了select 一、2兩點的缺點。

8.3 epoll函數底層邏輯

epoll是最新的一種多路IO複用的函數。這裏只說說它的特色。

epoll和上述兩個函數最大的不一樣是,它的fd是共享在用戶態和內核態之間的,因此能夠沒必要進行從用戶態到內核態的一個拷貝,這樣能夠節約系統資源。

另外,在select和poll中,若是某個請求的數據已經準備好,它們會將全部的請求都返回,供程序去遍歷查看哪一個請求存在數據,可是epoll只會返回存在數據的請求,這是由於epoll在發現某個請求存在數據時,首先會進行一個重排操做,將全部有數據的fd放到最前面的位置,而後返回(返回值爲存在數據請求的個數N),那麼咱們的上層程序就能夠沒必要將全部請求都輪詢,而是直接遍歷epoll返回的前N個請求,這些請求都是有數據的請求。

以上有關高性能線程、網絡IO模型的知識,能夠詳讀如下幾篇:

高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型
高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型

九、Java中BIO和NIO的概念總結

一般一些文章都是在開頭放上概念,可是我此次選擇將概念放在結尾,由於經過上面的實操,相信你們對Java中BIO和NIO都有了本身的一些理解,這時候再來看概念應該會更好理解一些了。

先來個例子理解一下概念,以銀行取款爲例:

1)同步 : 本身親自出馬持銀行卡到銀行取錢(使用同步IO時,Java本身處理IO讀寫);

3)異步 : 委託一小弟拿銀行卡到銀行取錢,而後給你(使用異步IO時,Java將IO讀寫委託給OS處理,須要將數據緩衝區地址和大小傳給OS(銀行卡和密碼),OS須要支持異步IO操做API);

3)阻塞 : ATM排隊取款,你只能等待(使用阻塞IO時,Java調用會一直阻塞到讀寫完成才返回);

4)非阻塞 : 櫃檯取款,取個號,而後坐在椅子上作其它事,等號廣播會通知你辦理,沒到號你就不能去,你能夠不斷問大堂經理排到了沒有,大堂經理若是說還沒到你就不能去(使用非阻塞IO時,若是不能讀寫Java調用會立刻返回,當IO事件分發器會通知可讀寫時再繼續進行讀寫,不斷循環直到讀寫完成)。

Java對BIO、NIO的支持:

1)Java BIO (blocking I/O):同步並阻塞,服務器實現模式爲一個鏈接一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,若是這個鏈接不作任何事情會形成沒必要要的線程開銷,固然能夠經過線程池機制改善;

2)Java NIO (non-blocking I/O): 同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。

BIO、NIO適用場景分析:

1)BIO方式: 適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4之前的惟一選擇,但程序直觀簡單易理解;

2)NIO方式: 適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。

十、本文小結

本文介紹了一些關於JavaBIO和NIO從本身實操的角度上的一些理解,我我的認爲這樣去理解BIO和NIO會比光看概念會有更深的理解,也但願各位同窗能夠本身去敲一遍,經過程序的運行結果得出本身對JavaBIO和NIO的理解。

附錄:更多NIO、網絡編程方面的資料

[1] NIO異步網絡編程資料:
Java新一代網絡編程模型AIO原理及Linux系統AIO介紹
有關「爲什麼選擇Netty」的11個疑問及解答
開源NIO框架八卦——究竟是先有MINA仍是先有Netty?
選Netty仍是Mina:深刻研究與對比(一)
選Netty仍是Mina:深刻研究與對比(二)
NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示
NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示
NIO框架入門(三):iOS與MINA二、Netty4的跨平臺UDP雙向通訊實戰
NIO框架入門(四):Android與MINA二、Netty4的跨平臺UDP雙向通訊實戰
Netty 4.x學習(一):ByteBuf詳解
Netty 4.x學習(二):Channel和Pipeline詳解
Netty 4.x學習(三):線程模型詳解
Apache Mina框架高級篇(一):IoFilter詳解
Apache Mina框架高級篇(二):IoHandler詳解
MINA2 線程原理總結(含簡單測試實例)
Apache MINA2.0 開發指南(中文版)[附件下載]
MINA、Netty的源代碼(在線閱讀版)已整理髮布
解決MINA數據傳輸中TCP的粘包、缺包問題(有源碼)
解決Mina中多個同類型Filter實例共存的問題
實踐總結:Netty3.x升級Netty4.x遇到的那些坑(線程篇)
實踐總結:Netty3.x VS Netty4.x的線程模型
詳解Netty的安全性:原理介紹、代碼演示(上篇)
詳解Netty的安全性:原理介紹、代碼演示(下篇)
詳解Netty的優雅退出機制和原理
NIO框架詳解:Netty的高性能之道
Twitter:如何使用Netty 4來減小JVM的GC開銷(譯文)
絕對乾貨:基於Netty實現海量接入的推送服務技術要點
Netty乾貨分享:京東京麥的生產級TCP網關技術實踐總結
新手入門:目前爲止最透徹的的Netty高性能原理和框架架構解析
寫給初學者:Java高性能NIO框架Netty的學習方法和進階策略
少囉嗦!一分鐘帶你讀懂Java的NIO和經典IO的區別
史上最強Java NIO入門:擔憂從入門到放棄的,請讀這篇!
手把手教你用Netty實現網絡通訊程序的心跳機制、斷線重連機制
Java的BIO和NIO很難懂?用代碼實踐給你看,再不懂我轉行!
>>  更多同類文章 ……
[2] 網絡編程基礎資料:
TCP/IP詳解 -  第11章·UDP:用戶數據報協議
TCP/IP詳解 -  第17章·TCP:傳輸控制協議
TCP/IP詳解 -  第18章·TCP鏈接的創建與終止
TCP/IP詳解 -  第21章·TCP的超時與重傳
技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)
通俗易懂-深刻理解TCP協議(上):理論基礎
通俗易懂-深刻理解TCP協議(下):RTT、滑動窗口、擁塞處理
理論經典:TCP協議的3次握手與4次揮手過程詳解
理論聯繫實際:Wireshark抓包分析TCP 3次握手、4次揮手過程
計算機網絡通信協議關係圖(中文珍藏版)
UDP中一個包的大小最大能多大?
P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介
P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解
P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解
通俗易懂:快速理解P2P技術中的NAT穿透原理
高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少
高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題
高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了
高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索
高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型
高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型
Java的BIO和NIO很難懂?跟着代碼示例,從新理解它們!
鮮爲人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)
鮮爲人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)
鮮爲人知的網絡編程(三):關閉TCP鏈接時爲何會TIME_WAIT、CLOSE_WAIT
鮮爲人知的網絡編程(四):深刻研究分析TCP的異常關閉
鮮爲人知的網絡編程(五):UDP的鏈接性和負載均衡
鮮爲人知的網絡編程(六):深刻地理解UDP協議並用好它
鮮爲人知的網絡編程(七):如何讓不可靠的UDP變的可靠?
鮮爲人知的網絡編程(八):從數據傳輸層深度解密HTTP
鮮爲人知的網絡編程(九):理論聯繫實際,全方位深刻理解DNS
網絡編程懶人入門(一):快速理解網絡通訊協議(上篇)
網絡編程懶人入門(二):快速理解網絡通訊協議(下篇)
網絡編程懶人入門(三):快速理解TCP協議一篇就夠
網絡編程懶人入門(四):快速理解TCP和UDP的差別
網絡編程懶人入門(五):快速理解爲何說UDP有時比TCP更有優點
網絡編程懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門
網絡編程懶人入門(七):深刻淺出,全面理解HTTP協議
網絡編程懶人入門(八):手把手教你寫基於TCP的Socket長鏈接
網絡編程懶人入門(九):通俗講解,有了IP地址,爲什麼還要用MAC地址?
網絡編程懶人入門(十):一泡尿的時間,快速讀懂QUIC協議
技術掃盲:新一代基於UDP的低延時網絡傳輸層協議——QUIC詳解
讓互聯網更快:新一代QUIC協議在騰訊的技術實踐分享
現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障
聊聊iOS中網絡編程長鏈接的那些事
移動端IM開發者必讀(一):通俗易懂,理解移動網絡的「弱」和「慢」
移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結
IPv6技術詳解:基本概念、應用現狀、技術實踐(上篇)
IPv6技術詳解:基本概念、應用現狀、技術實踐(下篇)
從HTTP/0.9到HTTP/2:一文讀懂HTTP協議的歷史演變和設計思路
腦殘式網絡編程入門(一):跟着動畫來學TCP三次握手和四次揮手
腦殘式網絡編程入門(二):咱們在讀寫Socket時,究竟在讀寫什麼?
腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識
腦殘式網絡編程入門(四):快速理解HTTP/2的服務器推送(Server Push)
腦殘式網絡編程入門(五):天天都在用的Ping命令,它究竟是什麼?
腦殘式網絡編程入門(六):什麼是公網IP和內網IP?NAT轉換又是什麼鬼?
以網遊服務端的網絡接入層設計爲例,理解實時通訊的技術挑戰
邁向高階:優秀Android程序員必知必會的網絡基礎
全面瞭解移動端DNS域名劫持等雜症:技術原理、問題根源、解決方案等
美圖App的移動端DNS優化實踐:HTTPS請求耗時減少近半
Android程序員必知必會的網絡通訊傳輸層協議——UDP和TCP
IM開發者的零基礎通訊技術入門(一):通訊交換技術的百年發展史(上)
IM開發者的零基礎通訊技術入門(二):通訊交換技術的百年發展史(下)
IM開發者的零基礎通訊技術入門(三):國人通訊方式的百年變遷
IM開發者的零基礎通訊技術入門(四):手機的演進,史上最全移動終端發展史
IM開發者的零基礎通訊技術入門(五):1G到5G,30年移動通訊技術演進史
IM開發者的零基礎通訊技術入門(六):移動終端的接頭人——「基站」技術
IM開發者的零基礎通訊技術入門(七):移動終端的千里馬——「電磁波」
IM開發者的零基礎通訊技術入門(八):零基礎,史上最強「天線」原理掃盲
IM開發者的零基礎通訊技術入門(九):無線通訊網絡的中樞——「核心網」
IM開發者的零基礎通訊技術入門(十):零基礎,史上最強5G技術掃盲
IM開發者的零基礎通訊技術入門(十一):爲何WiFi信號差?一文即懂!
IM開發者的零基礎通訊技術入門(十二):上網卡頓?網絡掉線?一文即懂!
IM開發者的零基礎通訊技術入門(十三):爲何手機信號差?一文即懂!
IM開發者的零基礎通訊技術入門(十四):高鐵上無線上網有多難?一文即懂!
IM開發者的零基礎通訊技術入門(十五):理解定位技術,一篇就夠
百度APP移動端網絡深度優化實踐分享(一):DNS優化篇
百度APP移動端網絡深度優化實踐分享(二):網絡鏈接優化篇
百度APP移動端網絡深度優化實踐分享(三):移動端弱網優化篇
技術大牛陳碩的分享:由淺入深,網絡編程學習經驗乾貨總結
可能會搞砸你的面試:你知道一個TCP鏈接上能發起多少個HTTP請求嗎?
知乎技術分享:知乎千萬級併發的高性能長鏈接網關技術實踐
>>  更多同類文章 ……

(本文同步發佈於:http://www.52im.net/thread-2846-1-1.html

相關文章
相關標籤/搜索