netty入門以及主要組件介紹
NIO
NIO優點
- 事件驅動模型
- 避免多線程
- 單線程處理多任務
- 非阻塞IO,IO讀寫不再阻塞,而是返回0
- 基於block的傳輸,通常比基於流的傳輸更加高效
- 更高級的IO函數,zero-copy
- IO多路複用大大提高了java網絡應用的可伸縮性和實用性
netty
有了NIO,爲什麼還要學習netty
- 併發量大
Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網絡通信框架,對比於BIO(Blocking I/O,阻塞IO),他的併發性能得到了很大提高 - 傳輸快
Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。我們知道,Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是佔用內存空間最大的一塊,也是Java對象存放的地方,一般我們的數據如果需要從IO讀取到堆內存,中間需要經過Socket緩衝區,也就是說一個數據會被拷貝兩次才能到達他的的終點,如果數據量大,就會造成不必要的資源浪費。
Netty針對這種情況,使用了NIO中的另一大特性——零拷貝,當他需要接收數據的時候,他會在堆內存之外開闢一塊內存,數據就直接從IO讀到了那塊內存中去,在netty裏面通過ByteBuf可以直接對這些數據進行直接操作,從而加快了傳輸速度。 - 對NIO做了進一步的封裝
簡化了編程。
netty組件調用關係圖
netty主要組件介紹
NIO編程回顧
Netty主要組件
- 1.Transport Channel ---- 對應NIO中的channel
- 2.EventLoop ---- 對應於NIO中的while循環
- EventLoopGroup: 多個EventLoop
- 3.ChannelHandler和ChannelPipeline — 對應於NIO中的客戶邏輯實現handleRead/handleWrite (interceptor pattern)
- 4.ByteBuf ---- 對應於NIO 中的ByteBuffer
- 5.Bootstrap 和 ServerBootstrap — 對應NIO中的Selector、ServerSocketChannel等的創建、配置、啓動等
Netty Server啓動主要流程
- 設置服務端ServerBootStrap啓動參數
- group(parentGroup, childGroup):
- channel(NioServerSocketChannel): 設置通道類型
- handler():設置NioServerSocketChannel的ChannelHandlerPipeline
- childHandler(): 設置NioSocketChannel的ChannelHandlerPipeline
- 通過ServerBootStrap的bind方法啓動服務端,bind方法會在parentGroup中註冊NioServerScoketChannel,監聽客戶端的連接請求
- 會創建一個NioServerSocketChannel實例,並將其在parentGroup中進行註冊
Netty Server執行主要流程
- Client發起連接CONNECT請求,parentGroup中的NioEventLoop不斷輪循是否有新的客戶端請求,如果有,ACCEPT事件觸發。
- ACCEPT事件觸發後,parentGroup中NioEventLoop會通過NioServerSocketChannel獲取到對應的代表客戶端的NioSocketChannel,並將其註冊到childGroup中。
- childGroup中的NioEventLoop不斷檢測自己管理的NioSocketChannel是否有讀寫事件準備好,如果有的話,調用對應的ChannelHandler進行處理。
Netty Transport-傳輸層
- 提供了統一的API,支持不同類型的傳輸層
- OIO-阻塞IO
- NIO-Java nio
- Epoll - Linux Epoll(JNI)
- Local Transport - IntraVM調用
- Embedded Transport - 供測試使用的嵌入傳輸
- UDS - Unix套接字的本地傳輸
Netty EventLoop——對應於NIO中的while循環
- ServerBootstrap:包括2個不同類型的EventLoopGroup:
- Parent EventLoop:負責處理Accept事件,接收請求
- Child EventLoop:負責處理讀寫事件
- EventLoopGroup
- 包括多個EventLoop
- 多個EventLoop之間不交互
- EventLoop:
- 每個EventLoop對應一個線程
- 所有連接(channel)都將註冊到一個EventLoop,並且只註冊到一個,整個生命週期中都不會變化
- 每個EventLoop管理着多個連接(channel)
- EventLoop來處理連接(Channel)上的讀寫事件
Netty Buffers——ByteBuf
- 相比JDK ByteBuffer,更加的易於使用
- 爲讀寫分別維護單獨的指針,不需要通過flip()進行讀寫模式切換
- 容量自動伸縮(類似ArrayList,StringBuilder)
- Fluent API(鏈式調用)
- 更好地性能
- 通過內置的CompositeBuffer來減少數據拷貝(Zero copy)
- 支持內存池,減少GC壓力
ByteBuf分配
- 不直接通過new來創建,而是通過ByteBufAllocator來創建
- Unpooled的工具類,它提供了靜態的輔助方法來創建未池化的ByteBuf實例
Netty ChannelHandler——對應於NIO中的客戶邏輯實現handleRead/handleWrite (interceptor pattern)
ChannelHandler——業務處理核心邏輯,用戶自定義
ChannelHandler介紹
Netty 提供2個重要的 ChannelHandler 子接口:
ChannelInboundHandler - 處理進站數據和所有狀態更改事件
ChannelOutboundHandler - 處理出站數據,允許攔截各種操作
注意:我們一般繼承ChannelInboundHandlerAdapter,就可以不用實現所有的方法,只處理我們需要的方法,其餘方法採用默認實現
Netty Channel狀態及轉換
- Channel的狀態及其轉換如下圖所示:
- 當 ChannelHandler 添加到 ChannelPipeline,或者從ChannelPipeline 移除後,對應的方法將會被調用
Netty ChannelInboundHandler
ChannelInboundHandler 回調方法在下表中:
- 當接收到數據或者與之關聯的 Channel 狀態改變時調用
- 與 Channel 的生命週期接近
Netty ChannelOutboundHandler
Netty——ChannelPipeline
ChannelPipeline 是ChannelHandler容器
- 包括一系列的ChannelHandler 實例,用於攔截流經一個 Channel 的入站和出站事件
- 每個Channel都有一個其ChannelPipeline
- 可以修改 ChannelPipeline 通過動態添加和刪除 ChannelHandler
- 定義了豐富的API調用來回應入站和出站事件
Netty—— ChannelHandlerContext
ChannelHandlerContext表示 ChannelHandler 和ChannelPipeline 之間的關聯
在 ChannelHandler 添加到 ChannelPipeline 時創建
netty編程示例一
pom文件中引入
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.30.Final</version>
</dependency>
服務器端代碼:
package test13;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println(
"服務器接收到消息:" + in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
package test13;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class EchoServer {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 採用默認值
}
}
new EchoServer().bind(port);
}
public void bind(int port) throws Exception {
// 配置服務端的NIO線程組
// 主線程組, 用於接受客戶端的連接,但是不做任何具體業務處理,像老闆一樣,負責接待客戶,不具體服務客戶
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 工作線程組, 老闆線程組會把任務丟給他,讓手下線程組去做任務,服務客戶
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 類ServerBootstrap用於配置Server相關參數,並啓動Server
ServerBootstrap b = new ServerBootstrap();
//鏈式調用
//配置parentGroup和childGroup
b.group(bossGroup, workerGroup)
//配置Server通道
.channel(NioServerSocketChannel.class)
//配置通道的ChannelPipeline
.childHandler(new ChildChannelHandler());
// 綁定端口,並啓動server,同時設置啓動方式爲同步
ChannelFuture f = b.bind(port).sync();
System.out.println(EchoServer.class.getName() +
" 啓動成功,在地址[" + f.channel().localAddress() +"]上等待客戶請求......");
// 等待服務端監聽端口關閉
f.channel().closeFuture().sync();
} finally {
// 釋放線程池資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new EchoServerHandler());
}
}
}
客戶端代碼
package test13;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("這是一個Netty示例程序!\n", CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
System.out.println("客戶端接收到消息: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
package test13;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class EchoClient {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new EchoClient().connect(port, "127.0.0.1");
}
public void connect(int port, String host) throws Exception {
// 工作線程組, 老闆線程組會把任務丟給他,讓手下線程組去做任務,服務客戶
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// 發起異步連接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客戶端鏈路關閉
f.channel().closeFuture().sync();
} finally {
// 釋放線程池資源
group.shutdownGracefully();
}
}
}
運行結果如下
服務器端
客戶端:
netty編程示例二
package com.demo.netty.bytebuf;
import io.netty.buffer.*;
import io.netty.util.ByteProcessor;
import java.nio.charset.Charset;
public class ByteBufExamples {
public static void main(String[] args) {
byteBufSetGet();
System.out.println("============");
byteBufWriteRead();
System.out.println("============");
writeAndRead();
System.out.println("============");
byteBufSlice();
System.out.println("============");
byteBufCopy();
System.out.println("============");
byteBufComposite();
System.out.println("============");
directBuffer();
System.out.println("============");
heapBuffer();
System.out.println("============");
}
public static void byteBufSetGet() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
System.out.println((char)buf.getByte(0));
int readerIndex = buf.readerIndex();
int writerIndex = buf.writerIndex();
System.out.println("readerIndex = " + readerIndex + "; writerIndex = " + writerIndex);
buf.setByte(0, (byte)'B');
System.out.println((char)buf.getByte(0));
System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
}
public static void byteBufWriteRead() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
System.out.println((char)buf.readByte());
int readerIndex = buf.readerIndex();
int writerIndex = buf.writerIndex();
System.out.println("readerIndex = " + readerIndex + "; writerIndex = " + writerIndex);
buf.writeByte((byte)'?');
System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
buf.readByte();
System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
}
public static void writeAndRead() {
ByteBuf buffer = Unpooled.buffer(20); //get reference form somewhere
int i = 0;
while (buffer.writableBytes() >= 4) {
buffer.writeInt(i++);
}
while (buffer.isReadable()) {
System.out.println(buffer.readInt());
}
}
public static void byteProcessor() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buffer = Unpooled.copiedBuffer("Netty\r in Action rocks! ", utf8);
int index = buffer.forEachByte(ByteProcessor.FIND_CR);
}
public static void byteBufSlice() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf sliced = buf.slice(0, 15);
System.out.println(sliced.toString(utf8));
buf.setByte(0, (byte)'J');
System.out.println(sliced.toString(utf8));
}
public static void byteBufCopy() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf copy = buf.copy(0, 15);
System.out.println(copy.toString(utf8));
buf.setByte(0, (byte)'J');
System.out.println(copy.toString(utf8));
}
public static void byteBufComposite() {
CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
Charset utf8 = Charset.forName("UTF-8");
ByteBuf headerBuf = Unpooled.copiedBuffer("Header", utf8);
ByteBuf bodyBuf = Unpooled.copiedBuffer("This is body", utf8);
messageBuf.addComponents(headerBuf, bodyBuf);
for (ByteBuf buf : messageBuf) {
System.out.println(buf.toString());
}
messageBuf.removeComponent(0); // remove the header
for (ByteBuf buf : messageBuf) {
System.out.println(buf.toString());
}
}
public static void heapBuffer() {
ByteBuf heapBuf = Unpooled.buffer(16);
if (heapBuf.hasArray()) {
int i = 0;
while (heapBuf.writableBytes() > 0) {
heapBuf.writeByte(i++);
}
byte[] array = heapBuf.array();
int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
int length = heapBuf.readableBytes();
handleArray(array, offset, length);
}
}
public static void directBuffer() {
ByteBuf directBuf = Unpooled.directBuffer(16);
if (!directBuf.hasArray()) {
int i = 0;
while (directBuf.writableBytes() > 0) {
directBuf.writeByte(i++);
}
int length = directBuf.readableBytes();
byte[] array = new byte[length];
directBuf.getBytes(directBuf.readerIndex(), array);
handleArray(array, 0, length);
}
}
public static void byteBufCompositeArray() {
CompositeByteBuf compBuf = Unpooled.compositeBuffer();
int length = compBuf.readableBytes();
byte[] array = new byte[length];
compBuf.getBytes(compBuf.readerIndex(), array);
handleArray(array, 0, array.length);
}
private static void handleArray(byte[] array, int offset, int len) {
for(int i = 0; i<len; i++) {
System.out.println(array[offset+i]);
}
}
}