在上篇文章中對BIO網絡編程的相關內容進行了講解,經過咱們一步一步的優化,雖然咱們經過多線程解決了併發訪問的問題,可是BIO自己的一些特性形成的問題卻沒有獲得解決。java
BIO是阻塞IO,咱們使用線程來進行IO的調度,咱們沒法肯定io是否就緒,可是每一個IO操做都會建立線程,這個時候若是IO未就緒,那麼建立的線程也會處於阻塞狀態。git
在以前講解NIO基本知識的時候咱們提到過NIO經過通道選擇器能夠實現同時對多個通道的管理,實際上就是經過管理多個IO操做,換句話說是單線程處理多線程併發,有效的防止線程由於IO沒有就緒而被掛起。github
在使用NIO進行網絡編程的時候須要用到的就是通道選擇器,因此咱們先看一下通道選擇器的相關內容。編程
Java NIO中的 ServerSocketChannel 是一個能夠監聽新進來的TCP鏈接的通道, 就像標準IO中的ServerSocket同樣。服務器
//代開ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
複製代碼
經過 ServerSocketChannel.accept() 方法監聽新進來的鏈接。當 accept()方法返回的時候,它返回一個包含新進來的鏈接的 SocketChannel。 能夠設置成非阻塞模式。在非阻塞模式下,accept() 方法會馬上返回,若是尚未新進來的鏈接,返回的將是null。網絡
Java NIO中的SocketChannel是一個鏈接到TCP網絡套接字的通道。能夠經過如下2種方式建立SocketChannel:多線程
//打開一個SocketChannel並鏈接到互聯網上的某臺服務器。
socketChannel.connect(new InetSocketAddress("localhost",8888));
//一個新鏈接到達ServerSocketChannel時,會建立一個SocketChannel。
SocketChannel socketChannel = SocketChannel.open();
複製代碼
在前言中提到過,NIO經過通道選擇器能夠實現同時對多個通道的管理,其實就是同時對多個IO操做的管理,也就是實現了單線程處理多線程併發問題。對於操做系統來講,線程之間上下文切換的開銷很大,並且每一個線程都要佔用系統的一些資源(如內存)。所以太多的線程會耗費大量的資源,因此使用通道選擇器來對多個通道進行管理。併發
1. 建立Selectorsocket
//經過調用Selector.open()方法建立一個Selector,以下:
Selector selector = Selector.open();
複製代碼
2.將通道註冊到通道選擇器中優化
//爲了將Channel和Selector配合使用,必須將channel註冊到selector上。經過SelectableChannel.register()方法來實現
// 建立ServerSocketChanner
ServerSocketChannel ssc = ServerSocketChannel.open();
// 綁定端口號
ssc.bind(new InetSocketAddress(8888));
// 設置通道非阻塞
ssc.configureBlocking(false);
// 建立通道選擇器
Selector selector = Selector.open();
// 將通道註冊到通道選擇器中 要求:通道都必須是非阻塞的 意味着不能將FileChannel與Selector一塊兒使用,由於FileChannel不能切換到非阻塞模式
// 第二個參數是咱們須要通道選擇器幫咱們管理什麼事件類型 註冊爲接受就緒
ssc.register(selector, SelectionKey.OP_ACCEPT);
複製代碼
register()方法的第二個參數表明註冊的通道事件類型,一個通道觸發一個事件就意味着該事件準備就緒了,總共有四種事件:Connect(鏈接就緒 ) Accept(接受就緒) Read(有數據可讀的通道 讀就緒) Write(寫就緒)。對於選擇器而言,能夠針對性的找到(監聽)的事件就緒的通道,進行相關的操做。
這四種事件用SelectionKey的四個常量來表示:
能夠用「位或」操做符將常量鏈接起來
SelectionKey對象
對象中包含不少的屬性,譬如:
3.選擇器的select ()方法
select()方法返回的int值表示有多少通道已經就緒。自上次調用select()方法後有多少通道變成就緒狀態。若是調用select()方法,由於有一個通道變成就緒狀態,返回了1,若再次調用select()方法,若是另外一個通道就緒了,它會再次返回1。若是對第一個就緒的channel沒有作任何操做,如今就有兩個就緒的通道,但在每次select()方法調用之間,只會有一個通道就緒。
4.選擇器的selectedKeys()方法
經過調用selector的selectedKeys()方法,能夠獲得就緒通道的集合。遍歷集合能夠找到本身須要的通道進行相關的操做。
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//處理nio事件
if(key.isAcceptable()){
// 獲取通道
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = channel.accept();
// 註冊讀事件類型
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
// 讀取數據
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = -1;
while (true){
byteBuffer.clear();
len = socketChannel.read(byteBuffer);
if(len == -1){
break;
}
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
bos.write(byteBuffer.get());
}
}
// 打印讀取到的數據
System.out.println(bos.toString());
//寫數據給客戶端 註冊事件類型 寫事件
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
if(key.isWritable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
String msg = "你好,我是服務器";
ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.close();
}
iterator.remove();
}
複製代碼
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws Exception{
// 建立ServerSocketChanner
ServerSocketChannel ssc = ServerSocketChannel.open();
// 綁定端口號
ssc.bind(new InetSocketAddress(8888));
// 設置通道非阻塞
ssc.configureBlocking(false);
// 建立通道選擇器
Selector selector = Selector.open();
// 將通道註冊到通道選擇器中 要求:通道都必須是非阻塞的
// 第二個參數是咱們須要通道選擇器幫咱們管理什麼事件類型 註冊爲接受就緒
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 遍歷通道選擇器
while (true){
System.out.println("我在8888等你......");
// 返回準備就緒的通道數量
int nums = selector.select();
// 若是數量小於1說明沒有通道準備就緒 跳過本次循環
if(nums<1) {continue;}
// 獲取全部的keys(通道 事件類型)
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//處理nio事件
if(key.isAcceptable()){
// 獲取通道
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = channel.accept();
// 註冊讀事件類型
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
// 讀取數據
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = -1;
while (true){
byteBuffer.clear();
len = socketChannel.read(byteBuffer);
if(len == -1){
break;
}
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
bos.write(byteBuffer.get());
}
}
// 打印讀取到的數據
System.out.println(bos.toString());
//寫數據給客戶端 註冊事件類型 寫事件
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
if(key.isWritable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
String msg = "你好,我是服務器";
ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.close();
}
iterator.remove();
}
}
}
}
複製代碼
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOCleint {
public static void main(String[] args) throws IOException {
// 建立sc
SocketChannel socketChannel = SocketChannel.open();
// 鏈接服務器
socketChannel.connect(new InetSocketAddress("localhost",8888));
// 寫數據給服務器
String msg = "你好,我是客戶端";
ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
// 關閉輸出流
socketChannel.shutdownOutput();
// 讀取數據
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream bosread = new ByteArrayOutputStream();
int len = -1;
while (true){
readBuffer.clear();
len = socketChannel.read(readBuffer);
if(len == -1){
break;
}
readBuffer.flip();
while (readBuffer.hasRemaining()){
bosread.write(readBuffer.get());
}
}
System.out.println("我收到:"+bosread.toString());
socketChannel.close();
}
}
複製代碼
我不能保證每個地方都是對的,可是能夠保證每一句話,每一行代碼都是通過推敲和斟酌的。但願每一篇文章背後都是本身追求純粹技術人生的態度。
永遠相信美好的事情即將發生。