Netty 学习笔记(已完结)

  • Post author:
  • Post category:其他


Netty



0代码示例



A.经典IO多线程

// 获取到的inputStream是SocketInputStream,这个类不是公开的,继承了FileInputStream,

InputStream inputStream = clientSocket.getInputStream();

// 经典IO多线程
public class OioMultiThreadExample {

    public static void main(String[] args) throws IOException {
        OioMultiThreadExample example = new OioMultiThreadExample();
        example.server(8089);
    }

    public void server(int port) throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        for (;;) {
            Socket clientSocket = serverSocket.accept();
            System.out.println("Accepted connection form "+ clientSocket);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    OutputStream outputStream = null;
                    try {
                        outputStream = clientSocket.getOutputStream();
                        outputStream.write("Hi!\r\n".getBytes(StandardCharsets.UTF_8));
                        outputStream.flush();
                        clientSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    finally {
                        try {
                            clientSocket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}



B.JDK NIO经典案例

可以看见,这个代码与上面的代码,差异非常大

public class JdkNioExample {

    public static void main(String[] args) throws IOException {
        JdkNioExample jdkNioExample = new JdkNioExample();
        jdkNioExample.server(8088);
    }

    public void server(Integer port) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        serverSocket.bind(address);

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes(StandardCharsets.UTF_8));
        for (;;){
            try {
                selector.select();
            } catch (IOException e) {
                e.printStackTrace();
                // handle exception
                break;
            }

            Set<SelectionKey> readKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readKeys.iterator();
            if (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel socketClient = server.accept();
                        socketClient.configureBlocking(false);
                        socketClient.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ ,msg.duplicate());
                        System.out.println("Accept connection form "+ socketClient);
                    }
                    if (key.isWritable()) {
                        SocketChannel socketClient = (SocketChannel) key.channel();
                        ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                        while (byteBuffer.hasRemaining()) {
                            if (socketClient.read(byteBuffer) == 0) {
                                break;
                            }
                        }
                        socketClient.close();
                    }
                } catch (IOException e) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException ioException) {
                        // ignore on close
                    }
                }
            }
        }
    }
}



5.netty执行流程分析与重要组件介绍



应用

  • rpc通信框架,通信协议(基于Socket,应用场景广泛)
  • 长连接服务器(web socket服务器,便于服务端消息推送)
  • http服务器(jetty,tomcat类似的servlet容器,不过其编程模型并不是基于servlet规范的,其参数获取,参数封装是有差异的,其并没有实现servlet规范,servlet接口)




特点

netty涉及到了很多对线程的处理,对线程的控制,还有对IO异步的操作,异步的调度



http 服务器的应用


核心概念

channel,通道,类似于一个连接一样,可以打开和关闭

channelHandler,通道处理器,类似于过滤器拦截器

pipeline,管道,可以理解为一个pipeline是由多个channelHandler构成的,请求沿着管道往前走


创建步骤

注意:无论是基于何种编程行为,下面的总的步骤都是一样的。

  • 创建两个事件循环组(一个也可以完成工作,但是通常都是定义两个)

    EventLoopGroup bossGroup = new NioEventLoopGroup(); 
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    

    实现类是NioEventLoopGroup,类似于一个死循环,类似于tomcat,里面也有一个死循环

    bossGroup接收连接,但是不对连接做任何处理

    workerGroup对连接进行处理,获取连接的参数,处理业务,然后返回给客户端

  • 创建ServerBootStrap,关联两个事件循环组,以及设置NioServerSocketChannel

    ServerBootstrap b = new ServerBootstrap(); // (2)
                b.group(bossGroup, workerGroup)
                 .channel(NioServerSocketChannel.class) // (3)
                 .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                     @Override
                     public void initChannel(SocketChannel ch) throws Exception {
                         ch.pipeline().addLast(new DiscardServerHandler());
                     }
                 })
                 .option(ChannelOption.SO_BACKLOG, 128)          // (5)
                 .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
    

    它封装了服务的启动,让我们很轻松的启动服务

    使用netty的方法链的编程风格

    设置NioServerSocketChannel后台是通过反射实现的

    注意:accpeter是netty nio中一个很重要的概念

  • 创建子处理器,实现channelInitializer

    @Override
                     public void initChannel(SocketChannel ch) throws Exception {
                         ch.pipeline().addLast(new DiscardServerHandler());
                     }
    

    需要实现initChannel接口,一旦channel被注册,这个方法就会被调用

    方法实现中可以添加各种处理器,有netty 自己实现的,也可以我们自己实现

  • 自己实现处理器channelHandler,继承SimpleChannelnboundHandler类

    实现响应内容,编码格式,http版本,影响状态码,设置响应头,flush and write

  • 绑定端口,优雅关闭

访问地址:

http://localhost:8081/

完整代码

package com.tang.springclouddemo.netty.sample01;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class HttpServer {
    public static void main(String[] args) throws Exception {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new TestServerInitialzer());

            ChannelFuture channelFuture = serverBootstrap.bind(8081).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

//========================================================
package com.tang.springclouddemo.netty.sample01;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http2.Http2Codec;

public class TestServerInitialzer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new TestChannelHandler());
    }
}


//========================================================

package com.tang.springclouddemo.netty.sample01;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

public class TestChannelHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
        if (httpObject instanceof HttpRequest) {
            ByteBuf content = Unpooled.copiedBuffer("netty hello my world", CharsetUtil.UTF_8);
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());

            channelHandlerContext.writeAndFlush(response);
        }
    }
}




6.netty回调与channel执行流程分析

主要在channelHandler里面,HttpRequest可以获取各种请求特征信息。

然后重写channlerHandler,里面有各种handler add,channel register,channel active,channel inactive,channel unregistered的回调处理,执行流程如上。

注意的是http1.1上面有keep alive机制,可能是3秒或者其他的,浏览器会有自己的策略,netty可以根据这个时间自主的决定关闭时间。不同于curl工具,是直接关闭连接的。



7.netty的socket编程详解

netty作为http server吞吐量肯定更高,但是tomcat spring mvc并不是一文不值,spring mvc的开发效率更高,比如:netty 没有对请求路由提供任何的支持。

netty更接近底层原理

web socket不必满足http的各种请求特征

netty 作为socket开发的时候,可以自定义协议,自定义请求头类似的规则,也不是很深奥


差异点

一个线程组

使用BootStrap,而不是ServerBootStrap



8.netty多客户端连接与通讯

聊天室模型,实现

终于开始理解,为什么说chanel就是连接,可以理解为,客户端与服务端的通讯的连接

可以从chanel里面拿到客户端的信息,也可以通过channel将信息发送给服务端


核心概念

channelHandler里面的静态成员变量:ChannelGroup



9.netty读写检测机制与长连接要素

同理,客户端可以通过BootStrap,获取channel,然后监听键盘输入,与服务端进行交互



10.netty对于web socket的支援

关键:netty的非阻塞的编程模型,是怎么样子的


前提

需要服务端提供web socket的支持,websocket需要tomcat8.5.8以上版本才支持,spring也支持,好像是4.x版本,netty就不用说了,也支持


知识点

面试的时候基本上都是问netty的底层原理:NIO编程

servlet编程:doGet,doPost

早期假的长连接的实现:客户端轮询,但是避免不了一点,http协议太重了,很多不必要的请求头等信息,重要的传输的数据,可能很小,但是需要带上繁重的http外壳

html5中web socket的实现,解决了长连接的问题

web socket是建立与http之上的,第一次向服务端建立连接的时候,是一个标准的http连接,不过携带了web socket的信息,服务端在收到请求后,读取到请求特征,连接从http连接升级成web socket长连接(全双工)


扩展

微信:自定义的协议


自己的思考

Sping Cloud的Feign调用,底层是基于http的,它有个特点,就是在Feign调用的时候,会去掉http的请求特征,开始遇到这种场景的时候,我有点不理解,觉得是框架不完善。现在开始理解了,这么做是为了rpc调用性能的考虑,http协议包含了太多的请求特征,各种繁琐的请求头,可能这个数据量已经超过了传输本身的数据量,而本身的http是无状态的,rpc调用的时候,相当于是回归了本质,继续回归到了最初的无状态的http,简单,高效。但是如果在开发过程中,需要http携带有状态的标识,可以通过feign拦截器实现,还可以通过动态的配置feign调用地址,有选择性的配置将部分接口加上请求特征,比如请求头,具体的拦截器是:

RequestInterceptor

11.netty实现客户端与服务端的长连接通讯



web socket编程实战


1.server不变


2.server端核心channel handler

  • HttpServerCodec,http编解码
  • ChunkedWriteHandler,按块写入
  • HttpObjectAggregator(8192),http聚合,自己定义channelHandler的时候,与HttpFullRequest和HttpFullResponse配合使用
  • WebSocketServerProtocolHandler(“/ws”),帮助处理繁重的工作去帮忙run web socket


3.TextWebSocketFrameHandler

泛型传TextWebSocketFrame




核心概念


frame 帧

有个概念叫frame,帧,text frame,文本帧,binary frame二进制帧,父类是WebSocketFrame,实现类分别是TextWebSocketFrame,BinaryWebSocketFrame,PingWebSocketFrame,PongWebSocketFrame,CloseWebSocketFrame,ContinuationWebSocketFrame,一共6种

rpc与

rmi

rmi是java内部的

rpc是可以跨语言的


问题

缺少心跳机制后,web socket客户端与服务端无法感知连接端掉



12.google protobuf详解

下载:protobuf编译器 ,https://github.com/protocolbuffers/protobuf/releases,下载对应操作系统的protobuf编译器

语言maven依赖

<dependency>
	 <groupId>com.google.protobuf</groupId>
	 <artifactId>protobuf-java</artifactId>
	  <version>3.5.1</version>
 </dependency>

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java-util</artifactId>
    <version>3.5.0</version>
</dependency>



13.定义protobuf文件及消息详解

java编写指南,https://developers.google.cn/protocol-buffers/docs/javatutorial



14.protobuf完整实例详解

是protoc命令从.proto文件生成.java文件,然后利用生成的api,创建类,赋值,转成字节数组,(网络传输),再转成对象,获取属性值



15.protobuf集成netty与多协议消息传递


代码实现,核心channelHandler

  • 四个Protobuf开头的(除了nacos结尾的两个)
  • Decoder的入参类型是MessageLite,是需要传输的对象类型

客户端与服务端的ChannelInitializer和channelHandler的代码几乎是一样的,区别在与客户端在建立连接的时候,在channel active事件的时候,去构造对象,然后write到channel里面,然后服务端直接在channelRead0上读取,打印


弊端

对象都是写死在channel handler里面和channelInitializer里面的,不过灵活,需要解决



16.protobuf多协议消息支援与工程最佳实践

上面的问题有两种解决方案

  • 自定义协议,重写解码channelHandler
  • 使用外层消息体包围一次,内部使用枚举和oneof实现

为什么需要上面这种做法,因为本质的netty本质上客户端与服务端建立的是一条tcp连接,不处理具体请求路由的逻辑


问题

如何让生成的.proto文件在多客户端共享?



17.protobuf使用最佳实践与Apache thrift介绍

上面的问题的解决方案,处理生成的中间项目(proto-java)

  • git submodule
  • git subtree

其他方案:

将proto-java打成一个jar包,然后deploy,缺点是每次deploy都要更改版本号,然后依赖到这个jar的项目也需要更改版本号


apache thrift

本意是节约的,节俭的,与protobuf一样,都需要idl,接口描述语言



32.io体系架构系统回顾与装饰器模式的具体应用



java的io模型


分类

  • 节点流,直接与源数据交互的流
  • 过滤流,构造函数中需要关联节点流,使用节点流作为输入或输出

下图中,除了FilterInputStream,都是节点流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LxlTL6mm-1640480780998)(/Users/tangyongshuang/Library/Application Support/typora-user-images/image-20211212104911645.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4WVi8NSV-1640480781002)(/Users/tangyongshuang/Library/Application Support/typora-user-images/image-20211212105300726.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nj7mqgDt-1640480781003)(/Users/tangyongshuang/Library/Application Support/typora-user-images/image-20211212105644064.png)]

java的io库使用了装饰器模式,通过流的链接,可以动态的增加流的功能,而这种功能的增加是通过组合流的一些基本功能而动态获取的。



装饰器模式

装饰器模式又被称为warpper模式

new C(new B(new A())),这样就直接拥有了C,B,A三个的功能了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ls3VMO8T-1640480781005)(/Users/tangyongshuang/Library/Application Support/typora-user-images/image-20211212110918992.png)]

装饰器模式与继承的区别


装饰模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yoDkfwk3-1640480781007)(/Users/tangyongshuang/Library/Application Support/typora-user-images/image-20211212112048730.png)]


继承

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rylvZkzM-1640480781009)(/Users/tangyongshuang/Library/Application Support/typora-user-images/image-20211212112343551.png)]



33.java nio深入详解与体系分析


核心概念

selector,channel,buffer,在java.nio中,我们是面向block或者buffer来编程的。buffer本身是一块内存,底层实现上是一个数组。

(简单点理解,可以将channel与io编程中的stream对应起来,但是两者还是有区别的),所有数据的读写都是通过buffer进行的

顺序——>thread——>selector——>channel(多个)——>buffer(多个)

buffer是一个双向通道,即可以写数据也可以读数据,所有的数据读写都是通过buffer操作的,而不是channel。

7种常见的buffer,IntBuffer,ByteBuffer,CharBuffer

重要的api:buffer.flip(),翻转



34.buffer中各重要状态属性的含义与关系图解


重要属性

-面试经常问

  • position:可读、可写的第一个元素索引位置
  • limit:不可读,不可写的第一个元素索引位置
  • capactity:容量值,不变的

position<=limit<=capatity

或者

0<=mark<=position<=limit<=capatity,不过一般情况下mark是未定义的,是undefined类型的



35.java nio核心类源码解读与分析

flip方法会将limit设置成position,将position设置成0,read和write都会将position的值加+

buffer不是线程安全的

buffer的get方法调用的时候,position的值是会增加的



36.文件通道用法详解

clear方法是将position的值设置成0,将limit的值设置成capatity值一样,但是不是直接清除buffer里面的数据,而是改变标志位的大小

//while(true)循环的时候,需要先将buffer里面的内容清空,否则会一直读取原来buffer里面的恶内容
//代码示例
public static void main(String[] args) throws Exception{
        FileInputStream fileInput = new FileInputStream("input.txt");
        FileOutputStream fileOutput = new FileOutputStream("output.txt");

        FileChannel inputChannel = fileInput.getChannel();
        FileChannel outputChannel = fileOutput.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(512);

        while (true) {
            buffer.clear();

            int read = inputChannel.read(buffer);

            if (read == -1) {
                break;
            }

            buffer.flip();

            outputChannel.write(buffer);
        }
    }



37.buffer深入详解

可以put和get基本类型化的数据,然后按序取出


buffer的slice方法

与原来的源buffer是共享的,对slicebuffer的修改对源buffer的修改是可见的


只读buffer

可以将普通buffer转化为只读buffer,但是不能将只读buffer转换成普通buffer

只读buffer的实现还是比较简单的,就是在调用修改的buffer的api中,直接抛出只读buffer异常



38.nio堆外内存与零拷贝深入讲解

DirectByteBuffer对应的内存,不在java堆上面,所以称为堆外内存

java应用程序在进行io操作的时候,对内的字节数组并不是直接和操作系统进行交互的,而是先在堆外划分一块内存空间,然后将堆内的字节拷贝到直接内存当中,然后再进行io操作,中间有一次拷贝的操作

如果直接申请到DirectByteBuffer中,就少了一次拷贝,就会快一些,效率更高

DirectByteBuffer父类的父类的父类Buffer中有一个成员变量address,存储的就是堆外内存的引用地址

为什么操作系统不直接访问对内存?

因为java堆存在gc,gc的时候,一些gc算法会进行对象压缩,整理,字节数组的内存地址会变掉,这样操作系统就无法获取到正确的内存地址,所以无法操作。

两种办法

1.固定对象,对于jvm来说,单独的固定某个对象,很难。

2.拷贝到堆外内存进行操作。

DirectByteBuffer的堆外内存,如何进行回收,DirectByteBuffer被回收了,它所标记的堆外地址会被找到,自然可以进行回收



39.nio中Scattering(分散)与Gathering(聚合)剖析


核心概念


内存映射文件

是放置在堆外内存中的,可以直接通过channel.map方法获取MappedByteBuffer,然后在内存中读写,然后同步的到磁盘


文件锁

可以通过chnnel的lock方法获取,可以是共享锁,也可以是排它锁


Scattering(分散)与Gathering(聚合)

实际应用场景,比如说,自定义协议的时候,可以将第一个buffer定义为一个头,第二个buffer定义为格式,第三个buffer定义为体,这样可以分门别类的实现数据读取,而不是把所有消息放在一个buffer里面,后面再手动分割。

两个tcp工具,telnet,nc



40.selector源码深入分析

SelectionKey:channel注册以后的信息获取,都是通过SelectionKey来实现的,是一个相当重要的概念,相当于channel的一个动作

key set:全部的监听事件

selected-key set:感兴趣的事件,是key set的子集

cancelledKey:取消的key,是key set的子集

新创建的selector的时候,上面的者三个集合都是空的



41.nio网络访问模式分析

可以通过channel的register方法将selector注册到channel上,同时也会将一个key添加到selector的集合当中

主要讲的是上面三种状态的转换,以及select,selectNow,select(long)的方法执行的含义



42.nio网络编程实例剖析

一个channel注册到selector上面会创建一个SelectionKey

selector.select()

事件使用完了之后一定要清除掉selectionKey.remove



47.netty服务器与客户端编码模式回顾及源码分析准备

案例回顾



48.netty与nio系统总结及nio与netty之间的关联关系分析

案例回顾



49.零拷贝深入剖析及用户空间与内核空间切换方式


经典io

read,write操作

经典io模型会经过4次上线问切换,2次没有必要的数据拷贝,实际上是不需要拷贝到用户空间的

零拷贝是依赖于操作系统的,操作系统提供了才能实现


零拷贝

linux,nuix上,sendfile操作

只有2次上下文切换,

顺序

磁盘——>写到内核空间缓存区——>socket缓存区——>发生数据,返回结果


疑问

能否直接将数据从磁盘写到socket缓存区



50.零拷贝实例深度剖析



52.NioEventLoopGroup源码分析与线程数设定

默认设置的是当前线程核心数*2

因为现在windows又超线程的存在,比如说4核8线程,也就是说会默认初始化16个线程。



53.netty对executor的实现机制源码分析


注意

默认的netty的package的包名带internal的,是netty不推荐外界使用的类,比如StringUtils

本节可以理解为是Executor类的详解,真的很细致

ThreadPerTaskExecutor用到了两种设计模式

  • 命令模式,jdk1.5的Executor类的,execute入参是Runable是command命名,里面封装了具体命令信息
  • 代理模式,ThreadPerTaskExecutor,本身不执行execute方法,而是通过内部引入的threadFactory去执行方法,threadFactory是构造ThreadPerTaskExecutor 的时候传进去的

实现Executor接口的execute方法,execute方法内部的执行可能发生在一个新的线程里,也可能发生在调用者线程里,比如下面的DirectExecutor类

public class DirectExecutor implements Executor {    public void execute(Runnable command) {       //不new线程,直接调用command的run方法实现        command.run();    }}

更典型的是创建新的线程实现ThreadPerTaskExecutor

public class ThreadPerTaskExecutor implements Executor {    public void execute(Runnable command) {        new Thread(command).start();    }}


英文学习

given:给定的



54.netty服务端初始化过程与反射在其中的应用分析


英文学习

tag interface:标记接口,或者也可以叫mark interface

bootstrap:启动


netty常识

NioServerSocketChannel的作用就等于nio selector

childHandler的方法接收的是channelHandler对象,ChannelInitializer最终继承的也是channelHandler对象

调用ServerBootstrap的bind方法的时候,会创建channel的实例,并绑定它

future就说明是异步的

//类对象创建实例,通过无参构造创建clazz.getConstructor().newInstance();//即,下面的代码,无参构造还可以这么玩,有意思public NioServerSocketChannel() {        this(newSocket(DEFAULT_SELECTOR_PROVIDER));    }private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();private static ServerSocketChannel newSocket(SelectorProvider provider) {        try {            /**             *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in             *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.             *             *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.             */            return provider.openServerSocketChannel();        } catch (IOException e) {            throw new ChannelException(                    "Failed to open a server socket.", e);        }    }

上面的SelectorProvider就是java.nio包里的SelectorProvider



55.netty提供的Future与ChannelFuture又是分析与源码讲解

通过前面的server端的代码实现,可以发现在调用bind方法之前,都是一些初始化,赋值给成员变量的操作,做一些准备工作。


Future封装的是异步计算的结果。

//典型应用interface ArchiveSearcher { String search(String target); } class App {   ExecutorService executor = ...   ArchiveSearcher searcher = ...   void showSearch(final String target)       throws InterruptedException {     Future<String> future       = executor.submit(new Callable<String>() {         public String call() {             return searcher.search(target);         }});     // 同步执行     displayOtherThings(); // do other things while searching     try {       displayText(future.get()); // use future     } catch (ExecutionException ex) { cleanup(); return; }   } }//future的应用FutureTask<String> future =   new FutureTask<String>(new Callable<String>() {     public String call() {       return searcher.search(target);   }}); executor.execute(future);

Future.cancel的入参boolean mayInterruptIfRunning,含义如下:

true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete

即:传true,执行中的任务可以被打断,传false,执行中任务允许被完成。

netty自己实现的Futute,增加了Listener,典型的观察者模式

java.util.concurrent.Future——>io.netty.util.concurrent.Future——>ChannelFuture

里面有一个实现比较好玩,重写的时候,子类的返回值,可以是父类的子类

	  @Override    ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);    @Override    ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);


注意

ChannelFuture里面有一句话,All I/O operations in Netty are asynchronous。所有的IO操作,在netty中都是异步的。

不要在ChannelHandler中调用await方法,否则可能会造成死锁。

要注意连接超时与等待超时的区别

读javadoc文档,比读书要更加好一点


英文学习

retireve:获取

computation:计算

additional:附加的

determine:确定

usable:可用的

declare:声明

underlying:底层

various:各种各样

logic:逻辑



56.netty服务器地址绑定底层源码分析

https://github.com/netty/netty/issues/2308

netty的channel是对java的channel的封装

NioServerSocketChannel的无参构造方法,里面有一些默认是实现

//构造方法中,关注了SelectionKey.OP_ACCEPTpublic NioServerSocketChannel(ServerSocketChannel channel) {        super(null, channel, SelectionKey.OP_ACCEPT);        config = new NioServerSocketChannelConfig(this, javaChannel().socket());    }//ServerSocketChannel是java.nio包里的



57.Reactor模式透彻理解,及其在netty中的应用

看下经典文章:https://www.jianshu.com/p/0d0eece6d467


留下疑问

  • 为什么要使用2个group

  • NioServerSocketChannel是怎么封装java.nio的

reactor模式,反应器模式


经典书籍

道格里书籍:Scalable IO in java,译文:https://www.cnblogs.com/dafanjoy/p/11217708.html

reactor论文:http://www.yeolar.com/note/2012/12/09/reactor/


英文学习

scalable:可伸缩的


事件驱动的优点

  • 更少的资源,不用为每个客户端产生一个线程
  • 更低的代价,更少的上下文切换,更少的锁事件
  • 但是转发可能会慢一点


应用场景

reactor的主要应用都是在网络编程方面


含义

  • 不同的事件是不同的处理器handler
  • handler执行非阻塞的动作
  • 是通过将handler绑定到事件上进行管理


理解

Reactor实际上是一个线程对象,可以理解为netty中的一个NioEventLoop就是一个Reactor




java nio 相关概念理解


Channel

:通道可以连接到文件,socket等等,支持非阻塞的读


Buffers

:类似于数组对象,能够被通道channel直接读或者写

**Selectors:**告诉一组channel中,哪些产生了IO事件

**SeletionKeys:**包含了事件状态和绑定信息,通过SeletionKeys我们就知道是哪个channel产生了 SeletionKey, 可以追随到原始的channel对象



58.Reactor模式与netty之间的关系详解

多线程的Reacor模式

新增mainReactor与subReactor的概念

netty内部也存在线程池与等待队列 ,最后会发现,很多框架都会实现自己的线程池与阻塞队列,业务里面也可以实现这样一个线程池与阻塞队列。



59.Acceptor与Dispatcher角色分析

论文地址:http://www.yeolar.com/note/2012/12/09/reactor/

reactor可以理解为一种设计模式,给它叫dispatcher或者notifier感觉也可以


acceptor

在连接建立时有参与,后面业务处理时,不参与


连接事件

acceptor注册到dispatcher上去,一旦dispatcher的select的方法返回(select是操作系统的方法,类似于serversocekt的accept方法),即事件发生,会通知acceptor处理事件,然后acceptor通知去创建处理器handler,handler注册到dispatcher上去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRSXcKpP-1640480781010)(/Users/tangyongshuang/Library/Application Support/typora-user-images/image-20211216070845062.png)]

一个channel对应一个pieline

ChanelHandlerContext实现了一个一个的处理channelHandler

初始化的时候,netty自己创建了acceptor添加到pieline里面,叫ServerBootstrapAcceptor

p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                      //调用了channel的eventLoop的execute方法,去添加ServerBootstrapAcceptor
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });



60.netty的自适应缓冲区分配策略与堆外内存创建方式

视频开始仔细讲解了boosGroup和workerGroup的作用与区别,我自己总结一下


总结

客户端建立连接的时候,是与boosGroup进行交互的, boosGroup中会有一个选择器,是基于nio中的Selector进行操作的,这个Selector不断的进行循环,监测客户端向boosGroup发起的连接请求,boosGroup只会监听一种事件,就是op_accept,事件产生之后,就会接收连接,连接建立之后 ,boosGroup会把与客户端的连接转交给workerGroup,这个时候可以说针对某一个客户端来说,boosGroup的使命就完成了。紧接着,连接到workerGroup之后,workerGroup关注的事件是op_read,不过是op_read还是前面的op_accept,都是基于选择器Selector的,两个Selector都一样,事件的监听都是通过不断的调用Selector的select方法,等待事件的发生,事件发生的载体是什么,就是SelectionKey。后续发生的op_read事件,就不用通过bossGroup了。

还有上面的连接转交给workerGroup,是通过Acceptor来实现的,实现机制是,boosGroup的accept返回的 SelectionKey的集合,是Selector的selectedKeys方法返回的SelectionKey集合,传递给workerGroup ,SelectionKey本身有channel方法,channel返回的是selectableChannel,在netty里,可以获取channel,这个channel就是SocketChannel,实际上就是NioSocketChannel,然后会将NioSocketChannel注册到workerGroup选择器中,这个选择器就会不断的去轮询。


问:bossGroup是怎么使用Selector的?

boosGroup也就是NioEventLoopGroup继承了MultithreadEventLoopGroup,

doc注释是这么说的

MultithreadEventLoopGroup implementations which is used for NIO Selector based Channels.

实现了基于channels的nio selector

// NioEventLoopGroup的无参构造,doc注释如下// Create a new instance using the default number of threads, the default ThreadFactory and the SelectorProvider // which is returned by SelectorProvider.provider().public NioEventLoopGroup() {        this(0);}//调用的构造方法如下public NioEventLoopGroup(int nThreads, Executor executor) {        this(nThreads, executor, SelectorProvider.provider());}


为什么要专门分出来boosGroup呢

因为op_accept只执行一次


自适应缓冲区

:实现类,AdaptiveRecvByteBufAllocator,sizeTable



61.Reactor模式5大角色彻底分析


传统的一个socket一个线程的模式的缺点

1.线程数创建的太多,但是服务端能够创建的线程数有限的

2.线程上下文切换是需要成本的

3.连接建立之后,并非时时刻刻都有数据传递,但是连接还需要保持,也就是说线程还需要存在,这样会造成一种现象,服务端会有很多空置的线程。

其他附加

node.js其实也是一种reactor的一种实现,甚至可以说,服务端使用一个线程就能解决所有的客户端的连接

在这里插入图片描述

在这里插入图片描述



62.Reactor模式组件调用关系全景分析

事件是由select方法产生的,它监听后将事件转化成内部的各种事件

有点非常有意思,上面的Event Handler,将所有的Event Handler注册到初始分发器中,使用的方法是register_handler(h),然后事件发生的时候,再遍历handler,根据事件类型,调用handler_event的方法



63.Reactor模式与Netty组件对比及Acceptor组件的作用分析

一个具体event handler可以处理多个事件,因此需要传入事件类型,根据类型处理不同的事件

reactor模式的流程

在这里插入图片描述



64.channel与channelPipeline关联关系及模式应用

sub Reactor本身是一个I/O线程池,自己的业务代码不要阻塞I/O线程池


英文学习

nexus:连接

channel类的pieline方法,返回pieline对象,非常重要。

channelId,短的id会重复,长的id不会重复。

eventLoop方法返回当前的EventLoop

channel是可以有parent的,当然也可以没有,比如SocketChannel就是ServerSocketChannel的方法获取到的,可以说ServerSocketChannel就是SocketChannel的parent。


何时创建channel的?

在bind方法的时候,将前面的BootStrap传入的NioServerSocketChannel,通过工厂模式,通过反射创建的。


何时创建Pipeline的?

在创建AbstractChannel对象的时候,构造方法里,创建的Pipeline

也可以说,创建channel的时候,就创建Pipeline了



65.ChannelPipeline的创建时机与高级拦截过滤器模式的使用

pipeline的addlast方法,决定了入站和出站的处理顺序

假如addlast添加的分别是1,2,3,4,5

那么inbound处理的顺序就会死12345,出站的处理顺序则是54321,入站处理器只会处理入站,出站处理器只会处理出站。



66.Netty常量池实现及ChannelOption与Attribute作用分析

ChannelOption主要是完成了tcp的设置项,主要作用是为了设置ChannelConfig,底层接口是Constans

Attribute相关的类,有AttributeKey,AttributeMap,类似于HashMap的key和value,Attribute相当于value



67.Channel与ChannelHandler及ChannelhandlerContext之间的关系分析

options0方法返回的options是LinkedHashMapprivate final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();

ChannelhandlerContext是channelHandler与channelPipeline之间交互的桥梁和纽带



68.Netty核心4大组件关系与构建方式深度解读

@Sharable 标识handler是否可以共享,不标记共享的不能重复加入pipeline

@Skip 跳过handler的执行

@UnstableApi 提醒不稳定,慎用


注意

Pipeline是一个容器,里面维护的是一个个ChannelhandlerContext对象(多个),ChannelhandlerContext对象内部存放的是我们编写的或者是netty提供的channelHandler对象。

ChannelhandlerContext中可以获取channel,channelHandler,还有pipeline。



69.Netty初始化流程总结及Channel及ChannelHandlerContext作用域分析

NioServerSocketChannel类内部的成员变量ServerSoscketChannelConfig,内部维护的是ChannelOption

Channel.attr() = ChannelHandlerContext.attr()

可以称为是channel的作用域,前面的channel设置过,后面的context都可以获取到。

ChannelHandlerContext.attr(..) == Channel.attr(..)

在这里插入图片描述


英文学习

vice versa:反之亦然



70.Channel注册流程深度解析

初始化channel的时候,会初始化Pipeline,acceptor的作用就是将连接从bossGroup转移到workerGroup。

ServerBootStrap的config方法是其抽象类AbstractBootstrap定义的抽象抽象方法config,抽象类返回的是AbstractBootstrapConfig类,ServerBootStrap重写这个方法,返回的是ServerBootstrapConfig,这个config是成员变量初始化的时候创建的类,传入ServerBootStrap

private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);

上面的抽象类设计还是挺有意思的,抽象类父类定义抽象方法,子类重新抽象方法,返回具体的实现。然后confg又传入ServerBootStrap

看一下这两个类的写法

public abstract class AbstractBootstrapConfig<B extends AbstractBootstrap<B, C>, C extends Channel> {    protected final B bootstrap;    protected AbstractBootstrapConfig(B bootstrap) {        this.bootstrap = ObjectUtil.checkNotNull(bootstrap, "bootstrap");    }



71.Channel选择器工厂与轮训算法及注册底层实现

选择器轮训算法实现类PowerOfTwoEventExecutorChooser和GenericEventExecutorChooser

返回的这两种EventExecutor,实际底层是为了返回合适的EventLoop

private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {        private final AtomicInteger idx = new AtomicInteger();        private final EventExecutor[] executors;        PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {            this.executors = executors;        }        @Override        public EventExecutor next() {            return executors[idx.getAndIncrement() & executors.length - 1];        }    }    private static final class GenericEventExecutorChooser implements EventExecutorChooser {        private final AtomicInteger idx = new AtomicInteger();        private final EventExecutor[] executors;        GenericEventExecutorChooser(EventExecutor[] executors) {            this.executors = executors;        }        @Override        public EventExecutor next() {            return executors[Math.abs(idx.getAndIncrement() % executors.length)];        }    }

channel的注册,底层是调用了unsafe的register方法,返回的是ChannelFuture方法,这个Unsafe是Channel内部的接口

Register a Channel with this EventLoop. The returned ChannelFuture will get notified once the registration was complete.

ChannelFuture register(Channel channel);

channel注册底层方法

promise.channel().unsafe().register(this, promise);


英文学习

round-robin:轮询



72.netty线程模型深度解读与架构设计原则

判断inEventLoop

@Override
    public boolean inEventLoop(Thread thread) {
        return thread == this.thread;
    }


netty中的几大原则

-线程模型

1.一个EventLoopGroup当中包含一个或多个EventLoop

2.一个EventLoop在它的整个生命周期当中都只会与一个Thread进行绑定(成员变量)

3.所有由EventLoop所处理的各种IO事件,都将在它所关联的Thread上进行处理

4.一个channel在它的整个生命周期中只会注册在一个EventLoop上

5.一个EventLoop在运行当中,会被分配给一个或多个channel

注意:channelHandler的处理,一定不能耗时,或者阻塞,因为一个线程可能服务于多个channel,阻塞了一个channel,多个channel都将受到影响。


扩展点

java nio中,多个channel可以注册到一个selector上面,通过事件来进行检测



73.Netty底层架构系统总结与应用实战

sync方法的作用,等待future完成,即isDone方法返回true,因为ChannelFuture是会马上返回的,可能返回的时候bind方法还没完成,这个时候直接执行下面的操作就可能报错,所以要等待其完成端口号绑定等操作,为什么可能还没完成,因为其内部是异步的,返回的Future对象也能够说明这一点。

为什么后面要调用future.channel().closeFuture().sync();这个方法,实际上一般情况下,我们的的程序就会阻塞在这个地方,等待着服务器关闭事件的触发,真正出发后,这段代码才会done

在这里插入图片描述



74.Netty对于异步读写操作的架构思想与观察者模式的重要应用

Netty的Future对象更加精细化有了isSuccess和isCancellable的方法,而不是单纯的jdk 原生的isDone,主要是提供了addListener,removeListener相关方法

GenericFutureListener只有一个方法,void operationComplete(F future) throws Exception;当Future完成之后会调用,我们熟悉的Future对象是ChannelFutureListener。ChannelFutureListener定义了3个常见的成员变量。我们在addListener 的时候,可以直接将这些默认的成员变量添加进去,少了一些代码。如果操作是耗时的,要以独立的方式,或者线程池的方式来实现operationComplete方法。

Listener会存储在集合里,主题对象会遍历集合,然后调用指定Listener



75.适配器模式与模板方法模式在入站处理器中的应用

适配器模式:一个类做一些默认实现,其他的类继承这个实现

模板方法模式:父类定义抽象方法,自己调用,但在子类实现这个抽象方法

典型的方法,

I imsg = (I) msg;channelRead0(ctx, imsg);protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;

user-platform中,用到了策略模式,模板方法模式,观察者模式,适配器模式



76.Netty项目开发过程中常见且重要事项分析

重要结论

在这里插入图片描述

实际开发中,如果走更少的channelHandler也能满足要求,那就走最少的channelHandler,即调用ctx的写消息的方法。


结论

:面试中也可能会问

在这里插入图片描述

netty是怎么兼容Oio与Nio的,怎么统一编程模型的,通过等待超时时间,超时的时候捕获异常,然后记录当前的操作,然后在EventLoop的下一次循环当中进行尝试。

netty中nio线程模型:EventLoopGroup —1:n—> EventLoop —1:n—> Channel

这种可以达到单台机器10万甚至百万的并发量。

netty中oio线程模型:EventLoopGroup —1:n—> EventLoop —1:1—> Channel

一个channel一个线程,显然无法维持过多的连接。

对于既充当服务端,又充当客户端的应用来说,它有一个最佳实践,即服务端与客户端共用一个EventLoop

伪代码

在这里插入图片描述



77.Java NIO Buffer总结回顾与难点扩展

Channel本身是双向的,这个更符合操作系统的真实情况,因为liunx中,底层操作系统的通道也是双向的。


使用nio读取文件所涉及的步骤

  1. 从FileInputStream中获取到Channel对象。
  2. 创建Buffer。
  3. 将数据从channel中读取到Buffer对象中。

0<=mark<=position<=limit<=capatity

ByteBuffer中有3个重要的方法


flip()方法

1.将limit值设置为当前的position

2.将position设为0


clear()方法

1.将limit值设为capacity

2.将position值设为0


compact()方法

1.将所有未读的数据复制到buffer起始位置处。

2.将position设置最后一个未读元素后面。

3.将limit设为capacity

4.现在buffer就准备好了,但是不会覆盖未读的数据


DirectByteBuffer属于user space,也就是用户态。

优秀问答:

https://www.zhihu.com/question/57374068/answer/152691891

优秀博主:

https://www.zhihu.com/people/rednaxelafx



78.Netty数据容器ByteBuffer底层数据结构深度剖析

ByteBuf的作用是为了存储数据的。

netty里面存储数据有两种方式,一种是池化Pooled,一种是非池化Unpooled,用完就销毁。

ByteBuf提供了2个指针变量,一个readIndex,一个writeIndex分别针对读写操作。

对于ByteBuf来说,内存区域被划成了3块,可丢弃的,可读的,可写的,最开始的时候区域都是writable可读的

    +-------------------+------------------+------------------+    | discardable bytes |  readable bytes  |  writable bytes  |    +-------------------+------------------+------------------+    |                   |                  |                  |    0      <=      readerIndex   <=   writerIndex    <=    capacity


英文学习:

capacity:可拍city

derived:衍生的

典型代码,因为java里面int占用4个字节,如果空间不够写的,那会抛出IndexOutOfBoundsException异常

   // Fills the writable bytes of a buffer with random integers.
   ByteBuf buffer = ...;
   while (buffer.maxWritableBytes() >= 4) {
       buffer.writeInt(random.nextInt());
   }

方法定义如下,

Sets the specified 32-bit integer at the current writerIndex and increases the writerIndex by 4 in this buffer.

Throws:

IndexOutOfBoundsException – if this.writableBytes is less than 4

public abstract ByteBuf writeInt(int value);

clear方法只是将两个指针置为0,使整个区域的位置编程可读的,对实际的数据并没有操作。

duplicate()是浅复制,copy()是深复制,

调用duplicate()方法时,对一个bytebuffer的操作,会影响另外一个,This method does not modify readerIndex or writerIndex of this buffer.。如果需要完全的复制一个byteBuffer,就需要copy()方法。

getByte方法是绝对方法,不会使readIndex加一的,readIndex才会使其加一



79.Netty的ByteBuf底层实现大揭秘

常见方法分析

**hasArray()**

方法

返回true的时候,表示是堆上的缓存,因为堆上的缓存才是使用数组的形式存储的

Netty的ByteBuf是会扩容的,默认的capacity是该

字符

集单字符的最大默认空间,utf-8就是3,你写入一个“hello world”,就会创建一个capacity为33的byte数组。你写入一个“唐hello world”,字节数是14,最大容量是12*3=36。

简而言之,writeIndex是实际字节的值,capacity是跟字符集相关,跟单个字符数量是相关的,12个字符就是最大12*3=36的空间。

直接按字节打印出来汉字,就是乱码。

**getCharSequence()**

方法

可以直接打印出汉字



80.Netty复合缓存区详解与3种缓冲区适用场景分析


heap buffer

:快速创建与释放,socket传输比较慢,多了一次拷贝。


direct buffer

:Netty通过内存池来存放direct buffer,减少了操作系统内存的分配与释放。socket传输的时候比较快。直接缓冲区是不能通过字节数组的方式来访问数据的。


composite buffer

:可以理解为一个容器或者是一个列表,可以包含heap buffer和direct buffer,这个是jdk没有实现的。比如http的请求,我们可以将头信息放在direct buffer里面,将body信息放到heap buffer里面。

在这里插入图片描述

在这里插入图片描述



81.Netty的引用计数的实现机制,与自旋锁的使用技巧

netty的bytebuffer的字节数组是没有final修饰的,是可以指向一个新的字节数组的。

在这里插入图片描述



82.Netty引用计数原子更新揭秘与AtomicIntegerFieldUpdater深度剖析

netty里面的引用计数变成0了,就这个对象就不能再使用了。

cas的操作的原子性的满足,要求对该自字段必须都是通过cas来实现才能保证原子性。



83.AtomicIntegerFieldUpdater实例演练与volatile关键字分析


AtomicIntegerFieldUpdater要点总结

  1. 更新器更新的必须是int类型变量,不能是其包装类型;
  2. 更新器更新的必须是volatile类型变量,确保线程之间共享变量时的立即可见性。
  3. 更新的变量不能是static变量,必须要是实例变量。因为Unsafe.objectFieldOffset()方法不支持静态变量(cas操作本质上是通过对象实例的偏移量来直接进行赋值)
  4. 更新器只能修改它可见范围内的变量,因为更新器都是通过反射来得到这个变量,如果变量不可见就会报错。

如果要更新的变量是包装类型,那么可以使用AtomicReferenceFieldUpdater来进行更新。


问:


为什么使用AtomicIntegerFieldUpdater而不是AtomicInteger?



84.netty引用计数注意事项,与内存泄漏检测方式

for;;的行为就是自旋锁,



85.Netty编解码器剖析与入站出站处理器详解

在这里插入图片描述



86.Netty自定义编解码器与TCP粘包拆包问题

ByteToMessageDecoder实现自定义解码器的基类。可以认为是比较基本的解码器。

MessageToByteEncoder是实现自定义编码器的基类。

Netty提供了完好的TCP粘包拆包工具,将消息数据还原成其本身的样子。


英文学习

Detection:检测



87.Netty编码器执行流程深度分析

在这里插入图片描述



88.ReplayingDecoder源码分析与特性解读

ReplayingDecoder没有重写ByteToMessageDecoder 的decode方法,它的好处在于不需要我们去判断ByteBuf中的字节够不够我们读取的。是通过内部的成员变量ReplayingDecoderByteBuf类来实现的,这个类也是继承了ByteBuf。

突然明白了,为什么http要在请求头中添加

Content-Length

这个请求头

因为浏览器需要告诉服务端,需要读取的消息数据的长度是多少,让解码器直接根据这个长度进行数据读取。

解码器固定读取请求头中的

Content-Length

的字段,然后根据取到的值,让解码器直接根据这个长度进行数据读取。假如我们要实现一个自定义协议,肯定要实现这个逻辑。



89.Netty常见且重要的编解码器详解

MessageToMessageDecoder将一种入站消息转化为另一种message。

解码器就是入站处理器,它的作用是将消息从Byte转换成Message或者将Message转化成其他Message。

还有MessageToMessageEncoder,MessageToMessageCodec可以处理出站消息,又可以处理入站消息

比较重要的一个解码器:LengthFieldBasedFrameDecoder,工作中用的比较多,按照指定的长度进行解码。

在这里插入图片描述



90.TCP粘包与拆包实例演示及分析

主要是对LengthFieldBasedFrameDecoder类的讲解


TCP协议是基于连接的,且是保证顺序的,先发的消息先到,后发的消息后到。

但是会出现粘包与拆包的问题,为什么呢,因为TCP是基于连接的,类似流的协议


每个客户端连接,都会调用initChannel方法,都会创建新的ServerChannel。


快捷键

command + shift + 上下键,可以换代码的位置



91.Netty自定义协议与TCP粘包拆包问题解决之道

自定义协议:一般定义length和body,类似http,按指定长度进行解码。



92.精通并发与Netty课程总结与展望

源码部分大概占了三分之一的课程,很多基础内容和案例也花了一些时间。



自我总结

从12月8号开始学习Netty,到现在12月26号,花了将近20天的时间,才将整个Netty过了一遍。从基本的应用httpServer,Rpc Socket,WebSocket,到java io,java nio 到各种rpc协议,再到源码解析,收获颇多,发现了很多开源框架和组件,都在应用Netty,感觉没有白学,再会。



版权声明:本文为qq_42145871原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。