終於在課設的閒時間把netty實戰的四五章給解決了
這裏來記錄一下第四章裏面所講的IO
首先說到IO,我想,必須要先了解阻塞,非阻塞,同步和異步這四個詞
看到一個講的很易懂的例子:https://www.cnblogs.com/George1994/p/6702084.html
那麼瞭解完這四個詞,就到了IO了
傳統的IO,即阻塞IO
也就是跟之前用socket編程那樣,沒有數據寫入到來這邊的線程就一直等待,直到數據到來然後再對數據進行處理,例如打印
拿出以前的老代碼
public void bio(int port) throws IOException{ final ServerSocket socket = new ServerSocket(port); try { //死循環,直到有連接再用線程將內容寫入 while (true){ Socket client = socket.accept();//阻塞 new Thread(new Runnable() { @Override public void run() { OutputStream out; try { out = client.getOutputStream(); out.write("hello world".getBytes(CharsetUtil.UTF_8)); out.flush(); client.close(); }catch (IOException e){ e.printStackTrace(); }finally { try { client.close(); }catch (IOException e){ e.printStackTrace(); } } } }).start(); } }catch (Exception e){ e.printStackTrace(); } }
代碼很容易懂,就是等客戶端連接就往裏面寫入數據,每一個連接對應一個線程去處理,索性採用了多線程,如果是單線程,系統就會阻塞在這裏,
多線程CPU在這裏能釋放做更多事情,的確是個很好的模型,至少我之前一直是這麼覺得的...但是似乎這裏忽略了一些問題,那就是連接數量的
問題,如果是小量的連接數還是好的,但是如果往大了增加連接數,打個比方一萬,那麼就會有一萬個線程,那麼問題就顯而易見了,內存這上面就
已經有瓶頸了,而且,線程數量增大,線程之間的切換也會成爲一個大問題,因爲線程切換的成本很高,有可能線程切換的時間比執行的時間還長,
導致更嚴重的問題
所以可以知道的是,如果連接量小,那麼這個模型是沒問題的,如果大了,那麼這個就無能爲力了
再來說非阻塞IO,即NIO
這是netty實戰上的非阻塞IO代碼
public void serve(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false);//channel設置爲非阻塞模式 ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); for (;;){ try { selector.select();//這個函數是阻塞的 } catch (IOException ex) { ex.printStackTrace(); //handle exception break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate());//註冊到selector中 System.out.println( "Accepted connection from " + client); } if (key.isWritable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); while (buffer.hasRemaining()) { if (client.write(buffer) == 0) { break; } } client.close(); } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { // ignore on close } } } } }
註冊當這幾個事件到來的時候所對應的處理器。然後在合適的時機告訴事件選擇器:對此事件感興趣。對於寫操作,就是寫不出去的時候對
寫事件感興趣;對於讀操作,就是完成連接和系統沒有辦法承載新讀入的數據的時,對於accept,一般是服務器剛啓動的時候;而對於connect,
一般是connect失敗需要重連或者異步調用connect的時候,NIO的讀寫函數可以立刻返回,這就不用另開線程去等待結果了,如果一個連接不能讀
寫(socket.read()返回0或者socket.write()返回0),就把這件事記下來,記錄的方式通常是在Selector上註冊標記位,然後切換到其它就緒的連
接(channel)繼續進行讀寫。
這樣的優點:相比於阻塞模型,非阻塞不用再等待任務,而是把時間花費到其它任務上,也就是這個當前線程同時處理多個任務,但是也有缺點:
那就是導致任務完成的響應延遲增大了,因爲每隔一段時間纔去執行詢問的動作,但是任務萬一在兩次詢問之間的時間間隔內完成,就會導致整體
數據吞吐量的降低。影響用戶體驗
Netty中的阻塞和非阻塞
以上是java 裏面的NIO,可以看到從阻塞改寫到非阻塞這其中還是有很多地方要改的,勢必會給帶來很多麻煩,下面繼續回到netty
而在netty中就不會出現這樣的事情了,因爲只需改變EventLoopGroup的種類,分別對應的OioEventLoopGroup和NioEventLoppGroup
和channel的種類,對應OioServerSocketChannel和NioServerSocketChannel,其餘地方基本不變
Epool,專用於linux的本地非阻塞傳輸
jdk在linuxl中的NOI的實現上採用了不同的辦法,就是使用了epoll調用,他有着比select和poll更好的性能,同時也是linux非阻塞網絡編程的事實標準
而且如果程序是運行在linux,也不需要改動很多代碼,就跟上面的例子一樣,將NioEventLoopGroup和NioServerSocketChannel改成EpollEventLoopGroup
和EpollServerSocketChannel,這一改動在高負載的情況下能比NIO有更好的表現
JVM內部的傳輸
用於在同一個虛擬機中的客戶端和服務端的通信,這個過程中的SocketAddress沒有綁定具體的物理網絡地址,只要服務器在運行,他就會存儲在註冊表裏,channel關閉
即從表裏註銷,這裏的傳輸不接收網絡流量,其實也能猜出來,畢竟本地,所以他不能和其他我們使用的socket傳輸互相操作,所以如果客戶端想使用本機,即同JVM中的服
務端就必須通過他實現
小結筆記:
IO訪問過程:(網絡IO本質上就是socket的讀取)
對於socket流,就是等待網絡上的數據到達,然後複製到圖中的緩衝區,然後複製到應用進程
JVM內部通信還有其他幾種IO那裏還沒來得及找例子做一做,找時間再補上,如果錯誤,還請多多指出