Netty笔记

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
  • 指令类型,是登录、注册、单聊、群聊...跟业务相关·请求序号,为了双工通信,提供异步能力
  • 正文长度
  • 消息正文
     
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值