Netty簡介
Netty是一個異步、基於事件驅動的網絡應用框架
應用場景
- 分佈式系統中各節點遠程過程調用(RPC:Dubbo)
- 遊戲服務器
- Hadoop通信
NIO
三大組件
NIO三大組件:Selector、Channel、Buffer
- 一個Channel對應一個Buffer,一個Selector管理多個Channel,一個線程對應一個Selector
- 程序切換到哪個Channel由事件決定,Event
- Buffer就是一個內存塊,底層是數組。Client通過Buffer進行數據的讀寫,NIO中的Buffer是雙向的,BIO中的輸入流、輸出流不是雙向的。
- Channel也是雙向的
Buffer
抽象類Buffer中的屬性:
- mark : 標記
- position : 位置,下一次要讀寫的元素的位置。
- limit : 緩衝區的終點,不能超過緩衝區的最大位置,可以修改
- capacity :容量,緩衝區創建時指定
IntBuffer intBuffer = IntBuffer.allocate(5);
for (int i = 0; i < intBuffer.capacity(); i++) {
// 將元素插入position位置
intBuffer.put(i << 10);
}
// 讀寫翻轉
intBuffer.flip();
// 將limit指定爲3,只能獲取索引小於3的元素
intBuffer.limit(3);
System.out.println(intBuffer.capacity());
while (intBuffer.hasRemaining()){
// 獲取position位置的元素
System.out.println(intBuffer.get());
}
基本類型中除了bool,其他類型都有對應的Buffer類。
IntegerBuffer類中的重要屬性:
// 存儲數據的數組
final int[] hb; // Non-null only for heap buffers
final int offset;
// 是否只讀
boolean isReadOnly;
Channel
Channel可以同時讀寫,可以異步讀寫數據。
- FileChannel:文件讀寫
FileOutPutStream和FileInputStream中包含了FileChannel屬性,可以通過這兩個類的實例獲得Channel。
實例:
FileOutputStream outputStream = new FileOutputStream("D://hello.txt");
// 從FileOutPutStream獲取FileChannelImpl
FileChannel channel = outputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
String s = "hello,World";
// 將byte數組放入緩衝區,buffer的position等於數組長度
buffer.put(s.getBytes());
// 讀寫翻轉,limit=position,而position置0
buffer.flip();
// 寫入
channel.write(buffer);
outputStream.close();
channel.close();
FileInputStream inputStream = new FileInputStream("D://hello.txt");
// FileInputStream獲取FileChannel,實際類型是FileChannelImpl
FileChannel channel = inputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
System.out.println(read);
String s = new String(buffer.array(), 0, read,"utf-8");
System.out.println(s);
inputStream.close();
channel.close();
FileInputStream inputStream = new FileInputStream("D://hello.txt");
FileOutputStream outputStream = new FileOutputStream("D://hello2.txt");
FileChannel inChannel = inputStream.getChannel();
FileChannel outputChannel = outputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(2);
while (true){
// clear不重置的話,position=limit,則read一直等於0
// 標誌位重置,position重置爲0,limit設爲capacity
buffer.clear();
int read = inChannel.read(buffer);
if (read == -1){
break;
}
buffer.flip();
outputChannel.write(buffer);
}
inputStream.close();
outputStream.close();
// 使用transfrom拷貝文件
FileInputStream inputStream = new FileInputStream("D://a.jpg");
FileOutputStream outputStream = new FileOutputStream("D://a2.jpg");
FileChannel inChannel = inputStream.getChannel();
FileChannel outputChannel = outputStream.getChannel();
// transferFrom拷貝
outputChannel.transferFrom(inChannel,0,inChannel.size());
inputStream.close();
outputStream.close();
Buffer和Channel注意事項
-
ByteBuffer,put什麼類型,取得時候就要相應的類型去get。
-
Buffer可以設置爲只讀
// 只讀Buffer,不可寫,否則會報ReadOnlyBufferException ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
-
MappedByteBuffer可以直接在內存(堆外內存)中修改,操作系統不需要拷貝一次
// 直接在內存中修改,不用操作系統再拷貝一次 RandomAccessFile accessFile = new RandomAccessFile("D://a.txt", "rw"); FileChannel channel = accessFile.getChannel(); /** * 參數說明; * 1.FileChannel.MapMode.READ_WRITE 使用讀寫模式 * 2.直接修改的起始位置 * 3.從起始位置映射到內存的大小(不是索引),超過字節大小將不能修改 */ MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5); mappedByteBuffer.put(0,(byte) 'a'); mappedByteBuffer.put(3,(byte) '9'); accessFile.close();
-
NIO支持多個Buffer的Scatting和Gathering
/** * Scatting:將數據寫入到buffer時,可以使用buffer數組,依次寫入 * Gathering:從buffer讀取數據時,可以採用buffer數組,依次讀取 */ ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetSocketAddress socketAddress = new InetSocketAddress(7000); serverSocketChannel.socket().bind(socketAddress); // 創建buffer數組 ByteBuffer[] buffers = new ByteBuffer[2]; buffers[0] = ByteBuffer.allocate(5); buffers[1] = ByteBuffer.allocate(8); int messageLength = 10; // 等待連接 SocketChannel socketChannel = serverSocketChannel.accept(); // 循環讀取 while (true) { int byteRead = 0; while (byteRead < messageLength){ long l = socketChannel.read(buffers); byteRead += l; System.out.println("byteRead = "+byteRead); Arrays.asList(buffers).stream().map(buffer -> "position = "+buffer.position() +",limit = "+buffer.limit()).forEach(System.out::println); } Arrays.asList(buffers).forEach(byteBuffer -> byteBuffer.flip()); int byteWrite = 0; while (byteWrite < messageLength){ long l = socketChannel.write(buffers); byteWrite += l; } Arrays.asList(buffers).forEach(byteBuffer -> byteBuffer.clear()); System.out.println("byteRead = "+byteRead + ",byteWrite = "+byteWrite +",messageLength = "+messageLength);
Selector
- Channel註冊到Selector,Selector能夠檢測到Channel是否有事件發生。如果有事件發生,則進行相應的處理。這樣可以實現一個線程管理多個Channel(即多個連接和請求)
- 只有通道真正有讀寫事件發生時,纔會進行讀寫。減少了創建的線程數,降低了系統開銷
- 減少了上下文的切換,用戶態和系統態的切換
以ServerSocketChannel爲例說明:
- 當有客戶端連接時,ServerSocketChannel會返回一個SocketChannel
- SocketChannel註冊到Selector。(register方法)
- register方法會返回一個SelectionKey,SelectionKey與Channel關聯
- Selector監聽select方法,返回有事件的個數
- 進一步得到SelectionKey
- 通過SelectionKey獲取SocketChannel(SelectionKey中的channel方法)
- 通過獲取的channel,執行業務處理
代碼說明:
SeverSocketChannel端:
// 創建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 創建Selector
Selector selector = Selector.open();
// 綁定ip
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
// 設置爲不阻塞
serverSocketChannel.configureBlocking(false);
// 將ServerSocketChannel註冊到selector。指定關心的事件爲OP_ACCEPT,
// 當有關心的事件發生時,會返回這個SelectionKey,通過SelectionKey可以拿到Channel
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while (true){
// Selector監聽,等於0說明此時沒有事件發生。
if (selector.select(1000) == 0) {
System.out.println("Selector監聽了一秒");
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
if (key.isAcceptable()){
// 獲得SocketChannel,此處的accept不會阻塞
SocketChannel socketChannel = serverSocketChannel.accept();
// 此處socketChannel也要設置爲非阻塞模式
socketChannel.configureBlocking(false);
// 註冊Selector。第三個參數是連接的對象,通過SelectionKey可以連接到這個對象
socketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
}
if (key.isReadable()){
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int read = channel.read(buffer);
System.out.println("客戶端 : "+new String(buffer.array(),0,read));
}
// 手動刪除避免重複
keyIterator.remove();
}
}
SocketClient端;
SocketChannel socketChannel = SocketChannel.open();
// 設置非阻塞模式
socketChannel.configureBlocking(false);
boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1",9000));
if (!connect){
while (!socketChannel.finishConnect()){
System.out.println("因爲連接需要時間,客戶端不會阻塞,可以做一些其他工作");
}
}
ByteBuffer buffer = ByteBuffer.wrap("This is a message!".getBytes());
socketChannel.write(buffer);
System.in.read();