Java學習總結 1-2-1 Java NIO網絡編程

TCP/UDP協議
    OSI網絡七層模型:
        高三層:
            應用層:爲用戶的應用進程提供網絡服務                                   第七層
            表示層:負責數據格式轉換、數據加密解密、壓縮解壓縮等                   第六層
            會話層:負責建立、管理和終止進程之間的會話和數據交換                   第五層
        傳輸層:提供可靠的端口到端口的數據傳輸服務(TCP/UDP協議)                  第四層
        低三層:
            網絡層:進行路由選擇和流量控制(IP協議)                               第三層
            數據鏈路層:通過檢驗、確認和反饋重發手段,形成穩定的數據鏈路(010101) 第二層
            物理層:使原始的數據比特流能在物理上傳輸                               第一層

    TCP:TCP提供面向連接、可靠、有序、字節流傳輸服務
        (transport control protocol,傳輸控制協議)是面向連接的,面向流的,提供高可靠性服務。收發兩端(
         客戶端和服務器端)都要有一一成對的socket
        三次握手、四次揮手
        Http協議:
            請求報文格式
                第一部分:請求行   -> 請求類型,資源路徑以及HTTP版本   例:GET /chen.do HTTP/1.1
                第二部分:請求頭部 -> 接着請求行(第一行)之後部分,用來說明服務器要使用的附加信息
                第三部分:空行     -> 請求頭後面的空行是必須的,請求頭部和數據主體之間必須有換行
                第四部分:請求的數據也叫主體,可以添加任意的數據。

            返回報文格式
                第一部分:狀態行 -> HTTP版本、狀態碼、狀態消息       例:HTTP/1.1 200 OK
                第二部分:響應報文頭部 -> 接着響應行(第一行)之後的部分,用來說明服務器要使用的附加信息
                第三部分:空行 -> 響應頭後面的空行是必須的,請求頭部和數據主體之間必須有換行
                第四部分:響應正文。可以添加任意數據

            響應狀態碼(HTTP協議)
                1XX:臨時響應  -> 表示臨時響應並需要請求者繼續執行的狀態碼
                2XX;成功      -> 成功處理了請求的狀態碼 
                3XX;重定向    -> 表示完成請求,需要進一步操作。通常這些狀態碼用來重定向
                4XX:請求錯誤   -> 表示請求可能出錯,妨礙了服務器的處理
                5XX:服務器錯誤 -> 表示服務器在嘗試處理請求時發生內部錯誤。這些錯誤可能是服務器本身的錯誤,而不是請求出錯

    UDP:用戶數據協議UDP是Internet傳輸層協議。提供無連接、不可靠、數據包盡力傳輸服務
    UDP(user datagram protocol,用戶數據報協議)是無連接的,面向消息的,提供高效率服務。不會使用塊的合併優化算法,
    由於UDP支持的是一對多的模式
        相比TCP:
            1.無需建立連接
            2.無連接狀態
            3.首部開銷小

    TCP/UDP:
            TCP            UDP
          面向連接        無連接
          提供可靠性保障  不可靠
             慢             快
          資源佔用多      資源佔用少

    Socket編程:Internet中應用最廣泛的網絡應用編程接口,實現於三種底層協議接口:
        1.數據包類型套接字SOCK_DGRAM (面向UDP接口)
        2.流式套接字SOCK_STREAM      (面向TCP接口)
        3.原始套接字SOCK_RAW         (面向網絡層協議接口IP、ICMP等)

        主要socket API及調用過程:
            創建套接字 -> 端點綁定 -> 發送數據 -> 接收數據 -> 釋放套接字


 BIO網絡編程
    
    阻塞IO:
        阻塞(blocking)IO:資源不可用時,IO請求一直阻塞,直到反饋結果(有數據或超時)
        非阻塞(non-blocking)IO:資源不可用時,IO請求離開返回,返回數據標識資源不可用

        同步(synchronous)IO:應用阻塞在發送或接收數據的狀態,直到數據成功傳輸或返回失敗
        異步:(asynchronous)IO:應用發送或接收數據後立即返回,實際處理時異步執行的

        阻塞導致在處理網絡I/O時,一個線程只能處理一個網絡連接
        注:阻塞和非阻塞是獲取資源的方式。同步和異步是程序如何處理資源的邏輯設計。ServerSocket#accept、InputStream#read都是阻塞API。操作系統底層API中,默認Socket操作都是Blocking型(阻塞),send/recv等接口都是阻塞的。

    API
        服務端:ServerSocket:
            listern(); accept(); 

        客戶端:Socket:
            send();


NIO非阻塞網絡編程(採用多路複用IO/事件驅動IO模型) https://www.cnblogs.com/lighten/p/8922186.html
        三個核心組件:
            Buffer:緩衝區 -> 與流式的 I/O 不同,NIO是基於塊(Block)的,它以塊爲基本單位處理數據。緩衝是一塊連續的內存塊,是 NIO
                    讀寫數據的中轉地。通道表示緩衝數據的源頭或者目的地,它用於向緩衝讀取或者寫入數據,是訪問緩衝的接口。相比較
                    直接對數組的操作,Buffer API更加容易操作和管理   示例:

                    三個重要屬性:
                        1.capacity容量:作爲一個內存塊,Buffer具有一定的固定大小,也成爲“容量”
                        2.position位置:寫入模式時代表寫數據的位置。讀取模式時代表讀取數據的位置
                        3.limit限制:   寫入模式,限制等於buffer的容量。讀取模式下,limit等於寫入的數據量


                ByteBuffer內存模型:
                    直接內存(direct堆外內存):在JVM之外向物理內存直接申請內存
                        ByteBuffrt directByteBuffer = ByteBuffer.allocateDirect(noBNytes);
                        優點:
                            1、進行IO或文件IO時比heapBuffer少一次拷貝。(file/socket -> OS memory -> jvm heap)
                            2、在GC範圍之外,降低GC壓力,但實現了自動管理。DirextByteBuffer類提供Cleaner對象(PhantomReference),Cleaner被GC前會執行clean方法,觸發DirectByteBuffer中定義的Deallocator
                    
                    非直接內存(heap堆內存):在JVM的申請內存

                    建議:性能確實可觀的時候纔去使用;分配給大型、長壽命;(網絡傳輸、文件讀寫場景)
                        通過虛擬機參數MaxxDirectMemorySize限制大小,防止耗盡整個機器內存

            Channel: 通道
                代碼 -> buffer -> channel

                和標準的IO Stream區別:在一個通道內進行讀取和寫入時stream通常是單向的(input和output),可以非阻塞讀取和寫入通道,通道始終讀取或寫入緩衝區,channel的API涵蓋了UDP/TCP網絡和文件IO。(FileCjannel,DatagramCjannel,SocketChannel,ServerSocketChannel)

                SocketChannel:用於創建TCP連接,類似java.net.Socket.有兩種創建的方式:
                    1、客戶端主動發起和服務的連接。
                    2、服務端獲取新的連接

                    write寫:write()在尚未寫入任何內容可能就返回了。需要在循環中調用
                    read讀:read()方法可能直接返回而根本不讀取任何數據,根據返回的int值判斷讀取了多少字節

                    SockerChannel:客戶端
                        //客戶端主動發起連接的方式
                        SocketChannel socketChannel = SocketChannel.open();
                        //設置爲非阻塞模式
                        socketChannel.configureBlocking(false);
                        //創建一個端口和ip
                        socketChannel.connect(new InetSocketAddress("htttp:163.com",8080));
                        //發送請求數據 - 向通道寫入數據
                        socketChannel.write(byteBuffer);
                        //讀取服務端返回 - 讀取緩衝區的數據
                        int butesRead = socketChannel.read(byteBuffer);
                        //關閉連接
                        socketChannel.close();

                    ServerSocketChannel:服務端
                        //創建網絡服務端
                        ServerSocketChannel serverSockerChannel = ServerSocketChannel.open();
                        //設爲非阻塞模式
                        serverSocletChannel.configureBlocking(false);
                        //綁定端口
                        serverSocletChannel.socket().bind(new InetSocketAddress(8080));
                        while(true){
                            SocketChannel soccketChannel = serverSocletChannel.accept();
                            if(socketChannel != null){
                                //TCP請求 讀取/響應
                            }
                        }
                        注:serverSocletChannel.accept()如果該通道處於非阻塞,如果沒有掛起的連接則該方法立即返回null。
                            所以必須檢查返回的對象是否爲空

            Selector:選擇器
                Thread -> Selector -> 多個channel(serverSocker socket) -> ByteBuffer
                Selector:          選擇器
                    四種事件:
                        Connect:連接(Selector.OP_CONNECT)
                                    客戶端與服務端建立連接,客戶端觸發事件
                        Accept: 準備就緒(OP_ACCEPT)
                                    服務端檢測到客戶端連接請求,服務端觸發事件
                        Read:   讀取(OP_READ)
                                    客戶端與服務端建立連接之後讀取操作,雙端觸發事件
                        Writ:   寫入(OP_WRITE)
                                    客戶端與服務端建立連接之後寫入操作,雙端觸發事件
                SelectableChannel: 可選擇通道
                SelectionKey:      選擇鍵
                
                服務端: 
                    對於服務端而言首先需要的就是確定監聽的端口,其次是與之對應的channel,而後就是selector,最後還需要一個線程池。爲什麼會需
                    要線程池呢?道理很簡單,select模式獲取了所有channel的change,對於服務端而言,change的可能有非常多的客戶端channel,而用
                    戶程序只有一個線程,如果這麼多個channel一個個順序執行,如果有耗時嚴重的操作,那麼後果是非常糟糕的,所有客戶端都會延時
                    處理,這也是多路IO複用的一個糟糕點。線程池就是爲每個客戶端分配一個線程去處理,減緩這種情況的後果。Server的基本四個內容
                    就出來了:
                        private int port;
                        private Selector selector;
                        private ServerSocketChannel serverSocketChannel;
                        private ExecutorService executorService;
                    接下來就是初始化服務端。初始化的步驟也是一般化:1.初始化連接池;2.初始化Selector;3.初始化綁定端口;4.將socket注
                    冊到select上。大致步驟就是這些,但是還有些額外的細節。具體代碼如下:
                        executorService = Executors.newCachedThreadPool();
                        selector = Selector.open();
                        serverSocketChannel = ServerSocketChannel.open();
                            serverSocketChannel.configureBlocking(false);
                            serverSocketChannel.socket().bind(new InetSocketAddress(port));
                        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        selector()方法:
            NIO使用的IO原型是多路複用IO模型,也叫事件驅動IO,這種IO模型採用select/epoll(IO多路複用,複用同一個線程)模式,
            select會輪詢查詢所有綁定在自身上的Channel,當有某個Channel數據到達了,就會通知進程(Channel數據到達意思是綁定在
            Channel上的socket產生了鏈接)。select()方法就是Selector通知進程之前使當前的代碼段進入阻塞狀態,說白了就是
            在while(true)裏阻塞,不消耗cpu而已
        
        發送流程:
            不管是客戶端發送服務端接收,還是服務端發送客戶端接收,基本的流程都是:發送方發送數據->buffer->發送方channel->接收方channel->buffer->接收方接收數據
        
        阻塞和非阻塞IO對比:    
            當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中
            的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。這個圖和blocking IO
            的圖其實並沒有太大的不同,事實上還更差一些。因爲這裏需要使用兩個系統調用(select和recvfrom),而blocking IO只調用
            了一個系統調用(recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。    
                
    總結:
        1. 如果處理的連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性
            能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。
        2. 在多路複用模型中,對於每一個socket,一般都設置成爲non-blocking,但是,如上圖所示,整個用戶的process其實是一直
            被block的。只不過process是被select這個函數block,而不是被socket IO給block。
        結論: select的優勢在於可以處理多個連接,不適用於單個連接 

                
                

    


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章