在IO執行有兩個階段很重要:html
當用戶進程調用了recvfrom這個系統調用,內核就開始了io的第一個階段:等待數據準備。若是數據還沒準備好(好比尚未收到一個完整的udp包),這時候內核要等待足夠的數據到來,而在用戶進程這邊,進程會被阻塞。當內核等到數據準備好,進程將數據從內核中拷貝到用戶空間,而後內核返回結果,用戶進程才解除block狀態,從新運行起來。數組
BIO在IO執行的兩個階段都被阻塞了。bash
示例代碼:socket
public static void server(){
ServerSocket serverSocket = null;
InputStream in = null;
try
{
serverSocket = new ServerSocket(8080);
int recvMsgSize = 0;
byte[] recvBuf = new byte[1024];
while(true){
Socket clntSocket = serverSocket.accept();
SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();
System.out.println("Handling client at "+clientAddress);
in = clntSocket.getInputStream();
while((recvMsgSize=in.read(recvBuf))!=-1){
byte[] temp = new byte[recvMsgSize];
System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);
System.out.println(new String(temp));
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally{
try{
if(serverSocket!=null){
serverSocket.close();
}
if(in!=null){
in.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
複製代碼
NIO是一種同步非阻塞的IO模型。同步指線程不斷輪詢IO事件是否就緒,非阻塞是指線程在等待IO的時候,能夠作其餘任務。同步的核心是Selector,Selector代替了線程自己輪詢IO事件,避免了阻塞同時減小了沒必要要的線程消耗;非阻塞的核心是通道和緩衝區,當IO事件就緒時,能夠經過寫入緩衝區,保證IO的成功,無需線程阻塞式地等待。學習
當用戶進程調用recvfrom時,系統不會阻塞用戶進程,若是數據還沒準備好,系統馬上返回一個ewouldblock錯誤給進程,用戶進程知道數據還沒準備好,用戶進程就能夠去作其餘事了。進程輪詢內核查看數據是否準備好。直到數據準備好了,而且收到用戶進程的system call,進程複製數據報,而後返回。ui
基本上,全部的IO在NIO中都從一個Channel開始。數據能夠從Channel讀到Buffer中,也能夠從Buffer寫到Channel。spa
Buffer經過如下幾個變量來保存這個數據的當前位置狀態:線程
capacity:緩衝區數組的總長度
position:下一個要操做的數據元素的位置
limit:緩衝區數組中不可操做的下一個元素的位置
mark:用於記錄當前position的前一個位置或者默認是-1
0 <= mark <= position <= limit <= capacity複製代碼
開始時Buffer的position爲0,limit爲capacity,程序寫入數據到緩衝區,position日後移。當Buffer寫入數據結束以後,調用flip()方法以後(此時limit=position,position=0),Buffer爲輸出數據作好準備;當Buffer輸出數據結束以後,調用clear()方法(此時limit=capacity,position=0),clear()方法不是清空Buffer的數據,它僅僅將position置爲0,將limit置爲capacity,爲再次向Buffer裝入數據作準備;3d
Buffer的使用:code
Buffer一些重要方法:
示例代碼:
public static void client(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try
{
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("10.10.195.115",8080));
if(socketChannel.finishConnect())
{
int i=0;
while(true)
{
TimeUnit.SECONDS.sleep(1);
String info = "I'm "+i+++"-th information from client";
buffer.clear();
buffer.put(info.getBytes());
buffer.flip();
while(buffer.hasRemaining()){
System.out.println(buffer);
socketChannel.write(buffer);
}
}
}
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
finally{
try{
if(socketChannel!=null){
socketChannel.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
複製代碼
Channel和Selector配合使用,必須先將Channel註冊到Selector上,經過register()方法來實現。Channel.register()方法會返回一個SelectionKey對象。這個對象表明了註冊到該Selector的通道。
一旦向Selector註冊了一個或多個通道,就能夠調用select()方法。
select()方法返回的int值表示有多少通道已經就緒。
參考資料:瘋狂Java講義