netty4

`## JAVA NIO

这里有三件宝贝:buffer,selector,channel
先来说说buffer

简介:在传统的面向流操作时,所有的数据都是直接传入stream对象中进行操作的,而面向缓冲区的nio是对缓冲区操作的,无论读取还是写入,缓冲区就是buffer,所有的基本类型都有对应的buffer类,所有缓冲buffer类都继承buffer

原理:buffer就是一个数组实现的,有三个变量对这个数组的读入和写入控制———— position,limit,capacity。position默认是0,limit默认和capacity一样大,capacity就是这个数组底层分配的初始大小。
系统从通道中读取一些数据到buffer中去,position就会++,直到读入的数据停下来
当系统开始从buffer里取数据时,必须先对buffer进行flip 操作,这个时候posijtion就会变为0,指向数组的起点,然后limit会变为position之前的位置,限制get() 时只能到之前写入的最远的position ,最后使用clear()方法三个变量会初始化为初值。

关于buffer的其余种类:缓冲区分片,只读缓冲区,直接缓冲区,内存映射

再来说说selector

传统的client/server 会使用tpr模式,thread per request 客户端来一个请求,服务端就来一个线程负责和客户端通信,这样的弊端就是线程数量随着用户的访问量极速上升,然后大家为了避免这种现象的发生,搞一个线程池,开200个线程去轮流处理,降低开销,但是如果这时候有第201个用户进来下载什么东西的话就会连接失败。

针对之上提到的问题,现在使用reactor 模式,io不会被阻塞而注册感兴趣的事件, 当可读数据到达,新的套接字连接时,发生特定的事件时,系统再通知我们。selector就是注册各种io事件的地方,当事件发生的时候,selector会告诉我们

原理:当有读写等任何注册的事件发生时,可以从selector中获取相应的selectionKey,同时从selectionKey中可以找到相对应的事件和该事件所对应的selectableChannel ,以获得客户端发送来的数据

步骤:
(1)给selector对象注册感兴趣的事件
(2)从selector中获取感兴趣的事件
(3)根据不同的事件进行相应的处理。

代码实例:
server端

package com.zdj.nettytest.core;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Server {
public static void main(String[] args) throws IOException {
    //创建ServerSocketChannel
    ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
    //得到一个selector对象
    Selector selector = Selector.open();
    //绑定端口
    InetSocketAddress inetSocketAddress=new InetSocketAddress(78);
    serverSocketChannel.socket().bind(inetSocketAddress);
    //设置为非阻塞
    serverSocketChannel.configureBlocking(false);
    //把serverSokcetChannel注册到selector 关心事件为OP_ACCPET,也就是连接事件
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    //循环等待连接
    while(true){
        //先等待一秒钟 如果没有连接事件发生,则返回
        if(selector.select(1000)==0){
            System.out.println("等待了1秒 无事发生");
            continue;
        }
        //如果返回大于0 则获得有事件发生的selectionKey
        //通过selectionKey反向获取通道
        Set<SelectionKey> selectionKeys=selector.selectedKeys();
        //使用迭代器遍历Set集合
        Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
        while(selectionKeyIterator.hasNext()){
            SelectionKey selectionKey=selectionKeyIterator.next();
            //根据selectionKey的通道发生的事件进行处理
            if(selectionKey.isAcceptable()){
                //处理的是OP_ACCPET,也就是连接事件
                //为该客户生成一个通道 socketChannel
                SocketChannel socketChannel=serverSocketChannel.accept();
                System.out.println("客户端连接成功");
                //将该通道设置为非阻塞状态 否则会报java.nio.channels.IllegalBlockingModeException异常
                socketChannel.configureBlocking(false);
                //将当前通道注册到selector中,关注事件为OP_READ,并绑定一个buffer
                socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
            }
            else if(selectionKey.isReadable()){
                //如果selectionKey是可读的
                //反向获取通道
                SocketChannel channel =(SocketChannel)selectionKey.channel();
                //获取该通道绑定的buffer
                ByteBuffer attachment = (ByteBuffer)selectionKey.attachment();
                channel.read(attachment);
                System.out.println("来自客户端的信息是"+new String(attachment.array()));
            }
            //从集合中删除key 防止重复操作
            selectionKeyIterator.remove();
        }

    }

}
}

client 端

package com.zdj.nettytest.core;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Server {
public static void main(String[] args) throws IOException {
    //创建ServerSocketChannel
    ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
    //得到一个selector对象
    Selector selector = Selector.open();
    //绑定端口
    InetSocketAddress inetSocketAddress=new InetSocketAddress(78);
    serverSocketChannel.socket().bind(inetSocketAddress);
    //设置为非阻塞
    serverSocketChannel.configureBlocking(false);
    //把serverSokcetChannel注册到selector 关心事件为OP_ACCPET,也就是连接事件
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    //循环等待连接
    while(true){
        //先等待一秒钟 如果没有连接事件发生,则返回
        if(selector.select(1000)==0){
            System.out.println("等待了1秒 无事发生");
            continue;
        }
        //如果返回大于0 则获得有事件发生的selectionKey
        //通过selectionKey反向获取通道
        Set<SelectionKey> selectionKeys=selector.selectedKeys();
        //使用迭代器遍历Set集合
        Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
        while(selectionKeyIterator.hasNext()){
            SelectionKey selectionKey=selectionKeyIterator.next();
            //根据selectionKey的通道发生的事件进行处理
            if(selectionKey.isAcceptable()){
                //处理的是OP_ACCPET,也就是连接事件
                //为该客户生成一个通道 socketChannel
                SocketChannel socketChannel=serverSocketChannel.accept();
                System.out.println("客户端连接成功");
                //将该通道设置为非阻塞状态 否则会报java.nio.channels.IllegalBlockingModeException异常
                socketChannel.configureBlocking(false);
                //将当前通道注册到selector中,关注事件为OP_READ,并绑定一个buffer
                socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
            }
            else if(selectionKey.isReadable()){
                //如果selectionKey是可读的
                //反向获取通道
                SocketChannel channel =(SocketChannel)selectionKey.channel();
                //获取该通道绑定的buffer
                ByteBuffer attachment = (ByteBuffer)selectionKey.attachment();
                channel.read(attachment);
                System.out.println("来自客户端的信息是"+new String(attachment.array()));
            }
            //从集合中删除key 防止重复操作
            selectionKeyIterator.remove();
        }
    }
  }
}

效果:
在这里插入图片描述

Channel

NIO读取数据:

(1) 从FileInputStream 获取Channel
(2) 创建buffer
(3) 讲数据从Channel 读取到Buffer中

代码实例:

    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("/Users/zhangdongjie/Desktop/1.txt");
        FileChannel fc = fileInputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        fc.read(buffer);
        buffer.flip();
        while(buffer.remaining() > 0) {
            byte b = buffer.get();
            System.out.print((char)(b));
        }
        fileInputStream.close();
    }

NIO写入数据
(1)从FileInputStream获取Channel
(2)创建Buffer
(3)将数据从Channel写入Buffer

    public static void main(String[] args) throws IOException {
        FileOutputStream fileInputStream = new FileOutputStream("/Users/zhangdongjie/Desktop/1.txt");
        FileChannel fc = fileInputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes(StandardCharsets.UTF_8));
        fc.write(buffer);
        fileInputStream.close();
    }

Netty 样例

服务端

public class EchoServer {

    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public void start() throws InterruptedException {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        //1、创建EventLoopGroup以进行事件的处理,如接受新连接以及读/写数据
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //2、创建ServerBootstrap,引导和绑定服务器
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(group, group)
                    //3、指定所使用的NIO传输Channel
                    .channel(NioServerSocketChannel.class)
                    //4、使用指定的端口设置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    //5、添加一个 EchoServerHandler 到子 Channel的 ChannelPipeline
                    //当一个新的连接被接受时,一个新的子Channel将会被创建,而 ChannelInitializer 将会把一个你的EchoServerHandler 的实例添加到该 Channel 的 ChannelPipeline 中
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(serverHandler);
                        }
                    });
            //6、异步地绑定服务器,调用sync()方法阻塞等待直到绑定完成
            ChannelFuture channelFuture = bootstrap.bind().sync();
            System.out.println(EchoServer.class.getName() + "started and listening for connections on" + channelFuture.channel().localAddress());
            //7、获取 Channel 的 CloseFuture,并且阻塞当前线程直到它完成
            channelFuture.channel().closeFuture().sync();

        } finally {
            //8、关闭 EventLoopGroup 释放所有的资源
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new EchoServer(9999).start();
    }
}
@ChannelHandler.Sharable //标识一个Channel-Handler 可以被多个Channel安全的共享
public class EchoServerHandler extends ChannelHandlerAdapter {


    /**
     * 对于每个传入的消息都要调用
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));
        //将接收到的消息写给发送者,而不冲刷出站消息
        //ChannelHandlerContext 发送消息。导致消息向下一个ChannelHandler流动
        //Channel 发送消息将会导致消息从 ChannelPipeline的尾端开始流动
        ctx.write(in);
    }

    /**
     * 通知 ChannelHandlerAdapter 最后一次对channel-Read()的调用是当前批量读取中的最后一条消息
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //暂存于ChannelOutboundBuffer中的消息,在下一次调用flush()或者writeAndFlush()方法时将会尝试写出到套接字
        //将这份暂存消息冲刷到远程节点,并且关闭该Channel
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 在读取操作期间,有异常抛出时会调用
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

}

客户端

public class EchoClient {

    private final String host;
    private final int port;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            //指定 EventLoopGroup 以处理客户端事件;适应于NIO的实现
            bootstrap.group(group)
                    //适用于NIO传输的Channel类型
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    //在创建Channel时,向ChannelPipeline中添加一个EchoClientHandler实例
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            //连接到远程节点,阻塞等待直到连接完成
            ChannelFuture channelFuture = bootstrap.connect().sync();
            //阻塞,直到Channel 关闭
            channelFuture.channel().closeFuture().sync();
        } finally {
            //关闭线程池并且释放所有的资源
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new EchoClient("127.0.0.1", 9999).start();

        System.out.println("------------------------------------");

        new EchoClient("127.0.0.1", 9999).start();

        System.out.println("------------------------------------");

        new EchoClient("127.0.0.1", 9999).start();
    }


}
@ChannelHandler.Sharable //标记该类的实例可以被多个Channel共享
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    /**
     * 当从服务器接收到一条消息时被调用
     *
     * @param ctx
     * @param msg ByteBuf (Netty 的字节容器) 作为一个面向流的协议,TCP 保证了字节数组将会按照服务器发送它们的顺序接收
     * @throws Exception
     */
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println("Client" + ctx.channel().remoteAddress() + "connected");
        System.out.println(msg.toString(CharsetUtil.UTF_8));
    }

    /**
     * 在到服务器的连接已经建立之后将被调用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx)  {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rock!", CharsetUtil.UTF_8));
    }


    /**
     * 在处理过程中引发异常时被调用
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

}

Netty组件和设计

Channel—Socket;
EventLoop—控制流、多线程处理、并发;
ChannelFuture—异步通知。

Channel
对底层网络io操作提供封装,基本构造是class socket 降低了socket直接使用的复杂度

EventLoop
用于处理连接生命周期中所发生的事件,
目前,图3-1
在高层次上说明了Channel、EventLoop、Thread 以及EventLoopGroup 之间的关系

在这里插入图片描述

ChannelFuture
正如我们已经解释过的那样,Netty 中所有的I/O 操作都是异步的。因为一个操作可能不会
立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了
ChannelFuture 接口,其addListener()方法注册了一个ChannelFutureListener,以
便在某个操作完成时(无论是否成功)得到通知。

ChannelHandler ChannelPipeline
ChannelHandler 是处理所有进站出站数据的处理逻辑的容器
netty有许多不同类型的ChannelHandler,它们各自的功能主要取决于
它们的超类。Netty 以适配器类的形式提供了大量默认的ChannelHandler 实现,其旨在简化
应用程序处理逻辑的开发过程。你已经看到了,ChannelPipeline中的每个ChannelHandler
将负责把事件转发到链中的下一个ChannelHandler。这些适配器类(及它们的子类)将自动
执行这个操作,所以你可以只重写那些你想要特殊处理的方法和事件。

ChannelPpieline
ChannelPipeline是将所有的channelhandeler穿起来(使得事件流经ChannelPipeline 是ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给
链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。实际上,
被我们称为ChannelPipeline 的是这些ChannelHandler 的编排顺序。)
ChannelPipeline 提供了ChannelHandler 链的容器,并定义了用于在该链上传播入站
和出站事件流的API。当Channel 被创建时,它会被自动地分配到它专属的ChannelPipeline。
ChannelHandler 安装到ChannelPipeline 中的过程如下所示:

  • 一个ChannelInitializer的实现被注册到了ServerBootstrap中;
  • 当ChannelInitializer.initChannel()方法被调用时,ChannelInitializer
    将在ChannelPipeline 中安装一组自定义的ChannelHandler;
  • ChannelInitializer 将它自己从ChannelPipeline 中移除。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值