探索Netty所提供的不同類型的傳輸,以及如何選擇一個最適合你的應用程序的傳輸。
本章主要內容
OIO 阻塞傳輸
NIO 異步傳輸
Local jvm內部的通信機制
Embedded 測試你的Channelhandler
寫一個案例:
java 寫一個應用程序簡單地接收連接,向客戶端寫Hi,然後關閉連接。
我們分爲java的阻塞IO(OIO),NIO,netty的OIO和NIO分別來實現。
java NIO版本
package com.moyang.netty.transport;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.Buffer;
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 NIOSocketDemo {
public void server(int port) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
serverSocket.bind(address);
// 打開Selector來處理Channel
Selector selector = Selector.open();
// 將ServerSocket註冊到Selector以接受連接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer msg = ByteBuffer.wrap("hi!\r\n".getBytes());
for (; ; ) {
try {
selector.select();
} catch (IOException e) {
e.printStackTrace();
break;
}
// 獲取所有接受時間的selectionKey實例
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ, msg.duplicate());
System.out.println("accept connection from " + client);
}
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
if (buffer.hasRemaining()) {
if (client.write(buffer) == 0) {
break;
}
}
client.close();
}
}catch (Exception e){
key.cancel();
key.channel().close();
}
}
}
}
public static void main(String[] args) {
try {
new NIOSocketDemo().server(8000);
} catch (IOException e) {
e.printStackTrace();
}
}
}
netty OIO版本
package com.moyang.netty.transport;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.oio.OioServerSocketChannel;
import java.nio.charset.Charset;
/**
* 使用netty實現的阻塞版網絡處理
*/
public class NettyOIODemo {
public void server(int port) {
final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi\r\n", Charset.defaultCharset().forName("UTF-8")));
EventLoopGroup group = new OioEventLoopGroup();
try {
// 創建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(OioServerSocketChannel.class)
.localAddress(port)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { // 添加一個ChannelInboundHandler來攔截和處理事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buf.duplicate())
//將消息重寫到客戶端,並添加ChannelFutureListener,以便消息一被寫完就關閉連接。
.addListener(ChannelFutureListener.CLOSE);
}
});
}
});
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
} finally {
try {
group.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new NettyOIODemo().server(8080);
}
}
netty NIO版本
package com.moyang.netty.transport;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.nio.charset.Charset;
public class NettyNIODemo {
public void server(int port) {
final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi\r\n",
Charset.forName("UTF-8")));
// 注意:使用了不同EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
// 創建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
// 使用NioServerSocketChannel,非阻塞模式
// 注意和OIO模式相比
.channel(NioServerSocketChannel.class)
.localAddress(port)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 將消息寫完關閉連接。
ctx.writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE);
}
});
}
});
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
group.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new NettyNIODemo().server(8081);
}
}
以上就是簡單的對Netty的使用。接下來詳細的學習一下Netty的API
4.2 傳輸API
傳輸API的核心是Channel接口。public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable
每個Channel都會被分配一個ChannelPipeline和ChannelConfig。ChannelConfig包含了該Channel的所有配置,並且支持熱更新。可以接收一個ChannelConfig的子類。
Channel實現Comparable接口,如果兩個不同的Channel實例返回了相同的散列碼就會報錯。
ChannelPipeline持有將所有的入站和出站的數據及事件的Channel實例。這些Channel實例實現了應用處理狀態邊和數據處理的邏輯。
ChannelHandler的典型用途:
- 將數據從一種格式轉換爲另一中格式。
- 提供異常的通知。
- 提供Channel變爲活動的或者非活動的通知。
- 提供當Channel註冊到EventLoop或者EventLoop註銷時的通知
- 提供有關用戶自定義事件的通知。
Netty的Channel實現是線程安全的.
4.3 內置的傳輸
netty內置了一些開箱急用的傳輸。但是並不是他們所有的傳輸都支持每一種協議。所以你必須選擇一個和你的應用程序所有的協議相容的傳輸。
表格: netty所提供的傳輸
名稱 | 包 | 描述 |
---|---|---|
NIO | io.netty.channel.socket.nio | 使用java.nio.channels包作爲基礎,基於選擇器的方式 |
Epoll | io.netty.channel.epoll | 由JNI驅動的epoll()和非阻塞IO。這個傳輸支持只有在Liunx上可用的多種特性,如SO_REUSEOIRT比NIO傳輸更快,而且完全是非阻塞的。 |
OIO | io.netty.channel.socket.oio | 使用java.net包作爲基礎–使用阻塞流 |
Local | io.netty.channel.local | 可以在VM內部通過管道進行通信的本地傳輸 |
Embedded | io.netty.channel.embedded | Embedded傳輸,允許使用Channelhandler而用不需要一個真正的基於網網絡的傳輸,在測試你的ChannelHandler實現時非常有用 |
4.3.1 NIO – 非阻塞IO
NIO提供了一個所有IO操作的全異步的實現,是基於選擇器的API。
選擇器的基本概念是充當一個註冊表,在請求在Channel的狀態發生變化的時候得到通知。可能變化有:
- 新的Channel已被接受並且就緒。
- Channel連接已經完成
- Channel有已經就緒的可供讀取的數據。
- Channel可用於寫數據。
選擇器運行一個檢查狀態變化並對其做出響應的線程上,在應用程序對狀態的改變作出響應之後,選擇器將會被重置,並重復這個過程。
4.3.2 Epoll–用於Liunx的本地非阻塞傳輸
性能要比NIO高。
使用的方式,替換爲EpollServerSocketChannel.class即可。
4.3.3 OIO – 舊的阻塞IO
Netty利用了SO_TIMEOUT這個Socket標誌,它指定了等待一個I/O操作完成的最大毫秒數。如果操作在指定的時間間隔內沒有完成,則將會拋出一個SocketTimeout Exception。Netty將捕獲這個異常並繼續處理循環。在EventLoop下一次運行時,它將再次嘗試。這實際上也是類似於Netty這樣的異步框架能夠支持OIO的唯一方式。
4.3.4 用於JVM內容通信的Local傳輸
在這個傳輸中,和服務器Channel 相關聯的SocketAddress 並沒有綁定物理網絡地址;相反,只要服務器還在運行,它就會被存儲在註冊表裏,並在Channel 關閉時註銷。因爲這個傳輸並不接受真正的網絡流量,所以它並不能夠和其他傳輸實現進行互操作。因此,客戶端希望連接到(在同一個JVM 中)使用了這個傳輸的服務器端時也必須使用它。除了這個限制,它的使用方式和其他的傳輸一模一樣。
4.3.5 Embedded傳輸
Netty提供了一種額外的傳輸,使得你可以將一組CHannelHandler作爲幫助器類嵌入到其他的ChannelHandler內部、通過這種方式,你將可以擴展一個ChannelHandler的功能,而又不需要修改內部的代碼。
Embedded傳輸的關鍵是一個被稱爲EmbeddedChannel的具體Channel實現。
下一章,介紹ByteBuf和ButeBufHolder-Netty的數據容器。
最後
如果你覺得寫的還不錯,就關注下公衆號唄,關注後,有點小禮物回贈給你。
你可以獲得5000+電子書,java,springCloud,adroid,python等各種視頻教程,IT類經典書籍,各種軟件的安裝及破解教程。
希望一塊學習,一塊進步!