netty是什么?
Netty是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户
创建一个netty服务器和一个客户端,用客户端向服务器端发送helloworld
package icu.weizhan.netty.c1;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
public class HelloServer {
public static void main(String[] args) {
//服务端启动器,负责组装netty组件
new ServerBootstrap()
//添加组件,事件循环组,包含了线程和选择器selector
.group(new NioEventLoopGroup())
//选择一个服务器的ServerSocketChannel实现
.channel(NioServerSocketChannel.class)
//boss负责处理连接worker(child)负责处理读写,决定了worker (child)能执行哪些操作(handler)
.childHandler(
//channel 代表和客户端进行数据读写的通道Initializer初始化,负责添加别的handler
new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder()); //将 ByteBuf转换为字符串 ,解码
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){ //自定义handle
//读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//打印上一步转换好的字符串
System.out.println(msg);
}
});
}
})
//监听端口
.bind(8080);
}
}
package icu.weizhan.netty.c1;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import java.net.InetSocketAddress;
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
//启动类
new Bootstrap()
//添加EventLoop
.group(new NioEventLoopGroup())
//选择客户端的channel实现
.channel(NioSocketChannel.class)
//添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
//连接建立之后被调用
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
//连接到服务器
.connect(new InetSocketAddress("localhost",8080))
.sync() //阻塞方法,知道连接建立
.channel() //代表连接对象
//向服务器发送数据
.writeAndFlush("hello world"); //发送数据
}
}
- 把channel理解为数据的通道
- 把 msg理解为流动的数据,最开始输入是ByteBuf,但经过pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf
- 把 handler理解为数据的处理工序
- 工序有多道,合在一起就是pipeline,pipeline负责发布事件(读、读取完成...)传播给每个handler,handler对自己感兴趣的事件进行处理(重写了相应事件处理方法)
- handler分Inbound和Outbound两类
- 把eventLoop理解为处理数据的工人
- 工人可以管理多个channel的 io操作,并且一旦工人负责了某个channel,就要负责到底(绑定)。工人既可以执行io操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个channel的待处理任务,任务分为普通任务、定时任务
- 工人按照 pipeline顺序,依次按照 handler的规划(代码)处理数据,可以为每道工序指定不同的工人
组件
1.EventLoop
事件循环对象
EventLoop本质是一个单线程执行器(同时维护了一个Selector),里面有run方法处理Channel上源源不断的io 事件。
它的继承关系比较复杂
- —条线是继承自j.u.c.ScheduledExecutorService因此包含了线程池中所有的方法
- 另—条线是继承自netty自己的OrderedEventExecutor
- 提供了boolean inEventLoop(Thread thread)方法判断一个线程是否属于此EventLoop
- 提供了parent方法来看看自己属于哪个EventLoopGroup
事件循环组(一般使用它)
EventLoopGroup是一组EventLoop,Channel一般会调用EventLoopGroup的register方法来绑定其中一个EventLoop,后续这个Channel上的 io事件都由此EventLoop来处理(保证了io事件处理时的线程安全)
- 继承自netty自己的 EventExecutorGroup
- 实现了lterable接口提供遍历EventLoop的能力。另有next方法获取集合中下一个EventLoop
package icu.weizhan.netty.c3;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
new ServerBootstrap()
//划分boss和worker
// .group(new NioEventLoopGroup())
//boss 负责ServerSocketChannel的accept事件,worker只负责SocketChannel的读和写事件
.group(new NioEventLoopGroup(),new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
};
});
}
})
.bind(8080);
}
}
package icu.weizhan.netty.c3;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import java.net.InetSocketAddress;
public class EventLoopClient {
public static void main(String[] args) throws InterruptedException {
//启动类
Channel channel = new Bootstrap()
//添加EventLoop
.group(new NioEventLoopGroup())
//选择客户端的channel实现
.channel(NioSocketChannel.class)
//添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
//连接建立之后被调用
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
//连接到服务器
.connect(new InetSocketAddress("localhost", 8080))
.sync() //阻塞方法,知道连接建立
.channel();//代表连接对象
System.out.println(channel);
System.out.println("");
}
}
如果nio执行耗时间长,需要创建新的EventLoopGroup,让它处理耗时间长的业务
package icu.weizhan.netty.c3;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
//细分2:创建一个独立的EventLoopGroup
EventLoopGroup group = new DefaultEventLoopGroup();
new ServerBootstrap()
//划分boss和worker
// .group(new NioEventLoopGroup())
//boss 负责ServerSocketChannel的accept事件,worker只负责SocketChannel的读和写事件
.group(new NioEventLoopGroup(),new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("handle1", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
ctx.fireChannelRead(msg); //让消息传递到下一个handle
};
}).addLast(group, "handle2", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
};
});
}
})
.bind(8080);
}
}
Channelchannel的主要作用
- close()可以用来关闭channel
- closeFuture()用来处理channel的关闭
- sync方法作用是同步等待channel关闭
- 而addListener方法是异步等待channel关闭
- pipeline()方法添加处理器
- write()方法将数据写入 (他会把数据放到缓冲区,但还没有发送,需要执行flush或者缓冲区达到一定量才会发送)
- writeAndFlush()方法将数据写入并刷出
channelFuture.sync()
package icu.weizhan.netty.c3;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
@Slf4j
public class EventLoopClient {
public static void main(String[] args) throws InterruptedException {
//启动类
//带有future,promise的类型都是和异步方法配套使用,用来处理结果
ChannelFuture channelFuture = new Bootstrap()
//添加EventLoop
.group(new NioEventLoopGroup())
//选择客户端的channel实现
.channel(NioSocketChannel.class)
//添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
//连接建立之后被调用
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
//连接到服务器
//异步非阻塞,main发起了调用,真正执行connect的是nio线程,如果没有channelFuture.sync()阻塞方法,主线程会不等连接建立直接往下执行,获得的channel是还没连接的channel
.connect(new InetSocketAddress("localhost", 8080));
//1.使用sync方法同步结果
// channelFuture.sync(); //阻塞方法,知道连接建立
// Channel channel = channelFuture.channel();//代表连接对象 ,主线程拿到结果
// channel.writeAndFlush("hello world");
//2。使用addListener(毁掉对象) 方法异步处理结果
channelFuture.addListener(new ChannelFutureListener() {
//在nio线程建立好之后会调用operationComplete
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Channel channel = future.channel();
log.debug("{}",channel);
channel.writeAndFlush("hello world");
}
});
}
}
ChannelFuture关闭问题
package icu.weizhan.netty.c3;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.util.Scanner;
@Slf4j
public class CloseFutureClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
ChannelFuture channelFuture = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
nioSocketChannel.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8080));
Channel channel = channelFuture.sync().channel();
log.debug("{}", channel);
new Thread(() -> {
Scanner scanner = new Scanner(System.in);
while (true){
String line = scanner.nextLine();
if ("q".equals(line)){
channel.close(); //异步操作
// log.debug("处理关闭之后的操作"); //不行c,hannel.close()异步,还没有关闭就执行了
break;
}
channel.writeAndFlush(line);
}
},"input").start();
// log.debug("处理关闭之后的操作"); //不行线程异步
//获取CloseFuture对象, 1)同步处理关闭 2)异步处理关闭
ChannelFuture closeFuture = channel.closeFuture();
// System.out.println("waiting close....");
// closeFuture.sync();
// log.debug("处理关闭之后的操作");
closeFuture.addListener((ChannelFutureListener) future -> {
log.debug("处理关闭之后的操作");
group.shutdownGracefully(); //关闭NioEventLoopGroup
});
}
}
3.3 Future & Promise
在异步处理时,经常用到这两个接口
首先要说明 netty中的Future 与jdk中的Future同名,但是是两个接口,netty的Future继承自jdk的Future,而Promise 又对 netty Future进行了扩展
- jdk Future只能同步等待任务结束(或成功、或失败)才能得到结果
- netty Future可以同步等待任务结束得到结果,也可以异步方式得到结果,但都是要等任务结束
- netty Promise不仅有netty Future的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器
JdkFuture
package icu.weizhan.netty.c3;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j
public class TestJdkFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//2、提交任务
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("执行计算");
Thread.sleep(1000);
return 50;
}
});
//3.主线程通过future来获取结果
log.debug("等待结果");
log.debug("结果是:{}",future.get());
// future.get(); //阻塞方法
}
}
nettyFuture
package icu.weizhan.netty.c3;
import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@Slf4j
public class TestNettyFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup(); //有多个EventLoop,一个EventLoop只有一个线程
EventLoop next = group.next(); //拿到一个EventLoop
Future<Integer> future = next.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("执行计算");
Thread.sleep(1000);
return 100;
}
});
// log.debug("等待结果");
// log.debug("结果是:{}",future.get());
//异步方法
future.addListener(new GenericFutureListener<Future<? super Integer>>() {
@Override
public void operationComplete(Future<? super Integer> future) throws Exception {
log.debug("接收结果:{}",future.getNow()); //future.getNow()非阻塞,这里已经回调了,肯定拿到结果了,没必要阻塞
}
});
}
}
NettyPromise
package icu.weizhan.netty.c3;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.DefaultPromise;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
@Slf4j
public class TestNettyPromise {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.准备EventLoop对象
EventLoop eventLoop = new NioEventLoopGroup().next();
//2.可以主动创建promise,结果容器
DefaultPromise<Integer> promise = new DefaultPromise<>(eventLoop);
new Thread(() -> {
//3.任意一个线程执行计算,计算完毕后向promise填充结果
log.debug("开始计算。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
promise.setFailure(e);
}
promise.setSuccess(80);
}).start();
//4.接收结果线程
log.debug("等待结果。。。");
log.debug("结果是:{}",promise.get());
}
}
Handler & Pipeline
ChannelHandler用来处理Channel上的各种事件,分为入站、出站两种。所有ChannelHandler 被生成一串,就是Pipeline
- 入站处理器通常是ChannellnboundHandlerAdapter的子类,主要用来读取客户端数据,写回结果·
- 出站处理器通常是ChannelOutboundHandlerAdapter的子类,主要对写回结果进行加工
打个比喻,每个Channel是一个产品的加工车间,Pipeline是车间中的流水线,ChannelHandler就是流水线上的各道工序,而后面要讲的ByteBuf是原材料,经过很多工序的加工;先经过一道道入站工序,再经过一道道出站工序最终变成产品
pipeline
package icu.weizhan.netty.c3;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
@Slf4j
public class TestPipeline {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
//1.通过channel拿到pipeline对象
ChannelPipeline pipeline = channel.pipeline();
//添加处理器 head -h1 -h2 -h3 -tail
pipeline.addLast("h1",new ChannelInboundHandlerAdapter(){
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
log.debug("1");
super.channelReadComplete(ctx);
}
});
pipeline.addLast("h2",new ChannelInboundHandlerAdapter(){
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
log.debug("2");
super.channelReadComplete(ctx);
}
});
pipeline.addLast("h3",new ChannelInboundHandlerAdapter(){
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
log.debug("3");
channel.write(ctx.alloc().buffer().writeBytes("service...".getBytes()));
super.channelReadComplete(ctx);
}
});
pipeline.addLast("h4",new ChannelOutboundHandlerAdapter(){
//写出时触发write
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.debug("4");
super.write(ctx, msg, promise);
}
});
pipeline.addLast("h5",new ChannelOutboundHandlerAdapter(){
//写出时触发write
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.debug("5");
super.write(ctx, msg, promise);
}
});
}
})
.bind(8080);
}
}
执行顺序:h1->h2->h3->h5->h4
embedded调试工具,测试channel(不用启动服务端和客户端)
package icu.weizhan.netty.c4;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.embedded.EmbeddedChannel;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TestEmbeddedChannel {
public static void main(String[] args) {
ChannelInboundHandlerAdapter h1 = new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("1");
super.channelRead(ctx, msg);
}
};
ChannelInboundHandlerAdapter h2 = new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("2");
super.channelRead(ctx, msg);
}
};
ChannelOutboundHandlerAdapter h3 = new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.debug("3");
super.write(ctx, msg, promise);
}
};
ChannelOutboundHandlerAdapter h4 = new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.debug("4");
super.write(ctx, msg, promise);
}
};
EmbeddedChannel channel = new EmbeddedChannel(h1, h2, h3, h4);
//模拟入站操作
channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello".getBytes()));
//模拟出站操作
channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("world".getBytes()));
}
}
ByteBuf
是对ByteBuffer的增强
package icu.weizhan.netty.c4;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;
public class TestByteBuf {
public static void main(String[] args) {
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();//不指定容量默认256,容量可以动态扩容
log(buffer);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 300; i++) {
sb.append("a");
}
ByteBuf buf = buffer.writeBytes(sb.toString().getBytes());
log(buf);
//PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
//PooledUnsafeDirectByteBuf(ridx: 0, widx: 300, cap: 512)
}
private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) +4;
StringBuilder buf = new StringBuilder(rows * 80* 2)
.append( "read index:" ).append(buffer.readerIndex())
.append( " write index: " ).append(buffer.writerIndex())
.append(" capacity: ").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}
2)直接内存(读写效率高,分配效率较低)vs堆内存(分配效率高,读写效率较低)
可以使用下面的代码来创建池化基于堆的 ByteBuf
Bytebuf buffer = ByteBufA1locator.DEFAULT.heapBuffer(10);
也可以使用下面的代码来创建池化基于直接内存的 ByteBuf
ByteBuf buffer = ByteBufA11ocator.DEFAULT.directBuffer(10);
- 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
- 直接内存对GC压力小,因为这部分内存不受JVM垃圾回收的管理,但也要注意及时主动释放
3)池化vs非池化
池化的最大意义在于可以重用ByteBuf,优点有
- 没有池化,则每次都得创建新的ByteBuf实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加GC压力
- 有了池化,则可以重用池中ByteBuf 实例,并且采用了与jemalloc类似的内存分配算法提升分配效率
- 高并发时,池化功能更节约内存,减少内存溢出的可能
池化功能是否开启,可以通过下面的系统环境变量来设置
-Dio.netty.allocator .type={unpooledl poo1ed}
- 4.1以后,非 Android平台默认启用池化实现,Android平台启用非池化实现
- 4.1之前,池化功能还不成熟,默认是非池化实现
扩容规则:
如何写入后数据大小未超过512,则选择下一个16的整数倍,例如写入后大小为12,则扩容后capacity是16
如果写入后数据大小超过512,则选择下一个2^n,例如写入后大小为513,则扩容后capacity是2^10=1024 (2^9=512已经不够了)
·扩容不能超过 max capacity会报错
slice
【零拷贝】的体现之一,对原始ByteBuf进行切片成多个ByteBuf,切片后的ByteBuf并没有发生内存复制,还是使用原始ByteBuf 的内存,切片后的 ByteBuf维护独立的read,write指针
package icu.weizhan.netty.c5;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import static icu.weizhan.netty.c4.TestByteBuf.log;
public class TestSlice {
public static void main(String[] args) {
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
buffer.writeBytes(new byte[]{'a','b','c','d','e','f','g','h','i','j'});
log(buffer);
//在切片过程中,没有发生数据复制(注意:最大容量为他的切割长度,不能扩容)
ByteBuf f1 = buffer.slice(0, 5);
ByteBuf f2 = buffer.slice(5, 5);
log(f1);
log(f2);
}
}
duplicate
【零拷贝】的体现之一,就好比截取了原始ByteBuf 所有内容,并且没有max capacity的限制,也是与原始ByteBuf 使用同一块底层内存,只是读写指针是独立的
copy
会将底层内存数据进行深拷贝,因此无论读写,都与原始 ByteBuf无关
compositeBuffer()
把两个小的ByteBuf组合成一个大的ByteBuf
package icu.weizhan.netty.c5;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import static icu.weizhan.netty.c4.TestByteBuf.log;
public class TestCompositeByteBuffer {
public static void main(String[] args) {
ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer();
buf1.writeBytes(new byte[]{1,2,3,4,5});
ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer();
buf2.writeBytes(new byte[]{6,7,8,9,10});
CompositeByteBuf byteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
byteBuf.addComponents(true,buf1,buf2);
log(byteBuf);
}
}
ByteBuf优势
- 池化-可以重用池中 ByteBuf实例,更节约内存,减少内存溢出的可能·读写指针分离,不需要像ByteBuffer一样切换读写模式
- 可以自动扩容
- 支持链式调用,使用更流畅
- 很多地方体现零拷贝,例如slice.duplicate.CompositeByteBuf
粘包
- 现象,发送abc def,接收abcdef
- 原因
- 应用层:接收方ByteBuf设置太大(Netty 默认1024)
- 滑动窗口:假设发送方256 bytes表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包
- Nagle算法:会造成粘包
半包
- 现象,发送abcdef,接收abc def
- 原因
- 应用层:接收方 ByteBuf 小于实际发送数据量
- 滑动窗口:假设接收方的窗口只剩了128 bytes,发送方的报文大小是256 bytes,这时放不下了,只能先发送前128 bytes,等待ack 后才能发送剩余部分,这就造成了半包
- MSS限制:当发送的数据超过MSS限制后,会将数据切分发送,就会造成半包
本质是因为 TCP是流式协议,消息无边界
模拟redis协议发送
package icu.weizhan.netty.c6;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
/*
set key value
*3 命令数组长度
$3 第一个命令长度
set 第一个命令
$4 键长度
name 键
$8 值长度
zhangsan 值长度
*/
@Slf4j
public class TestRedis {
public static void main(String[] args) {
final byte[] LINE = {13,10};
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(worker);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { //连接建立时触发
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes("*3".getBytes());
buffer.writeBytes(LINE);
buffer.writeBytes("$3".getBytes());
buffer.writeBytes(LINE);
buffer.writeBytes("set".getBytes());
buffer.writeBytes(LINE);
buffer.writeBytes("$4".getBytes());
buffer.writeBytes(LINE);
buffer.writeBytes("name".getBytes());
buffer.writeBytes(LINE);
buffer.writeBytes("$8".getBytes());
buffer.writeBytes(LINE);
buffer.writeBytes("zhangsan".getBytes());
buffer.writeBytes(LINE);
ctx.writeAndFlush(buffer);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接收
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(byteBuf.toString(Charset.defaultCharset()));
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 6379).sync();
channelFuture.channel().closeFuture().sync();
}catch (InterruptedException e){
log.error("client error", e);
}finally {
worker.shutdownGracefully();
}
}
}
http编解码
package icu.weizhan.netty.c6;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
@Slf4j
public class TestHttp {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new HttpServerCodec()); //http编解码器
ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {//只关心某类的请求
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) throws Exception {
//获取请求
log.debug(httpRequest.uri());
//返回响应
DefaultFullHttpResponse response = new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);
byte[] bytes = "<h1>hello, world</h1>".getBytes();
response.headers().setInt(CONTENT_LENGTH,bytes.length);
response.content().writeBytes(bytes);
channelHandlerContext.writeAndFlush(response);
}
});
// ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// log.debug("{}",msg.getClass());
// if (msg instanceof HttpRequest){ //处理请求头,请求行
//
// }else if (msg instanceof HttpContent){ //处理请求体
//
// }
// }
// });
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
}catch (InterruptedException e){
log.error("client error", e);
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
自定义协议要素
- 魔数,用来在第一时间判定是否是无效数据包·版本号,可以支持协议的升级
- 序列化算法,消息正文到底采用哪种序列化反序列化方式,可以由此扩展,例如: json.protobuf.hessian、jdk
- 指令类型,是登录、注册、单聊、群聊...跟业务相关·请求序号,为了双工通信,提供异步能力
- 正文长度
- 消息正文