• 学习笔记 | Netty


    学习笔记 | Netty

    • Netty编程的整体步骤

      1. 编写服务器
      2. 在服务器中添加自定义的Initializer对象
      3. 在Initializer对象中添加自定义的handler对象
      4. 在handler里重写netty提供的回调方法
    • Netty网络编程

      1. 编写Server
        • EventLoopGroup:bossGroup,workerGroup
        • ServerBootstrap: serverBootstrap
        • serverBootstrap
          .group(bossGroup,workerGroup)
          .channel(NioServerSocketChannel.class)
          .childHandler(new MyInitializer());
      2. 编写Initializer
        • 继承ChannelInitializer<SocketChannel>
        • 重写initChannel(SocketChannel)方法
          • 获取ChannelPipeLine:ChannelPipeline pipeline = socketChannel.pipeline();
          • 向pipeLine中添加handler
            • HttpServerCodec:处理Http请求的handler
      3. 编写Handler
        • 继承SimpleChannelInbondHandler<HttpObject>
        • 重写channelRead0方法
    • NettySocket编程

      • server端使用ServerBootstrap,client端使用Bootstrap
      • Initialize用到的HandlerLengthFieldBasedFrameDecoder,LengthFieldPrepender,StringDecoder,StringEncoder
    • Netty聊天室Demo

      • Initializer用到的Handler:DelimiterBasedFrameDecoder,StringDecoder,StringEncoder
      • 自定义Handler:
        1. 定义一个静态私有变量ChannelGroup,用来保存所有已连接的client的Channel
          private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    • Netty心跳检测机制

      • Initializer用到的Handler:IdleStateHandler
      • 自定义的Handler:
        • 继承ChannelInboundHandlerAdapter
        • 重写userEventTrigged
    • websocket为了解决啥问题

      • HTTP的长连接问题
      • HTTP是一种无状态的,基于请求/响应模式的协议
        • 无状态:服务器不会记录请求的信息,
        • 请求/响应模式:客户端发送请求,服务器端返回响应.
      • websocket是基于HTTP协议的,在初次连接时,客户端会在header中携带websocket相关的信息Upgrade:Websocket,请求服务端将连接升级为websocket连接.服务端读到header中信息后,就将当前连接升级为可以支持长连接的websocket连接了.
      • 虽然websocket是基于HTML5规范的,但是也可以用在非浏览器的场合.像Android,iOS
    • 基于websocket的netty示例

      • Initializer中使用到的Handler:ChunkedWriteHandler,HttpObjectAggregator,WebSocketServerProtocolHandler
      • 自定义Handler
        • 继承SimpleChannelInboundHandler<TextWebSocketFrame>
        • 重写channelRead0
    • ProtoBuf相关

      • 用于数据交换的序列结构化数据格式.具有更小的传输体积,更高的编码解码能力.特备适用于数据存储,网络数据传输等对存储体积,实时性要求高的领域.
      • 优点:空间效率高,时间效率高,对数据大小敏感,传输效率高
      • 缺点:消息结构可读性不高
      • 通过.proto生成的Java对象有个toByteArray方法,可以生成对象的字节数组,同时,对象有个parseFrom方法,可以将字节数组转换成当前对象.
    • Netty中使用ProtoBuf

      • 需要在ChannelInitializer的ChannelPipeLine中添加ProtoBuf相关的handler: ProtobufVarint32FrameDecoder,ProtobufDecoder,ProtobufVarint32LengthFieldPrepender,ProtobufEncoder
      • 其中ProtobufDecoder解码器handler中需要传入的参数就是要转换的对象的实例
      • 这样就可以在channel中直接write需要传输的对象,netty会自动把对象使用protobuf编码传输,并可以在另一端自动解析成对象.
    • gRPC的使用

      1. 添加grpc的maven插件
      2. src/proto文件夹下编写proto文件
      3. 使用mvn 编译项目,在arget/generated-sources/protobuf下能够找到生成的java文件.通常生成的service在grpc-java下
      4. 编写service的具体实现类,需要继承生成的[ServiceName]Grpc.[ServiceName]ImplBase
        • eg:public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase
        • 重写所有的service方法.方法中会有个SteamObserver类型的参数:responseObserver,用来传递返回信息或错误信息
        • responseObserver.onNext(myResponse) 添加myResponse到返回信息中
        • responseObserver.onError(throwable); 返会错误信息
        • responseObserver.onCompleted();
      5. 编写server端代码
        1. 使用ServerBuilder获取server对象
          Server server = ServerBuilder.forPort(8899).addService(new StudentServiceImpl()).build();
        2. 调用server的start方法开启服务端
        3. 因为gRPC的server开启后,如果不作处理会自动关闭,所以要调用server的awaitTermination方法让程序一直在运行.
      6. 编写client端代码
        1. 使用ManagedChannelBuilder创建ManagedChannel
        2. 使用生成的ServiceGrpc类的newBlockingStub方法(方法中传入上一步创建的managedChannel)获得blockingStub
        3. 使用blockingStub便可直接调用Service中的方法并获取返回值了.
    • NIO相关

      • NIO三大组成部分
        1. selector
        2. channel
        3. buffer
      • 数据通过channel.read方法写入buffer中,或者通过channel.write通过buffer写入到channel中
      • Buffer的常用类型: ByteBuffer,ShortBuffer,IntBuffer,FloatBuffer,LongBuffer,DoubleBuffer,CharBuffer
      • Buffer中成员变量说明
        • mark:使用mark()方法,将position赋值给mark,使用reset()将position赋值为标记的mark值.如果mark值是-1,报错.
        • position:下一个读或者写的位置
        • limit:buffer能够读或者写的最大位置+1
        • capcity: buffer的最大容量
        • long address: 保存direct buffer在内存中的地址
      • mark <= position <= limit <= capcity
      • ByteBuffer使用说明
        1. ByteBuffer无法直接new出来,需要使用ByteBuffer.allocate(int capcity)方法来返回一个ByteBuffer实例.实际上返回的类型是ByteBuffer的子类HeapByteBuffer.
        2. 使用ByteBuffer.allocate(int capcity)生成的ByteBuffer的各变量的值:
          1. mark = -1
          2. position = 0
          3. limit = capcity
        3. buffer.flip()
          • limit = position;
          • posion = 0;
          • mark = -1;
        4. buffer.rewind();
          • position = 0;
          • mark = -1;
        5. buffer.clear();
          • position = 0;
          • limit = capcity;
          • mark = -1;
        6. buffer.mark();
          • mark = position;
        7. buffer.reset();
          • if mark = -1 throw InvalidMarkException
          • position = mark;
      • ByteBuffer的几个具体类:
        • HeapByteBuffer:可读写的,创建在堆上的ByteBuffer; 通过ByteBuffer.allocate(int capcity)获得
        • HeapByteBufferR:只读的ByteBuffer, 通过bytebuffer.asReadOnlyBuffer()获得
        • DirectByteBuffer:通过ByteBuffer.allocateDirect(int capcity)获得
        • DirectByteBufferR
      • HeapByteBuffer和DirectByteBuffer,零拷贝相关
        • HeapByteBuffer是在JVM堆中创建的对象,其中保存数据的byte数组也是保存在堆中的.实际上操作系统在使用这个byte数组时,会将jvm堆中的byte数组拷贝到操作系统内存中,然后通过内存中的数据与IO设备交互,这样其实就多了一次拷贝的过程
        • DirectBtyeBuffer在创建时就已经在堆外申请了一个内存地址,实际的byte数组数据直接存放在这个内存地址中了,堆中buffer对象持有这个地址的引用.每次在进行数据的读写时,操作系统直接通过这块内存地址与IO设备通信,省去了一次内存拷贝过程.
        • jvm堆也是在操作系统的内存中,为啥HeapByteBuffer还需要将数据拷贝到内存中?
          • 因为在堆中,可能会发生垃圾回收,对象的地址可能发生改变.
    • Selector(选择器或者叫多路复用器)

      • 使用selector的好处:
        • selector使用轮询的方式来判断channel是否处于某些状态,因此使用更少的线程来就可以来处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。
      • Selector.open()返回一个新的Selector对象
      • selector.select()返回触发事件的channel的个数,其中事件是channel在注册时指定的感兴趣的事件.
        • SelectionKey.OP_ACCEPT = 1 << 4
        • SelectionKey.OP_CONNECT = 1 << 3
        • SelectionKey.OP_WRITE = 1 << 2
        • SelectionKey.OP_READ = 1 << 0
    • 完整的Server客户端

      • 第一步:将ServerSocketChannel注册到Selector上
        1. 创建一个selector对象Selector selector = Selector.open();
        2. 使用ServerSocketChannel.open()获得ServerSocketChannel对象
        3. 配置serverSocketChannel为非阻塞,因为要注册到Selector的channel必须是非阻塞的serverSocketChannel.configureBlocking(false)
        4. 设置ServerSocketChannel中ServerSocket的绑定的端口号serverSocketChannel.socket().bind(new INetSocketAddress(5678))
        5. 将serverSocketChannel注册到selector上serverSocketChannel.registor(selector,SelectionKey.OP_ACCEPT);
      • 第二步:编写Selector的轮询逻辑
        1. int num = selector.select();这个方法是一个阻塞方法,只有当selector中注册的channel触发了它在注册时感兴趣的事件,这个方法才会返回值,其返回值是可以进行I/O操作的channel的个数.
        2. Set<SelectionKey> selectionKeys = selector.selectionKeys()获得当前准备好IO操作的channel对应的SelectionKey集合.
        3. Iterator<SelectionKey> iterator = selectionKeys.iterator();
        4. 循环遍历迭代器中的每个SelectionKey,判断当前触发的是哪个IO操作,根据不同的IO操作处理不同的业务逻辑.
          • selectionKey.isAcceptable()判断当前SelectionKey是否触发accept,如果触发了,调用ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel()获得当前的ServerSocketChannel,在使用SocketChannel socketChannel = serverSocketChannel.accept()方法获得请求连接的SocketChannel,设置socketChannel为非阻塞模式并将其注册到selector上.
          • selectionKey.isConnectable()
          • selectionKey.isReadable()
          • selectionKey.isWritable()
        5. 每次遍历最后都要调用iterator.remove()方法,将当前的SelectionKey从selectionKeys中移除
          • 因为selector不会自动删除selectionKeys集合中的SelectionKey,所以如果不手动删除的话,下次轮询结果中还会有上一次轮询时的结果.
    • java nio的零拷贝

      • 从操作系统的角度
      • user space用户空间
      • kernel space内核空间

    sendfile() syscall
    sendfile() returns

    • fileChannel.transferTo(startP,total,socketChannel)
  • 相关阅读:
    code light
    asp.net(C#)定时自动发送邮件
    示例代码(一)
    devExpress 7.2.5
    使用VSIUAL C#.NET操作Excel -把DataTable中的数据写入Excel
    C#日期格式化的几种处理方法
    配置
    关于Windows 2008 R2 Web服务器环境搭建、安全流程
    安全
    FTP 用户目录病隔离
  • 原文地址:https://www.cnblogs.com/Serenity1994/p/12445011.html
Copyright © 2020-2023  润新知