inputStream,outputStream
(2)面向字符操做的接口:Reader,Writer
File
(2)面向網絡操做的I/O接口:Socket
以網絡IO爲例:html
當客戶端發送的網絡包通過路由器和交換器的轉發後到達對應服務端的網絡適配器(網卡),並存儲在對應網絡I/O的套接字文件中,而後操做系統會將該文件中的數據通常經過DMA
複製到內存中供應用程序使用;java
在Unix網絡編程
這本書中概述了完成上述操做的幾種模型:編程
(1)阻塞與非阻塞: 阻塞與非阻塞主要是從 CPU 的消耗上來講的,阻塞就是 CPU 停下來等待一個慢的操做完成後CPU 才接着完成其它的事。非阻塞就是在這個慢的操做在執行時 CPU 去幹其它別的事,等這個慢的操做完成時,CPU 再接着完成後續的操做。bash
(2)同步與非同步: 同步與非同步主要是從程序方面來講的,同步指程序發出一個功能調用後,沒有結果前不會返回。非同步是指程序發出一個功能調用後,程序便會返回,而後再經過回調機制通知程序進行處理。服務器
recvfrom方法
等待客戶端發送的數據發送到內存並返回;
這個模型最大的問題就是操做系統中最典型的CPU速度與外設速度不匹配的問題,網絡適配器的速度相對於CPU的速度是極慢的,而且此時CPU卻一直在阻塞。網絡
recvfrom 方法
後,若是此時套接字文件尚未準備好,則直接返回一個錯誤信息,而後CPU就會去作其餘事情,而該線程會不斷獲取CPU時間片進行輪詢,因此該模式下雖然是非阻塞,但其線程切換確實很頻繁的,因此經過該方式增長的CPU使用時間與線程切換的成本仍是須要好好評估的;
而且當數據準備好後,而且線程獲取到時間片再次調用recvfrom
時,線程仍是須要等待數據拷貝至內存的。異步
多路複用IO:(Java NIO原理) socket
select
,該方法一直會阻塞到IO事件的到來(即套接字文件準備好)再返回,這個時候咱們再調用recvfrom方法
就只須要等待數據拷貝至內存便可;而且select方法
能夠監聽多個事件,因此聯繫到Java NIO中時,就是多個線程能夠向同一個Selector
註冊多個事件,從而達到了多路複用的效果。 異步IO(AIO):post
aio_read
,應用程序調用後便直接返回,而且不須要像前幾種模型同樣須要等待數據拷貝至內存;
但其內在的實現仍是很複雜的,底層仍是使用BIO實現的,就不展開描述了,由於對編程人員好像並無太大的做用。性能
網絡適配器(網卡)的數據準備好的這個過程當中
,而都是通發出種信號進行通知應用程序,雖然信號的實現方式或是用
select
或是用更底層的方式,但本質上仍是很類似的;但信號驅動IO也是須要線程等待數據拷貝至用戶空間的。
Java中的Socket是對進行通訊的兩端的抽象,其封裝了一系列TCP/IP層面的底層操做; 代碼以下:
//經過一個IP:PORT套接字新建一個Socket對象,肯定要鏈接的服務器的位置和端口
Socket socket = new Socket("127.0.0.1", 8089);
//經過Socket對象拿到OutputStream,能夠將其理解經過其向服務器端對應的套接字文件寫入數據
OutputStream outputStream = socket.getOutputStream();
//使用默認的字符集去解析outputStream的字節流
PrintWriter printWriter = new PrintWriter(outputStream, true);
/*向服務器發送一個HTTP1.1的請求*/
printWriter.println("GET /index.html HTTP/1.1");
printWriter.println("Host: localhost:8080");
printWriter.println("Connection Close");
printWriter.println();
複製代碼
//ServerSocket在該套接字上監聽鏈接事件
ServerSocket serverSocket = new ServerSocket(8089, 1, InetAddress.getByName("127.0.0.1"));
//服務端阻塞在accept()方法上,直到客戶端的connect()請求,並返回一個Socket對象
socket = serverSocket.accept();
//從返回的Socket對象中獲取該Socket對應的套接字文件的內容並進行讀取
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
int i = 0;
while (i != -1) {
i = bufferedReader.read();
System.out.println("拿到的數據爲:"+(char)i);
}
socket.close();
複製代碼
其實Java BIO 即爲對系統提供的網絡I/O方法的封裝;
咱們通常都是適用Acceptor模型來進行BIO服務端的建立
,即經過一個ServerSocket()
監聽來自客戶端的鏈接,而後經過三次握手創建鏈接後便會建立一個子線程並經過線程池進行相應的邏輯處理;
backlog
這個參數來代表在服務端拒絕鏈接請求以前,能夠排隊的請求數量,因此這樣的模型註定了BIO性能的侷限性(排隊的通訊線程可能要阻塞一段時間),處理量的侷限性;Java NIO
經過多路複用IO的模型實現了單個Selector線程
管理了多個鏈接,解決了BIO最致命的一個問題;
不管是In/OutputStream
仍是Java NIO中的通道channel
本質上都是對網絡I/O文件的抽象,與前者不一樣,channel
是雙通道的,既能夠讀又能夠寫。
因此按照I/O多路複用 的模型,當channel
中的數據準備好了的時候會返回一個可讀的事件,而且經過selector進行處理,安排相應的Socket進行相應數據的讀取,這是一個數據可讀的事件,而Selector可監聽的事件有四種:
SelectionKey.OP_CONNECT // 鏈接事件
SelectionKey.OP_ACCEPT //接收事件
SelectionKey.OP_READ //數據可讀事件
SelectionKey.OP_WRITE //可寫事件
複製代碼
socket.getInputStream.write()
方法來直接進行讀寫的,而NIO中向channel
中寫入數據必須從buffer中獲取,而channel
也只能向buffer寫入數據,這樣使得這樣的操做更爲接近操做系統執行I/O的方式;細一點講,是由於在向OutputStream中write()
數據即爲向接收方Socket對象中的InputStream
中的RecvQ隊列中,而若是write()
的數據大於隊列中每一個數據對象限定的長度,就須要進行拆分,而這個過程,咱們是不能夠控制的,並且涉及到用戶空間與內核空間地址的轉換;可是當咱們使用Buffer後,咱們能夠控制Buffer的長度,是否擴容以及如何擴容咱們均可以掌握。 參考文章:www.ibm.com/developerwo…/**
* @CreatedBy:CVNot
* @Date:2020/2/21/15:30
* @Description:
*/
public class NIOServer {
public static void main(String[] args) {
try {
//建立一個多路複用選擇器
Selector selector = Selector.open();
//建立一個ServerSocket通道,並監聽8080端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(8080));
//設置爲非阻塞
serverSocketChannel.configureBlocking(false);
//監聽接收數據的事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
selector.select();
//拿到Selector關心的已經到達事件的SelectionKey集合
Set keys = selector.selectedKeys();
Iterator iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = (SelectionKey)iterator.next();
iterator.remove();
//由於咱們只註冊了ACCEPT事件,因此這裏只寫了當鏈接處於這個狀態時的處理程序
if(selectionKey.isAcceptable()){
//拿到產生這個事件的通道
ServerSocketChannel serverChannel = (ServerSocketChannel)selectionKey.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
//併爲這個通道註冊一個讀事件
clientChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
}
else if(selectionKey.isReadable()){
SocketChannel clientChannel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
long bytesRead = clientChannel.read(byteBuffer);
while(bytesRead > 0){
byteBuffer.flip();
System.out.printf("來自客戶端的數據" + new String(byteBuffer.array()));
byteBuffer.clear();
bytesRead = clientChannel.read(byteBuffer);
}
byteBuffer.clear();
byteBuffer.put("客戶端你好".getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
客戶端:
/**
* @CreatedBy:CVNot
* @Date:2020/2/21/16:06
* @Description:
*/
public class NIOClient {
public static void main(String[] args) {
try {
Selector selector = Selector.open();
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(8080));
clientChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
//若是事件沒到達就一直阻塞着
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()) {
/**
* 鏈接服務器端成功
*
* 首先獲取到clientChannel,而後經過Buffer寫入數據,而後爲clientChannel註冊OP_READ事件
*/
clientChannel = (SocketChannel) key.channel();
if (clientChannel.isConnectionPending()) {
clientChannel.finishConnect();
}
clientChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.clear();
byteBuffer.put("服務端你好,我是客戶端".getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
clientChannel.register(key.selector(), SelectionKey.OP_READ);
} else if (key.isReadable()) {
//通道能夠讀數據
clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
long bytesRead = clientChannel.read(byteBuffer);
while (bytesRead > 0) {
byteBuffer.flip();
System.out.println("server data :" + new String(byteBuffer.array()));
byteBuffer.clear();
bytesRead = clientChannel.read(byteBuffer);
}
} else if (key.isWritable() && key.isValid()) {
//通道能夠寫數據
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼