netty網絡編程(一)之Java IO與NIO

Linux I/O模型介紹

Linux IO流程

一個socket進程進行一次read可以分成兩個階段,等待數據是否準備好,以及數據從內核copy到用戶空間
在這裏插入圖片描述

Linux IO模型

網絡IO的本質是socket的讀取socket在linux系統被抽象爲流IO可以理解爲對流的操作。對於一次IO訪問(以read爲例),數據會先被拷貝到操作系統內核的緩衝區中,然後纔會從操作系統內核的緩衝區拷貝到應用程序的地址空間。對於socket流而言

  • 通常涉及等待網絡上的數據分組到達,然後被複制到內核的某個緩衝區。
  • 把數據從內核緩衝區複製到應用進程緩衝區。

網絡應用需要處理2類問題:網絡IO和數據計算,相對於數據計算,網絡IO的延遲,給應用程序帶來的性能瓶頸較大。所以需要選擇一種合理的網絡IO模型,常見的網絡IO模型有5種。分別是阻塞式I/O模型、非阻塞式I/O模型、I/O多路複用、信號驅動式I/O、異步I/O

阻塞式I/O模型

  • 阻塞就是一直等待到事情做完才返回
  • 如果是阻塞進程read一直等待,進程控制權就沒了,要阻塞兩個階段,數據到來和數據從內核copy到進程緩衝區。

非阻塞式I/O模型

  • 非阻塞就是立馬返回一個消息(沒做好或者已經做好)
  • 非阻塞則是read一發出立馬返回一個錯誤信息,程序裏必須不斷去read直到數據到達返回可讀消息,然後進程阻塞把數據從內核緩衝區copy到進程緩衝區,實際阻塞的是後面取數據的階段。

阻塞式IO和非阻塞式IO的對比

針對一個IO,非阻塞IO沒有阻塞IO的效率高。而且相對於阻塞而言,非阻塞是輪詢要發出很多次系統調用的,而且很多次都是空輪詢,看起來毫無優勢可言。但是非阻塞IO因爲等待數據階段是非阻塞的,所以可以同時進行很多個IO操作

I/O多路複用

儘管非阻塞支持同時多IO,但是資源上還是很浪費,存在很多空輪詢。於是出現了一種新的IO模型,叫做IO多路複用模型。例如select模型、poll模型、epoll模型

select模型

select模型支持一次性監控一大堆感興趣的IO事件發生的描述符集合,一旦有一個或多個IO準備就緒,就返回所有描述符集合。然後掃描返回列表,找到有可讀或者可寫的描述符,可能是一個也可能多個,進行相應第二階段讀取或者寫入數據。select模型調用的時候也是阻塞的,只是它支持多路IO前提下減少了不必要的空輪詢。
優點:
跨平臺性好
缺點:

  • 首先支持的監控的描述符集合數量有限制,內核參數決定的。
  • 每次select調用進程是阻塞的,並且需要把描述符集合從用戶態copy到內核態,一旦數量增加,資源消耗線性增加。
  • select返回的描述符是沒有經過篩選的

信號驅動式I/O

首先我們允許Socket進行信號驅動IO,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。
數據準備階段:未阻塞,當數據準備完成之後,會主動的通知用戶進程數據已經準備完成,對用戶進程做一個回調。
數據拷貝階段阻塞用戶進程,等待數據拷貝。

異步I/O

相對於同步IO,異步IO不是順序執行。用戶進程進行aio_read系統調用之後,無論內核數據是否準備好,都會直接返回給用戶進程,然後用戶態進程可以去做別的事情。等到socket數據準備好了,內核直接複製數據給進程,然後從內核向進程發送通知。IO兩個階段,進程都是非阻塞的

小結

在這裏插入圖片描述

Java I/O

單線程程序

客戶端

package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class EchoClient {
	public static void main(String[] args) {

		Socket echoSocket = null;
		PrintWriter out = null;
		BufferedReader in = null;

		try {

			echoSocket = new Socket("127.0.0.1", 8080);
 			out = new PrintWriter(echoSocket.getOutputStream(), true);
 			in = new BufferedReader(new InputStreamReader(
					echoSocket.getInputStream()));
			System.out.println("連接到服務器......");
			System.out.println("請輸入消息[輸入\"Quit\"]退出:");
			BufferedReader stdIn = new BufferedReader(new InputStreamReader(
					System.in));
			String userInput;

			while ((userInput = stdIn.readLine()) != null) {
				out.println(userInput);
				System.out.println(in.readLine());

				if (userInput.equals("Quit")) {
					System.out.println("關閉客戶端......");
					out.close();
					in.close();
					stdIn.close();
					echoSocket.close();
					System.exit(1);
				}
				System.out.println("請輸入消息[輸入\"Quit\"]退出:");
			}
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host: PallaviÕs MacBook Pro.");
			System.exit(1);
		} catch (IOException e) {
			System.err.println("Couldn't get I/O for "
					+ "the connection to: PallaviÕs MacBook Pro.");
			System.exit(1);
		}

	}
}

服務端

package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SingleThreadEchoServer {

	private int port;

	public SingleThreadEchoServer(int port) {
		this.port = port;
	}

	public void startServer() {
		ServerSocket echoServer = null;
		int i = 0;
		System.out.println("服務器在端口[" + this.port + "]等待客戶請求......");
		try {
			echoServer = new ServerSocket(this.port);
			while (true) {
				Socket clientRequest = echoServer.accept();
				handleRequest(clientRequest, i++);
			}
		} catch (IOException e) {
			System.out.println(e);
		}
	}

	private void handleRequest(Socket clientSocket, int clientNo) {
		PrintStream os = null;
		BufferedReader in = null;
		try {
			in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			os = new PrintStream(clientSocket.getOutputStream());
			String inputLine;
			while ((inputLine = in.readLine()) != null) {

				// 輸入'Quit'退出
				if (inputLine.equals("Quit")) {
					System.out.println("關閉與客戶端[" + clientNo + "]......" + clientNo);
					os.close();
					in.close();
					clientSocket.close();
					break;
				} else {
					System.out.println("來自客戶端[" + clientNo + "]的輸入: [" + inputLine + "]!");
					os.println("來自服務器端的響應:" + inputLine);
				}
			}
		} catch (IOException e) {
			System.out.println("Stream closed");
		}
	}

	public static void main(String[] args) throws IOException {
		new SingleThreadEchoServer(8080).startServer();
	}
}

運行結果如下
在這裏插入圖片描述
在這裏插入圖片描述

多線程程序

在這裏插入圖片描述

客戶端

package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class EchoClient {
	public static void main(String[] args) {

		Socket echoSocket = null;
		PrintWriter out = null;
		BufferedReader in = null;

		try {

			echoSocket = new Socket("127.0.0.1", 8080);
 			out = new PrintWriter(echoSocket.getOutputStream(), true);
 			in = new BufferedReader(new InputStreamReader(
					echoSocket.getInputStream()));
			System.out.println("連接到服務器......");
			System.out.println("請輸入消息[輸入\"Quit\"]退出:");
			BufferedReader stdIn = new BufferedReader(new InputStreamReader(
					System.in));
			String userInput;

			while ((userInput = stdIn.readLine()) != null) {
				out.println(userInput);
				System.out.println(in.readLine());

				if (userInput.equals("Quit")) {
					System.out.println("關閉客戶端......");
					out.close();
					in.close();
					stdIn.close();
					echoSocket.close();
					System.exit(1);
				}
				System.out.println("請輸入消息[輸入\"Quit\"]退出:");
			}
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host: PallaviÕs MacBook Pro.");
			System.exit(1);
		} catch (IOException e) {
			System.err.println("Couldn't get I/O for "
					+ "the connection to: PallaviÕs MacBook Pro.");
			System.exit(1);
		}

	}

}

服務端

package com.demo.io;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
 

public class MultiThreadedEchoServerV1 {
	private int port;
	
	public MultiThreadedEchoServerV1(int port) {
		this.port = port;
	}
	
	public void startServer() {
 		ServerSocket echoServer = null;
		int i = 0;
		System.out.println("服務器在端口[" + this.port + "]等待客戶請求......");
		try {
			echoServer = new ServerSocket(8080);
			while (true) {
				Socket clientRequest = echoServer.accept();
				new Thread(new ThreadedServerHandler(clientRequest, i++)).start();
			}
		} catch (IOException e) {
			System.out.println(e);
		}

	}
 
    public static void main(String[] args) throws IOException {
    	new MultiThreadedEchoServerV1(8080).startServer();
     }
}
package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class ThreadedServerHandler implements Runnable {
	Socket clientSocket = null;
	int clientNo = 0;

	ThreadedServerHandler(Socket socket, int i) {
		if (socket != null) {
			clientSocket = socket;
			clientNo = i;
			System.out.println("創建線程爲[" + clientNo + "]號客戶服務...");
		}
	}

	@Override
	public void run() {
		PrintStream os = null;
		BufferedReader in = null;
		try {
			in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			os = new PrintStream(clientSocket.getOutputStream());
			String inputLine;
			while ((inputLine = in.readLine()) != null) {

				// 輸入'Quit'退出
				if (inputLine.equals("Quit")) {
					System.out.println("關閉與客戶端[" + clientNo + "]......" + clientNo);
					os.close();
					in.close();
					clientSocket.close();
					break;
				} else {
					System.out.println("來自客戶端[" + clientNo + "]的輸入: [" + inputLine + "]!");
					os.println("來自服務器端的響應:" + inputLine);
				}
			}
		} catch (IOException e) {
			System.out.println("Stream closed");
		}
	}
}

運行結果如下
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

Java NIO

3大核心組件:Buffer、Channel、Selector

Java IO和Java NIO對比

在這裏插入圖片描述

Buffer

一個 Buffer 本質上是內存中的一塊, 可以將數據寫入這塊內存, 從這塊內存獲取數據。
在這裏插入圖片描述
Java NIO Buffer三大核心概念:position、limit、capacity

  • 代表這個緩衝區的容量,一旦設定就不可以更改。比如 capacity 爲 1024 的 IntBuffer,代表其一次可以存放 1024 個 int 類型的值。
  • 一旦 Buffer 的容量達到 capacity,需要清空 Buffer,才能重新寫入值。
  • 從寫操作模式到讀操作模式切換的時候(flip),position 都會歸零,這樣就可以從頭開始讀寫了。
  • 寫操作模式下,limit 代表的是最大能寫入的數據,這個時候 limit 等於 capacity。
  • 寫結束後,切換到讀模式,此時的 limit 等於 Buffer 中實際的數據大小,因爲 Buffer 不一定被寫滿了。

案例

理解capacity、limit、position、mark
0 – mark – position – limit – capacity

package com.demo.nio.buffers;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;

public class BufferCreate {
	public static void main(String[] args) {
		ByteBuffer buffer0 = ByteBuffer.allocate(10);
		if (buffer0.hasArray()) {
			System.out.println("buffer0 array: " + buffer0.array());
			System.out.println("Buffer0 array offset: " + buffer0.arrayOffset());

		}
		System.out.println("Capacity: " + buffer0.capacity());
		System.out.println("Limit: " + buffer0.limit());
		System.out.println("Position: " + buffer0.position());
		System.out.println("Remaining: " + buffer0.remaining());
		System.out.println();

		ByteBuffer buffer1 = ByteBuffer.allocateDirect(10);
		if (buffer1.hasArray()) {
			System.out.println("buffer1 array: " + buffer1.array());
			System.out.println("Buffer1 array offset: " + buffer1.arrayOffset());
		}
		System.out.println("Capacity: " + buffer1.capacity());
		System.out.println("Limit: " + buffer1.limit());
		System.out.println("Position: " + buffer1.position());
		System.out.println("Remaining: " + buffer1.remaining());
		System.out.println();

		byte[] bytes = new byte[10];
		ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
		if (buffer2.hasArray()) {
			System.out.println("buffer2 array: " + buffer2.array());
			System.out.println("Buffer2 array offset: " + buffer2.arrayOffset());

		}
		System.out.println("Capacity: " + buffer2.capacity());
		System.out.println("Limit: " + buffer2.limit());
		System.out.println("Position: " + buffer2.position());
		System.out.println("Remaining: " + buffer2.remaining());
		System.out.println();

		byte[] bytes2 = new byte[10];
		ByteBuffer buffer3 = ByteBuffer.wrap(bytes2, 2, 3);
		if (buffer3.hasArray()) {
			System.out.println("buffer3 array: " + buffer3.array());
			System.out.println("Buffer3 array offset: " + buffer3.arrayOffset());
		}
		System.out.println("Capacity: " + buffer3.capacity());
		System.out.println("Limit: " + buffer3.limit());
		System.out.println("Position: " + buffer3.position());
		System.out.println("Remaining: " + buffer3.remaining());

	}
}

在這裏插入圖片描述

package com.demo.nio.buffers;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;


public class BufferAccess {
	public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocate(10);
		printBuffer(buffer);
		
		buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
		printBuffer(buffer);

		buffer.flip();
		printBuffer(buffer);

		//取buffer
		System.out.println("" + (char) buffer.get() + (char) buffer.get());
		printBuffer(buffer);

		buffer.mark();
		printBuffer(buffer);

		//讀取兩個元素後,恢復到之前mark的位置處
		System.out.println("" + (char) buffer.get() + (char) buffer.get());
		printBuffer(buffer);

		buffer.reset();
		//buffer.rewind();

		printBuffer(buffer);


		buffer.compact();
		printBuffer(buffer);
		

		buffer.clear();
		printBuffer(buffer);

	}
	
	private static void printBuffer(Buffer buffer) {
		System.out.println("[limit=" + buffer.limit() 
				+", position = " + buffer.position()
				+", capacity = " + buffer.capacity()
 				+", array = " + buffer.toString()+"]");
	}
}

在這裏插入圖片描述

package com.demo.nio.buffers;

import java.nio.Buffer;
import java.nio.ByteBuffer;

/**
 * 緩衝區分片與數據共享
 */
public class BufferSlice {
	public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocate(10);
		for(int i = 0; i < buffer.capacity(); i++) {
			buffer.put((byte) i);
		}
		
		printBuffer(buffer);
		//
		buffer.position(3).limit(7);
		
		printBuffer(buffer);

		ByteBuffer sliceBuffer = buffer.slice();
		for(int i = 0;i < sliceBuffer.capacity();i++) {
			byte b = sliceBuffer.get();
			b *= 11;
			sliceBuffer.put(i, b);
		}
		printBuffer(sliceBuffer);

		buffer.position(0).limit(buffer.capacity());
		while(buffer.hasRemaining()) {
			System.out.println(buffer.get());
		}
		
		printBuffer(buffer);

	}
	
	private static void printBuffer(Buffer buffer) {
		System.out.println("[limit=" + buffer.limit() 
				+", position = " + buffer.position()
				+", capacity = " + buffer.capacity()
				+", array = " + buffer.toString()+"]");
	}
}

在這裏插入圖片描述

package com.demo.nio.buffers;

import java.nio.Buffer;
import java.nio.CharBuffer;


public class DuplicateBuffer {

	public static void main(String[] args) {
		CharBuffer buffer = CharBuffer.allocate(8);
		for(int i= 0 ; i < buffer.capacity() ; i++) {
			buffer.put(String.valueOf(i).charAt(0));
		}
		printBuffer(buffer);

		buffer.flip();
		printBuffer(buffer);
		
 		buffer.position(3).limit(6).mark().position(5);
 		printBuffer(buffer);

		CharBuffer dupeBuffer = buffer.duplicate();
		buffer.clear();
 		printBuffer(buffer);
 		
		printBuffer(dupeBuffer);
		
 		dupeBuffer.clear();
		printBuffer(dupeBuffer);
		
	}
	
	private static void printBuffer(Buffer buffer) {
		System.out.println("[limit=" + buffer.limit() 
				+", position = " + buffer.position()
				+", capacity = " + buffer.capacity()
				+", array = " + buffer.toString()+"]");
	}

}

在這裏插入圖片描述

package com.demo.nio.buffers;

import java.nio.*;

public class SliceBuffer {
    static public void main(String args[]) throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        for (int i = 0; i < buffer.capacity(); ++i) {
            buffer.put((byte) i);
        }

        buffer.position(3);
        buffer.limit(7);

        ByteBuffer slice = buffer.slice();

        for (int i = 0; i < slice.capacity(); ++i) {
            byte b = slice.get(i);
            b *= 11;
            slice.put(i, b);
        }

        buffer.position(0);
        buffer.limit(buffer.capacity());

        while (buffer.remaining() > 0) {
            System.out.println(buffer.get());
        }
    }
}

在這裏插入圖片描述

DirectByteBuffer和non-direct ByteBuffer對比

在這裏插入圖片描述

小結

Buffer創建
1、allocate/allocateDirect方法
2、wrap方法
Buffer讀取
1、put/get方法
2、flip方法
3、mark/reset方法
4、compact方法
5、rewind/clear
Buffer複製 – 淺複製
1、duplicate方法
2、asReadOnlyBuffer方法
3、slice方法

Channel

NIO 操作始於通道,通道是數據來源數據寫入的目的地
在這裏插入圖片描述
FileChannel:文件通道,用於文件的讀和寫
DatagramChannel:用於 UDP 連接的接收和發送
SocketChannel:把它理解爲 TCP 連接通道,簡單理解就是 TCP 客戶端
ServerSocketChannel:TCP 對應的服務端,用於監聽某個端口進來的請求
在這裏插入圖片描述
在這裏插入圖片描述

案例

package com.demo.nio.channels;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;


public class CopyFile {
    static public void main(String args[]) throws Exception {
        String infile = "H:\\workspace\\learning-nio\\src\\main\\resources\\CopyFile.java";
        String outfile = "H:\\workspace\\learning-nio\\src\\main\\resources\\CopyFile.java.copy";


        // 從流中獲取通道
        FileInputStream fin = new FileInputStream(infile);
        FileOutputStream fout = new FileOutputStream(outfile);

        FileChannel fcin = fin.getChannel();
        FileChannel fcout = fout.getChannel();

        // 創建緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            // 讀入之前要清空
            buffer.clear();

            // position自動前進
            int r = fcin.read(buffer);

            if (r == -1) {
                break;
            }

            // position = 0; limit=讀到的字節數
            buffer.flip();

            // 從 buffer 中讀
            fcout.write(buffer);
        }
    }
}

在這裏插入圖片描述

Selector

Selector是Java NIO中的一個組件,用於檢查一個或多個NIO Channel的狀態是否處於可讀、可寫。如此可以實現單線程管理多個channels,也就是可以管理多個網絡連接。
在這裏插入圖片描述
在這裏插入圖片描述

創建Selector(Creating a Selector)
Selector selector = Selector.open();

註冊Channel到Selector上(Registering Channels with the Selector)
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
register的第二個參數,這個參數是一個“關注集合”,代表關注的channel狀態,
有四種基礎類型可供監聽, 用SelectionKey中的常量表示如下:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

從Selector中選擇channel(Selecting Channels via a Selector)
一旦向Selector註冊了一個或多個channel後,就可以調用select來獲取channel
select方法會返回所有處於就緒狀態的channel
select方法具體如下:
int select()
int select(long timeout)
int selectNow()
select()方法的返回值是一個int,代表有多少channel處於就緒了。也就是自上一次select後有多少channel進入就緒。

selectedKeys()
在調用select並返回了有channel就緒之後,可以通過選中的key集合來獲取channel,這個操作通過調用selectedKeys()方法:
Set<SelectionKey> selectedKeys = selector.selectedKeys();    
Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

案例

客戶端

package com.demo.nio.demo;

public class NIOEchoClient {

    public static void main(String[] args) {

        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
            }
        }
        new Thread(new NIOEchoClientHandler("127.0.0.1", port), "NIOEchoClient-001").start();
    }
}
package com.demo.nio.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NIOEchoClientHandler implements Runnable {

    private String host;
    private int port;

    private Selector selector;
    private SocketChannel socketChannel;

    private ExecutorService executorService;

    private volatile boolean stop;

    public NIOEchoClientHandler(String host, int port) {
        this.host = host == null ? "127.0.0.1" : host;
        this.port = port;
        this.executorService = Executors.newSingleThreadExecutor();

        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    @Override
    public void run() {
        try {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            socketChannel.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stop) {
            try {
                selector.select(1000);
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null)
                                key.channel().close();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }

        // 多路複用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動去註冊並關閉,所以不需要重複釋放資源
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (executorService != null) {
            executorService.shutdown();
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            // 判斷是否連接成功
            SocketChannel sc = (SocketChannel) key.channel();
            if (key.isConnectable()) {
                if (sc.finishConnect()) {
                    System.out.println("連接到服務器......");

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    System.out.println("請輸入消息[輸入\"Quit\"]退出:");

                    executorService.submit(() -> {
                        while (true) {
                            try {
                                buffer.clear();
                                InputStreamReader input = new InputStreamReader(System.in);
                                BufferedReader br = new BufferedReader(input);

                                String msg = br.readLine();

                                if (msg.equals("Quit")) {
                                    System.out.println("關閉客戶端......");
                                    key.cancel();
                                    sc.close();
                                    this.stop = true;
                                    break;
                                }

                                buffer.put(msg.getBytes());
                                buffer.flip();

                                sc.write(buffer);

                                System.out.println("請輸入消息[輸入\"Quit\"]退出:");

                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }
                        }
                    });
                    sc.register(selector, SelectionKey.OP_READ);
                } else {
                    // 連接失敗,進程退出
                    System.exit(1);
                }
            }

            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "UTF-8");
                    System.out.println(body);

                    if (body.equals("Quit")) {
                        this.stop = true;
                    }
                } else if (readBytes < 0) {
                    // 對端鏈路關閉
                    key.cancel();
                    sc.close();
                }
            }
            if (key.isWritable()) {
                System.out.println("The key is writable");
            }
        }
    }
}

服務端

package com.demo.nio.demo;

import java.io.IOException;


public class NIOEchoServer {


    public static void main(String[] args) throws IOException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                // 採用默認值
            }
        }
        EchoHandler timeServer = new EchoHandler(port);
        new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
    }
}
package com.demo.nio.demo;

import java.io.IOException;
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 EchoHandler implements Runnable {

	private Selector selector;
	private ServerSocketChannel servChannel;
	private volatile boolean stop;
	private int num = 0;

	public EchoHandler(int port) {
		try {
			selector = Selector.open();
			servChannel = ServerSocketChannel.open();
			servChannel.configureBlocking(false);
			servChannel.socket().bind(new InetSocketAddress(port), 1024);
			servChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("服務器在端口[" + port + "]等待客戶請求......");
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	public void stop() {
		this.stop = true;
	}

	@Override
	public void run() {
		while (!stop) {
			try {
				selector.select(1000);
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> it = selectedKeys.iterator();
				SelectionKey key = null;
				while (it.hasNext()) {
					key = it.next();
					it.remove();
					try {
						handleInput(key);
					} catch (Exception e) {
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}

		// 多路複用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動去註冊並關閉,所以不需要重複釋放資源
		if (selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void handleInput(SelectionKey key) throws IOException {

		if (key.isValid()) {
			// 處理新接入的請求消息
			if (key.isAcceptable()) {
				ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
				// Non blocking, never null
				SocketChannel socketChannel = ssc.accept();
				socketChannel.configureBlocking(false);
				SelectionKey sk = socketChannel.register(selector, SelectionKey.OP_READ);

				sk.attach(num++);
			}
			if (key.isReadable()) {
				// 讀取數據
				SocketChannel sc = (SocketChannel) key.channel();
				ByteBuffer readBuffer = ByteBuffer.allocate(1024);
				int readBytes = sc.read(readBuffer);
				if (readBytes > 0) {
					readBuffer.flip();
					byte[] bytes = new byte[readBuffer.remaining()];
					readBuffer.get(bytes);
					String body = new String(bytes, "UTF-8");
					System.out.println("來自客戶端[" + key.attachment() + "]的輸入: [" + body.trim() + "]!");

					if (body.trim().equals("Quit")) {
						System.out.println("關閉與客戶端[" + key.attachment() + "]......");
						key.cancel();
						sc.close();
					} else {
						String response = "來自服務器端的響應:" + body;
						doWrite(sc, response);
					}

				} else if (readBytes < 0) {
					// 對端鏈路關閉
					key.cancel();
					sc.close();
				} else {

				}
			}
		}
	}

	private void doWrite(SocketChannel channel, String response) throws IOException {
		if (response != null && response.trim().length() > 0) {
			byte[] bytes = response.getBytes();
			ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
			writeBuffer.put(bytes);
			writeBuffer.flip();
			channel.write(writeBuffer);
		}
	}
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

NIO的優點

  • 事件驅動模型
  • 單線程處理多任務,避免多線程
  • 非阻塞IO,IO讀寫不再阻塞,而是返回0
  • 基於block的傳輸,通常比基於流的傳輸更高效
  • 更高級的IO函數,zero-copy
  • IO多路複用大大提高了java網絡應用的可伸縮性和實用性

NIO的缺點

  • NIO不一定更快的場景
    客戶端應用
    連接數<1000
    併發程度不高
    局域網環境下
  • NIO仍然是基於各個OS平臺的IO系統實現的,差異仍然存在
  • 離散的事件驅動模型,編程困難
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章