-
Blocking IO,阻塞型IO
-
NIO
No Blocking IO,非阻塞型IO
-
阻塞IO的弊端
在等待的過程中,什么事也做不了
-
非阻塞IO的好處
-
BIO是阻塞的,NIO是非阻塞的
-
區別二
BIO是面向流的,NIO是面向緩沖區的
-
用來存儲數據
-
通道
用來建立連接和傳輸數據
-
選擇器
方法名 | 說明 |
---|---|
static ByteBuffer allocate(長度) | 創建byte類型的緩沖區 |
static ByteBuffer wrap(byte[] array) | 創建一個有內容的byte類型緩沖區 |
代碼示例
public class CreateByteBufferDemo1 { public static void main(String[] args) { //method1(); //method2(); ByteBuffer wrap = ByteBuffer.wrap("aaa".getBytes()); for (int i = 0; i < 3; i++) { System.out.println(wrap.get()); } } private static void method2() { byte [] bytes = {97,98,99}; ByteBuffer byteBuffer2 = ByteBuffer.wrap(bytes); //緩沖區的長度3 //緩沖區里面的內容就是字節數組的內容. for (int i = 0; i < 3; i++) { System.out.println(byteBuffer2.get()); } System.out.println(byteBuffer2.get()); } private static void method1() { ByteBuffer byteBuffer1 = ByteBuffer.allocate(5); //get for (int i = 0; i < 5; i++) { System.out.println(byteBuffer1.get()); } System.out.println(byteBuffer1.get()); } }
代碼示例
public class ByteBufferDemo2 { public static void main(String[] args) { // int position() 當前要操作的索引 // int limit() 最多能操作到哪個索引 // int capacity() 緩沖區的總長度 ByteBuffer byteBuffer = ByteBuffer.allocate(10); System.out.println(byteBuffer.position());//0 System.out.println(byteBuffer.limit());//10 System.out.println(byteBuffer.capacity());//10 // put(byte b) 一次添加一個字節 // byteBuffer.put((byte) 97); // System.out.println(byteBuffer.position()); // System.out.println(byteBuffer.limit()); // System.out.println(byteBuffer.capacity()); // put(byte[] src) 一次添加一個字節數組 // byteBuffer.put("aaa".getBytes()); // System.out.println(byteBuffer.position());//3 // System.out.println(byteBuffer.limit());//10 // System.out.println(byteBuffer.capacity());//10 // position(int newPosition) 修改position // byteBuffer.position(1); // limit(int newLimit) 修改limit // byteBuffer.limit(5); // System.out.println(byteBuffer.position()); // System.out.println(byteBuffer.limit()); // System.out.println(byteBuffer.capacity()); // int remaining() 還有多少能操作 // boolean hasRemaining() 是否還有能操作的 byteBuffer.put("0123456789".getBytes()); System.out.println(byteBuffer.remaining()); System.out.println(byteBuffer.hasRemaining()); } }
方法名 | 介紹 |
---|---|
flip() | 切換讀寫模式(寫à讀) |
get() | 讀一個字節 |
get(byte[] dst) | 讀多個字節 |
get(int index) | 讀指定索引的字節 |
rewind() | 將position設置為0,可以重復讀 |
clear() | 數據讀寫完畢(讀->寫) |
array() | 將緩沖區轉換成字節數組返回 |
代碼示例
public class ByteBufferDemo3 { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.put("abc".getBytes()); // flip() 切換讀寫模式(寫讀) byteBuffer.flip(); // get() 讀一個字節 // while(byteBuffer.limit() != byteBuffer.position()){ // System.out.println((char) byteBuffer.get()); // } for (int i = 0; i < byteBuffer.limit(); i++) { System.out.println((char) byteBuffer.get()); } // get(byte[] dst) 讀多個字節 // byte [] bytes = new byte[byteBuffer.limit()]; // byteBuffer.get(bytes); // System.out.println(new String(bytes)); // get(int index) 讀指定索引的字節 // System.out.println((char) byteBuffer.get(0)); // rewind() 將position設置為0,可以重復讀 // byteBuffer.rewind(); // for (int i = 0; i < byteBuffer.limit(); i++) { // System.out.println((char) byteBuffer.get()); // } // clear() 數據讀寫完畢(讀->寫) byteBuffer.clear(); byteBuffer.put("qqq".getBytes()); // array() 將緩沖區轉換成字節數組返回 byte[] bytes = byteBuffer.array(); System.out.println(new String(bytes)); } }
-
數據是從外面進入到緩沖區的,所以緩沖區在做讀數據的操作。
-
需求:我要把數據從緩沖區中讀出來。
數據是從緩沖區里面到外面的。所以緩沖區在做寫數據的操作。
-
capacity:容量(長度) limit: 界限(最多能讀/寫到哪里) posotion:位置(讀/寫哪個索引)
-
獲取緩沖區里面數據之前,需要調用flip方法
-
再次寫數據之前,需要調用clear方法,
-
-
打開通道
-
指定IP和端口號
-
寫出數據
-
釋放資源
-
public class NIOClient { public static void main(String[] args) throws IOException { //1.打開通道 SocketChannel socketChannel = SocketChannel.open(); //2.指定IP和端口號 socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); //3.寫出數據 ByteBuffer byteBuffer = ByteBuffer.wrap("一點寒毛先制".getBytes()); socketChannel.write(byteBuffer); //4.釋放資源 socketChannel.close(); } }
-
-
服務端通道
只負責建立建立,不負責傳遞數據
-
客戶端通道
建立建立並將數據傳遞給服務端
-
緩沖區
客戶端發送的數據都在緩沖區中
-
服務端通道內部創建出來的客戶端通道
相當於客戶端通道的延伸用來傳遞數據
-
-
服務端實現步驟
-
打開一個服務端通道
-
綁定對應的端口號
-
通道默認是阻塞的,需要設置為非阻塞
-
此時沒有門衛大爺,所以需要經常看一下有沒有連接發過來沒?
-
如果有客戶端來連接了,則在服務端通道內部,再創建一個客戶端通道,相當於是客戶端通道的延伸
-
獲取客戶端傳遞過來的數據,並把數據放在byteBuffer1這個緩沖區中
-
給客戶端回寫數據
-
釋放資源
-
public class NIOServer { public static void main(String[] args) throws IOException { // 1.打開一個服務端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2.綁定對應的端口號 serverSocketChannel.bind(new InetSocketAddress(10000)); // 3.通道默認是阻塞的,需要設置為非阻塞 //如果傳遞true 表示通道設置為阻塞通道...默認值 //如果傳遞false 表示通道設置為非阻塞通道 serverSocketChannel.configureBlocking(false); // 4.此時沒有門衛大爺,所以需要經常看一下有沒有連接發過來沒? while (true) { // 5.如果有客戶端來連接了,則在服務端通道內部,再創建一個客戶端通道,相當於是客戶端通道的延伸 //此時已經設置了通道為非阻塞 //所以在調用方法的時候,如果有客戶端來連接,那么會創建一個SocketChannel對象. //如果在調用方法的時候,沒有客戶端來連接,那么他會返回一個null SocketChannel socketChannel = serverSocketChannel.accept(); //System.out.println(socketChannel); if(socketChannel != null){ // 6.客戶端將緩沖區通過通道傳遞給服務端,就到了這個延伸通道socketChannel里面 // 7.服務端創建一個空的緩沖區裝數據並輸出 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //獲取傳遞過來的數據,並把他們放到byteBuffer緩沖區中. //返回值: //正數: 表示本次讀到的有效字節個數. //0 : 表示本次沒有讀到有效字節. //-1 : 表示讀到了末尾 int len = socketChannel.read(byteBuffer); System.out.println(new String(byteBuffer.array(),0,len)); //8.釋放資源 socketChannel.close(); } } } }
-
實現步驟
-
打開通道
-
指定IP和端口號
-
寫出數據
-
讀取服務器寫回的數據
-
釋放資源
-

public class Clinet { public static void main(String[] args) throws IOException { // 1.打開通道 SocketChannel socketChannel = SocketChannel.open(); // 2.指定IP和端口號 socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); // 3.寫出數據 ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孫一棒棒".getBytes()); socketChannel.write(byteBuffer1); // 手動寫入結束標記 socketChannel.shutdownOutput(); System.out.println("數據已經寫給服務器"); // 4.讀取服務器寫回的數據 ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024); int len; while((len = socketChannel.read(byteBuffer2)) != -1){ byteBuffer2.flip(); System.out.println(new String(byteBuffer2.array(),0,len)); byteBuffer2.clear(); } // 5.釋放資源 socketChannel.close(); } }
-
實現步驟
-
打開一個服務端通道
-
綁定對應的端口號
-
通道默認是阻塞的,需要設置為非阻塞
-
此時沒有門衛大爺,所以需要經常看一下有沒有連接發過來沒?
-
如果有客戶端來連接了,則在服務端通道內部,再創建一個客戶端通道,相當於是客戶端通道的延伸
-
獲取客戶端傳遞過來的數據,並把數據放在byteBuffer1這個緩沖區中
-
給客戶端回寫數據
-
釋放資源
-

public class Sever { public static void main(String[] args) throws IOException { // 1,打開一個服務端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2,綁定對應的端口號 serverSocketChannel.bind(new InetSocketAddress(10000)); // 3,通道默認是阻塞的,需要設置為非阻塞 serverSocketChannel.configureBlocking(false); // 4,此時沒有門衛大爺,所以需要經常看一下有沒有連接發過來沒? while(true){ // 5,如果有客戶端來連接了,則在服務端通道內部,再創建一個客戶端通道,相當於是客戶端通道的延伸 SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ System.out.println("此時有客戶端來連接了"); // 6,獲取客戶端傳遞過來的數據,並把數據放在byteBuffer1這個緩沖區中 ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); //socketChannel.read(byteBuffer1); int len; //針對於緩沖區來講 //如果 從添加數據 ----> 獲取數據 flip //如果 從獲取數據 ----> 添加數據 clear while((len = socketChannel.read(byteBuffer1)) != -1){ byteBuffer1.flip(); System.out.println(new String(byteBuffer1.array(),0,len)); byteBuffer1.clear(); } System.out.println("接收數據完畢,准備開始往客戶端回寫數據"); // 7,給客戶端回寫數據 ByteBuffer byteBuffer2 = ByteBuffer.wrap("哎喲,真疼啊!!!".getBytes()); socketChannel.write(byteBuffer2); // 8,釋放資源 socketChannel.close(); } } } }
-
服務端內部獲取的客戶端通道在讀取時,如果讀取不到結束標記就會一直阻塞
-
解決方案
將服務端內部獲取的客戶端通道設置為非阻塞的

// 客戶端 public class Clinet { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孫一棒棒".getBytes()); socketChannel.write(byteBuffer1); System.out.println("數據已經寫給服務器"); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024); int len; while((len = socketChannel.read(byteBuffer2)) != -1){ System.out.println("客戶端接收回寫數據"); byteBuffer2.flip(); System.out.println(new String(byteBuffer2.array(),0,len)); byteBuffer2.clear(); } socketChannel.close(); } } // 服務端 public class Sever { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(10000)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ System.out.println("此時有客戶端來連接了"); // 將服務端內部獲取的客戶端通道設置為非阻塞的 socketChannel.configureBlocking(false); //獲取客戶端傳遞過來的數據,並把數據放在byteBuffer1這個緩沖區中 ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); //socketChannel.read(byteBuffer1); int len; //針對於緩沖區來講 //如果 從添加數據 ----> 獲取數據 flip //如果 從獲取數據 ----> 添加數據 clear while((len = socketChannel.read(byteBuffer1)) > 0){ System.out.println("服務端接收發送數據"); byteBuffer1.flip(); System.out.println(new String(byteBuffer1.array(),0,len)); byteBuffer1.clear(); } System.out.println("接收數據完畢,准備開始往客戶端回寫數據"); ByteBuffer byteBuffer2 = ByteBuffer.wrap("哎喲,真疼啊!!!".getBytes()); socketChannel.write(byteBuffer2); socketChannel.close(); } } } }
-
Selector
選擇器對象
-
SelectionKey
綁定的key
-
SelectableChannel
能使用選擇器的通道
-
SocketChannel
-
-
-
打開一個服務端通道(open)
-
綁定對應的端口號
-
通道默認是阻塞的,需要設置為非阻塞
-
打開一個選擇器(門衛大爺)
-
將選擇器綁定服務端通道,並監視服務端是否准備好
-
如果有客戶端來連接了,大爺會遍歷所有的服務端通道,誰准備好了,就讓誰來連接 連接后,在服務端通道內部,再創建一個客戶端延伸通道
-
代碼實現

// 客戶端 public class Clinet { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孫一棒棒".getBytes()); socketChannel.write(byteBuffer1); System.out.println("數據已經寫給服務器"); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024); int len; while((len = socketChannel.read(byteBuffer2)) != -1){ System.out.println("客戶端接收回寫數據"); byteBuffer2.flip(); System.out.println(new String(byteBuffer2.array(),0,len)); byteBuffer2.clear(); } socketChannel.close(); } } // 服務端 public class Server { public static void main(String[] args) throws IOException { //1.打開服務端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2.讓這個通道綁定一個端口 serverSocketChannel.bind(new InetSocketAddress(10000)); //3.設置通道為非阻塞 serverSocketChannel.configureBlocking(false); //4.打開一個選擇器 //Selector --- 選擇器 // SelectionKey --- 綁定通道后返回那個令牌 // SelectableChannel --- 可以使用選擇器的通道 Selector selector = Selector.open(); //5.綁定選擇器和服務端通道 serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); while(true){ System.out.println("11"); //選擇器會監視客戶端通道的狀態. //6.返回值就表示此時有多少個客戶端來連接. int count = selector.select(); System.out.println("222"); if(count != 0){ System.out.println("有客戶端來連接了"); //7.會遍歷所有的服務端通道.看誰准備好了,誰准備好了,就讓誰去連接. //獲取所有服務端通道的令牌,並將它們都放到一個集合中,將集合返回. Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while(iterator.hasNext()){ //selectionKey 依次表示每一個服務端通道的令牌 SelectionKey selectionKey = iterator.next(); if(selectionKey.isAcceptable()){ //可以通過令牌來獲取到了一個已經就緒的服務端通道 ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel(); //客戶端的延伸通道 SocketChannel socketChannel = ssc.accept(); //將客戶端延伸通道設置為非阻塞的 socketChannel.configureBlocking(false); socketChannel.register(selector,SelectionKey.OP_READ); //當客戶端來連接的時候,所有的步驟已經全部執行完畢. }else if(selectionKey.isReadable()){ //當前通道已經做好了讀取的准備(延伸通道) SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); //socketChannel.read(byteBuffer1); int len; while((len = socketChannel.read(byteBuffer1)) > 0){ byteBuffer1.flip(); System.out.println(new String(byteBuffer1.array(),0,len)); byteBuffer1.clear(); } //給客戶端的回寫數據 socketChannel.write(ByteBuffer.wrap("哎喲喂好疼啊!!!".getBytes())); socketChannel.close(); } iterator.remove(); } } } } }