NIO同步非阻塞IO

一、NIO概述

1.首先介紹一下BIO(同步阻塞IO)

BIO-JDK1.0 - 同步阻塞式IO-BlockingIO

在執行ACCEPT CONNECT READ WRITE 四中操作時都會產生阻塞

Accept:客戶端未連接
Connect:連接超時(connection reset);連接拒絕connection refuse
Read:服務器端讀取數據,但是客戶端未寫入數據,產生阻塞
Write:客戶端寫入數據,服務器端未讀取,當寫入數據數量達到緩衝區極限,產生阻塞

在平常開發當中並不是問題 甚至因爲這樣的模型直觀而簡單 應用的場景非常廣泛

但是在高併發的場景下 這樣的阻塞式IO可能會造成如下問題:

在服務器開發中 需要在服務器端通過少量線程處理多個客戶端請求 這就要求 在少量的線程應該可以靈活的切換處理不同客戶端 但傳統的BIO阻塞式的工作方式 一旦阻塞了線程 線程就被掛起 無法繼續執行 無法實現這樣的功能

2.NIO - JDK4.0 - 同步非阻塞式IO - NonBlockingIO/NewIO
    操作:    Accepet、  Connect、 Read、Write(非阻塞的)
    可以隨時讓線程切換所處理的客戶端 從而可以實現高併發服務器的開發
    需求:用少量的線程來處理多個客戶端請求
    騰訊QQ聊天案例
        多人連接服務器,通過一箇中心選擇器進行管理,當多人需要聊天發送消息時(排隊處理),提供線程,一個客戶佔用線程沒發消息,導致其他人消息發不出去(BIO)
        使用NIO用少量的線程來處理多個客戶端請求,讓線程可以隨時切換所處理的客戶端

兩者特點對比:

BIO:同步阻塞式IO 面向流 操作字節或字符 單向傳輸數據
NIO:同步非阻塞式IO 面向通道 操作緩衝區 雙向傳輸數據

二、開源的NIO結構的服務器框架(瞭解)

        MINA
        Netty(更好)
    IO方式:
        阻塞/非阻塞:
討論的是線程的角度,當執行某些操作不能立即完成時,線程是否被掛起,失去cpu爭奪權無法繼續執行直到阻塞結束或被喚醒 
        同步/異步:討論的是參與通信雙方的工作機制,是否需要互相等待對方的執行
            同步:
              
 通信過程中 一方在處理通信,另一方 要等待對方執行不能去做其他無關的事
            異步:
                
通信過程中 一方在處理通信,另一方 可以不用等待對方而可以去做其他無關的事 直到對方處理通信完成 再在適合的時候繼續處理通信過程
    三種IO機制的區別!!!

 BIO jdk1.0  同步阻塞式IO 面向流 操作字節或字符 單向傳輸數據
 NIO jdk4.0  同步非阻塞式IO  面向通道 操作緩衝區 雙向傳輸數據
AIO  jdk7.0 異步非阻塞式IO 大量使用回調函數 異步處理通信過程  

三、粘包問題

概念:通過socket發送多段數據時 底層的tcp協議 會自動根據需要 將數據 拆分或合併 組成數據包後發送給接受者 ,接受者收到數據後 無法直接通過tcp協議本身判斷數據的邊界,這個問題就稱之爲粘包問題.

產生原因:

1.本質上是因爲tcp協議是傳輸層的協議 本身沒有對會話控制提供相應的能力

2.socket開發網絡程序時相當於在自己實現會話層、表示層和應用層的功能,所以需要自己來相辦法解決粘包問題
舉例:aaa  bbbbb  cccc
    TCP屬於傳輸層,底層靠數據包傳輸,只負責傳輸數據,不負責分配數據格式,結果爲ccccbbbbbaaa
    導致不能明確數據的結構進行分段

解決方案:
    a.只發送固定長度的數據
        通信的雙發約定每次發送數據的長度,每次只發送固定長度的數據,接收數據方 每次都按照固定長度獲取數據
        缺點:
            不夠靈活,只適合每次傳輸的數據都有固定長度的場景
    b. 約定分隔符
        通信雙方約定一個特殊的分隔符用來表示數據的邊界,接收方收到數據時,不停讀取,以分隔符爲標誌,區分數據的邊界
        缺點:
            如果數據本身就包含分隔符字符,則需要對數據進行預處理將數據本身包含的分隔符進行轉義,相對來說比較麻煩
    c. 數據分頭和體,在頭信息中描述數據長度或格式
        通過頭信息中傳遞數據長度或格式信息,在接收方法接收數據時,先讀取頭信息,在根據頭信息來決定如何獲取後續數據

 

協議:
    公共協議:HTTP FTP SMTP POP3等等,需按照協議來通信,約束較大
    通信問題解決:真正在開發過程中,如果需要開發底層網絡通信機制,可以根據需要選擇公有協議 或 自定義私有協議來解決通信規範問題

四、NIO通信案例

服務端:

服務端:
package cn.tedu.nio.channel;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ServerSocketChannelDemo01 {
	public static void main(String[] args) throws Exception {
		//1.創建ServerSockentChannel對象
		ServerSocketChannel ssc = ServerSocketChannel.open();
		//2.綁定指定端口
		ssc.bind(new InetSocketAddress(44444));
		//3.設置非阻塞模式
		ssc.configureBlocking(false);
		//4.接收客戶端連接
		SocketChannel sc = null;
		while(sc == null){
			sc = ssc.accept();
		}
		sc.configureBlocking(false);
		//5.讀取數據
		ByteBuffer buf = ByteBuffer.allocate(5);
		while(buf.hasRemaining()){
			sc.read(buf);
		}
		//6.獲取數據打印
		byte[] arr = buf.array();
		String str = new String(arr);
		System.out.println(str);
		
		//5.關閉通道
		sc.close();
		ssc.close();
	}

}

客戶端:

客戶端:
	package cn.tedu.nio.channel;
	
	import java.net.InetSocketAddress;
	import java.nio.ByteBuffer;
	import java.nio.channels.SocketChannel;
	
	public class SocketChannelDemo01 {
		public static void main(String[] args) throws Exception {
			//1.創建客戶端SocketChannel
			SocketChannel sc = SocketChannel.open();
			//2.配置啓用非阻塞模式
			sc.configureBlocking(false);
			//3.連接服務器
			boolean isConn = sc.connect(new InetSocketAddress("127.0.0.1", 44444));
			if(!isConn){
				while(!sc.finishConnect()){
				}
			}
			
			//4.發送數據到服務器
			ByteBuffer buf = ByteBuffer.wrap("abcde".getBytes());
			while(buf.hasRemaining()){
				sc.write(buf);
			}
			
			//5.關閉通道
			sc.close();
		}
	}

五、少量線程處理多客戶端請求案例

客戶端:

客戶端:
	package cn.tedu.nio.channel;
	
	import java.net.InetSocketAddress;
	import java.nio.ByteBuffer;
	import java.nio.channels.SocketChannel;
	
	public class SocketChannelDemo01 {
		public static void main(String[] args) throws Exception {
			//1.創建客戶端SocketChannel
			SocketChannel sc = SocketChannel.open();
			//2.配置啓用非阻塞模式
			sc.configureBlocking(false);
			//3.連接服務器
			boolean isConn = sc.connect(new InetSocketAddress("127.0.0.1", 44444));
			if(!isConn){
				while(!sc.finishConnect()){
				}
			}
			
			//4.發送數據到服務器
			ByteBuffer buf = ByteBuffer.wrap("abcde".getBytes());
			while(buf.hasRemaining()){
				sc.write(buf);
			}
			
			//5.關閉通道
			sc.close();
		}
	}

服務端:

	package cn.tedu.nio.selector;
	
	import java.net.InetSocketAddress;
	import java.nio.ByteBuffer;
	import java.nio.channels.SelectionKey;
	import java.nio.channels.Selector;
	import java.nio.channels.ServerSocketChannel;
	import java.nio.channels.SocketChannel;
	import java.util.Iterator;
	import java.util.Set;
	
	public class ServerSocketDemo01 {
		public static void main(String[] args) throws Exception {
			//0.創建選擇器
			Selector selc = Selector.open();
			//1.創建代表服務器的ServerSocketChannel對象
			ServerSocketChannel ssc = ServerSocketChannel.open();
			//2.設置爲非阻塞模式
			ssc.configureBlocking(false);
			//3.設置監聽的端口
			ssc.bind(new InetSocketAddress(44444));
			//4.將ssc註冊到選擇器中關注ACCEPT操作
			ssc.register(selc, SelectionKey.OP_ACCEPT);
			
			//5.通過選擇器選擇就緒的鍵
			while(true){
				selc.select();//嘗試到註冊的鍵集中來尋找就緒的鍵 如果一個就緒的鍵都找不到 就進入阻塞 直到找到就緒的鍵 返回就緒的鍵的個數
				
				//6.獲取就緒的鍵的集合
				Set<SelectionKey> keys = selc.selectedKeys();
				
				//7.遍歷處理就緒的鍵 代表的操作
				Iterator<SelectionKey> it = keys.iterator();
				while(it.hasNext()){
					//--獲取到就緒的鍵 根據鍵代表的操作的不同 來進行不同處理
					SelectionKey key = it.next();
					
					if(key.isAcceptable()){
						//--發現了Accept操作 
						//--獲取通道
						ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
						//--完成Accept操作
						SocketChannel sc = sscx.accept();
						//--在sc上註冊讀數據的操作
						sc.configureBlocking(false);
						sc.register(selc, SelectionKey.OP_READ);
					}else if(key.isConnectable()){
						
					}else if(key.isWritable()){
						
					}else if(key.isReadable()){
						//--發現了Read操作
						//--獲取就緒的通道
						SocketChannel scx = (SocketChannel) key.channel();
						//--完成讀取數據的操作
						ByteBuffer buf = ByteBuffer.allocate(10);
						while(buf.hasRemaining()){
							scx.read(buf);
						}
						String msg = new String(buf.array());
						System.out.println("[收到來自客戶端的消息]:"+msg);
					}else{
						throw new RuntimeException("未知的鍵,見了鬼了~");
					}
					
					//8.移除處理完的鍵
					it.remove();
				}
			}
		}
	}

 

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