# netty_study **Repository Path**: Zyred/netty_study ## Basic Information - **Project Name**: netty_study - **Description**: netty-demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-04-19 - **Last Updated**: 2024-01-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Netty 4.1.67.final ### Netty 与 NIO 对比 ```text 1. nio 编码过多,bug 量多 2. NIO 需要自己解决拆包和粘包 3. epoll 模型空轮询问题导致 CPU 100%,netty 解决了该问题 4. 性能优化,ByteBuffer 优化成 ByteBuf,ThreadLocal 优化成 FastThreadLocal、内存分配优化 ``` ### Netty 与 Mina 对比 ```text 1. Mina 3.x 版本不向下兼容 2. Netty 迭代迅速,API 简洁 3. Mina 性能低与 Netty 4. Netty 使用更加简单,扩展性强 ``` ### Netty ByteBuf 扩容 容量小于 4M,每次扩容都被翻倍 容量大于 4M,每次扩容都会在原来的基础上加 4M ```java public static void main(String[] args) { ByteBuf b = ByteBufAllocator.DEFAULT.buffer(); // 小于 4M 翻倍 // 大于 4M 容量 + 4M for (int i = 0; i < 12582913; i++) { b.writeByte((byte) i); } // 12M -> 16M // 12582913 -> 16777216 System.out.println(b.capacity()); } ``` #### Netty ByteBuf API ### Netty 对拆包和粘包的解决方案 ```text 1. 固定长度的解码器 FixedLengthFarameDecoder 2. 发送消息加上换行符 \n LineBasedFrameDecoder 3. 自定义分隔符解码器 DelimiterBasedFrameDecoder 4. 长度阈解码器 LengthFieldBasedFrameDecoder ``` #### LengthDieldBasedFrameDecoder 长度阈解码器 > new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 0); > > ```text > maxFrameLength: 最大的报文长度,如果超过了,则丢弃 > lengthFieldOffset:长度字段的偏移量,多少个字符串后面是长度字段 > lengthFieldLength:消息报文的长度字段占用的字节数 > lengthAdjustment:需要调整的长度数,最后的长度的值相加为你要得到的值的长度,调整的字节+长度=length > initialBytesToStrip:跳过多少字节读取内容,跳过定义的长度4位读取真正的内容 > ``` #### LengthFieldPrepender 长度编码器 > new LengthFieldPrepender(4, 0, false); > > ```text > byteOrder: 采用大端序列还是小端(应用层采用大端,底层采用小端) > lengthFieldLength:表示报文的长度字段有 4 个字节标识,只能是 1,2,3,4,8 > lengthAdjustment:需要调整的长度数,最后的长度的值相加为你要得到的值的长度 > lengthIncludesLengthFieldLength:true(length字段的值等于字段的长度加内容的长度),false(length字段的值等于内容的长度) > ``` ### Netty 自定义消息编、解码 | 总长度 | 头长度 | 头 | 消息体 | | ------ | ----------- | ------ | ------ | | LENGTH | HEAD_LENGTH | HEADER | BODY | #### 编码器 ```java public class NettyMessageEncode extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, MessageRecord msg, ByteBuf out) throws Exception { byte[] header = serializer(msg.getHeader()); byte[] body = serializer(msg.getBody()); // 坑: 该字段的长度不应该被计算到总长度中 int totalLen = CodecConstant.LEN_OF_LEN + header.length + body.length; out.writeInt(totalLen); out.writeInt(header.length); out.writeBytes(header); out.writeBytes(body); } /** * 反序列化对象 * * @param t 被反序列化的对象实体 * @param 实体类型 * @return 二进制 */ private static byte[] serializer (T t) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream); outputStream.writeObject(t); return byteArrayOutputStream.toByteArray(); } } ``` #### 解码器 ```java public class NettyMessageDecode extends LengthFieldBasedFrameDecoder { public NettyMessageDecode() { // 跳过前四个表示总长度的几个字段 super(Integer.MAX_VALUE, 0, 4, 0, 4); } @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { /* 4 4 219 90 total len | head len | head | body 317 219 {} {} 调用父类的方法返回的结果中,已经跳过了 total len, 所以 readBytes 中是不包含 total len 字段 */ ByteBuf readBytes = (ByteBuf) super.decode(ctx, in); if (readBytes == null) { System.out.println("解码器父类解析内容为空..."); return null; } MessageRecord msg = new MessageRecord(); // 317 - 4(total len) = 313 (这里面数据是: head len | head | body) int totalLength = readBytes.readableBytes(); System.out.print("总长度: " + totalLength); // head len int headLen = readBytes.readInt(); // 读取头部的数据 byte[] heads = new byte[headLen]; readBytes.readBytes(heads); Header header = deserializer(heads); header.setHeaderLength(headLen); // 313 - 4(head len of len) - 219(head len) = 90 int bodyLength = totalLength - CodecConstant.LEN_OF_LEN - headLen; byte[] body = new byte[bodyLength]; readBytes.readBytes(body); User user = deserializer(body); msg.setHeader(header); msg.setBody(user); msg.setLength(bodyLength); return msg; } @SuppressWarnings("all") private static T deserializer (byte[] data) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (T) objectInputStream.readObject(); } } ``` ## Netty HTTP 服务器 ### HttpObjectAggregator 类 > HttpRequestDecoder 最少分两次往下传递 head 和 body, 使用 HttpObjectAggregator 可以对消息组装完毕后往后传递, ```java public class NettyHttpServer { private static final int PORT = 9090; public static void main(String[] args) { EventLoopGroup boss = new NioEventLoopGroup(1); EventLoopGroup worker = new NioEventLoopGroup(); ServerBootstrap sb = new ServerBootstrap(); sb.group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new HttpRequestDecoder()) // HttpRequestDecoder 最少分两次往下传递 head 和 body, // 使用 HttpObjectAggregator 可以对消息组装完毕后往后传递 .addLast(new HttpObjectAggregator(Integer.MAX_VALUE)) .addLast(new HttpResponseEncoder()) .addLast(new HttpServerHandler()); } }); try { ChannelFuture future = sb.bind(PORT).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { boss.shutdownGracefully(); worker.shutdownGracefully(); } } } ``` ```java public class HttpServerHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws IOException { StringBuilder sb = new StringBuilder(); Map params = RequestParamParser.parseParams(msg); params.forEach((key, value) -> { sb.append("

"); sb.append(key).append(" : ").append(value); sb.append("

"); }); DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=utf-8"); response.content().writeBytes(Unpooled.copiedBuffer(sb, StandardCharsets.UTF_8)); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } } ``` ```java public class RequestParamParser { public static Map parseParams (FullHttpRequest request) throws IOException { Map params = new HashMap<>(17); String methodName = request.method().name(); // Get 请求 if (HttpMethod.GET.name().equals(methodName)) { QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); decoder.parameters().forEach((key, value) -> params.put(key, value.get(0))); } else if (HttpMethod.POST.name().equals(methodName)) { HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request); decoder.offer(request); List dataList = decoder.getBodyHttpDatas(); for (InterfaceHttpData data : dataList) { Attribute attr = (Attribute)data; params.put(attr.getName(), attr.getValue()); } } return params; } } ```