NIODemo

传统请求应答模型

传统Server/Client模式会基于TPR(Thread Pre Request),服务器会为每一个客户端请求创建一个线程,由该线程单独负责处理一个客户请求;这种模式存在一个问题,那就是在高并发时,会创建大量线程,最终会导致服务扛不住而宕机。大多数的实现为了避免宕机的风险而采用线程池,并设置线程池的最大数量,但这又会带来新的问题;假如最大线程数是200,这时刚好有200个用户都在进行大文件的下载,那么第201个用户请求到达时会得不到正常响应,即便只是请求几KB大小的数据。

NIO模型

NIO中采用非阻塞I/O基于Reactor模式的工作方式,IO调用不会被阻塞,而是注册感兴趣的IO事件,如可读数据到达,新的套接字连接等等,在发生特定事件时,系统再通知我们,NIO中实现非阻塞IO的核心对象就是Selector,Selector就是各种注册IO事件的地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件。

当有读或写等任何注册事件发生时,可以从Selector中获取得到相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的具体SelectableChannel,以获得客户端发送过来的数据。

使用NIO大体可以分以下步骤:

  1. 向Selector对象注册感兴趣事件

  2. 从Selector中获取感兴趣事件

  3. 根据不同的事件进行相应的处理

package com.tiger.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;

/**
 * 打开 cmd【telnet localhost 8080】 客户端
 * 使用NIO大体可以分以下步骤:
 * 1. 向Selector对象注册感兴趣事件
 * 2. 从Selector中获取感兴趣事件
 * 3. 根据不同的事件进行相应的处理
 *
 * @description:
 * @author: tiger
 * @create: 2021-05-02 21:56
 */
public class NIODemo {

    static final int PORT = 8080;

    public static void main(String[] args) throws IOException {
        listen(getSelector());
    }

    /**
     * 1、注册感兴趣事件步骤:
     * 创建ServerSocketChannel对象,并配置为非阻塞模式,
     * 接着绑定到具体端口,最后向Selector中注册事件,此处指定的参数是OP_ACCEPT,
     * 即指定我们要监听accept事件,也就是新的连接发生时所产生的事件,
     * 对于ServerSocketChannel通道来说,我们唯一可以指定的参数就是OP_ACCEPT
     *
     * @return
     * @throws IOException
     */
    private static Selector getSelector() throws IOException {
        // 创建Selector对象
        Selector selector = Selector.open();

        // 创建可选择通道,并配置为非阻塞模式
        ServerSocketChannel server = ServerSocketChannel.open();
        server.configureBlocking(false);

        // 绑定通道到指定端口
        ServerSocket socket = server.socket();
        InetSocketAddress addr = new InetSocketAddress(PORT);
        socket.bind(addr);

        // 向selector中注册感兴趣事件,即监听accept事件
        server.register(selector, SelectionKey.OP_ACCEPT);
        return selector;
    }

    /**
     * 2、从Selector中获取感兴趣事件,开始监听,内部不断循环
     * 非阻塞IO内部循环模式基本遵循这种方式,首先调用select()阻塞,
     * 直到至少有一个事件发生,然后再使用selectedKeys()得到SelectionKey,
     * 再进行迭代循环,操作业务逻辑
     */
    private static void listen(Selector selector) {
        System.out.println("listen on:" + PORT);
        try {
            while (true) {
                // 该调用会阻塞,直到至少有一个事件发生
                int select = selector.select();
                if (select == 0) {
                    continue;
                }
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iter = keys.iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    process(key);
                    iter.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 3、根据不同的事件做对应的处理
     * 分别判断是接收请求、读数据还是写事件,分别作不同的处理。
     * 在java1.4之前的IO系统中,提供的都是面向流的IO系统,
     * 系统一次一个字节地处理,一个输入流产生一个字节数据,
     * 一个输出流消费一个字节数据,面向流的IO速度非常慢。
     * 而在java1.4中退出的NIO,这是面向块的IO系统,系统以块的方式处理数据,
     * 每一个操作在一步中产生或消费一个块数据,因此速度提升了不少
     *
     * @param key
     */
    private static void process(SelectionKey key) throws IOException {
        Selector selector = key.selector();

        // 接收请求,判断客户端是否已经连上来了
        if (key.isAcceptable()) {
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            SocketChannel clientChannel = serverChannel.accept();
            System.out.println(String.format("接收请求(isAcceptable) - Connection from %s", clientChannel.getRemoteAddress()));
            clientChannel.configureBlocking(false);
            //设置为 OP_READ ,说明要读数据
            clientChannel.register(selector, SelectionKey.OP_READ);
        }

        // 读取信息
        else if (key.isReadable()) {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = clientChannel.read(buffer);
            if (len > 0) {
                buffer.flip();
                String content = new String(buffer.array(), 0, len);
                System.out.println(String.format("读取信息(isReadable) - From %s : %s", clientChannel.getRemoteAddress(), content));
                SelectionKey skey = clientChannel.register(selector, SelectionKey.OP_WRITE);
                skey.attach(content);
            } else {
                clientChannel.close();
            }
            buffer.clear();
        }

        // 写事件
        else if (key.isWritable()) {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            String content = (String) key.attachment();
            System.out.println(String.format("写事件(isWritable) - From %s : %s", clientChannel.getRemoteAddress(), content));
            ByteBuffer buffer = ByteBuffer.wrap(("输出内容:" + content).getBytes());
            if (buffer != null) {
                clientChannel.write(buffer);
            } else {
                clientChannel.close();
            }
        }

        // isConnectable
        else if (key.isConnectable()) {
            System.out.println("=== isConnectable ===");
        }

        // isValid
        else if (key.isValid()) {

        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ljt-tiger

thanks

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值