Java NIO的简单封装

一、主要目的

封装Java NIO框架,更方便使用。

主要是提供事件处理的接口,任务队列,快速事件注册,读写任务类。

二、主要代码

(一)主服务器类

java 复制代码
package org.aio;

import org.aio.entity.DefaultListener;
import org.aio.entity.Listener;
import org.aio.entity.Session;
import org.aio.util.KeyUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
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;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public class NIOServer {

    private String host = "0.0.0.0";

    private int port = 8080;

    private ServerSocketChannel server = null;

    private AtomicBoolean isRun = new AtomicBoolean(true);

    private Listener listener = new DefaultListener();

    public NIOServer(){}

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

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

    public void setListener(Listener listener) {
        this.listener = listener;
    }

    public void start() {
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(host, port));
            Selector selector = Selector.open();

            // 注册ACCEPT事件
            server.register(selector, SelectionKey.OP_ACCEPT);

            while (isRun.get()) {
                selector.select();  // 阻塞直到有事件就绪
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();

                while (it.hasNext()) {
                    SelectionKey key = it.next();

                    if (key.isAcceptable()) {
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        System.out.println("远方客户端:" + client.getRemoteAddress());

                        listener.handleAccept(selector, client);
                    } else if (key.isReadable()) {
                        System.out.println("处理读取");
                        SocketChannel client = (SocketChannel) key.channel();
                        listener.handleRead(selector, client, (Session)key.attachment());
                    } else if (key.isWritable()) {
                        System.out.println("处理写入");
                        SocketChannel client = (SocketChannel) key.channel();
                        listener.handleWrite(selector, client, (Session)key.attachment());
                    }
                    it.remove();

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭服务器
     */
    public void close(){
        this.isRun.set(false);
        try {
            this.server.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

(二)监听器

java 复制代码
package org.aio.entity;

import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public interface Listener {

    public abstract void handleAccept(Selector selector, SocketChannel channel);

    public abstract void handleRead(Selector selector, SocketChannel channel, Session session);

    public abstract void handleWrite(Selector selector, SocketChannel channel, Session session);
}

(三)监听器实现类

java 复制代码
package org.aio.entity;

import org.aio.util.KeyUtil;

import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public class DefaultListener implements Listener {
    @Override
    public void handleAccept(Selector selector, SocketChannel channel) {

        // 注册读取事件
        try {
            KeyUtil.registerRead(selector, channel);
            SelectionKey key = channel.keyFor(selector);

            Session session = new Session(key);
            key.attach(session);

            ReadTask task = new ReadTask(session);
            task.setHandler(new MReadTask());

            // 添加读取任务
            System.out.println("---- 添加读取任务 -----");
            session.getReadQueue().enQueue(task);
        } catch (ClosedChannelException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void handleRead(Selector selector, SocketChannel channel, Session session) {
        // 处理读取任务
        session.doRead(channel);
    }

    @Override
    public void handleWrite(Selector selector, SocketChannel channel, Session session) {
        // 处理写入任务
        session.doWrite(channel);
    }
}

(四)会话类

java 复制代码
package org.aio.entity;

import org.aio.util.KeyUtil;

import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import org.aio.entity.Queue;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public class Session {

    private SelectionKey key;

    /**
     * 读取队列
     */
    private Queue<Task> rQueue = new Queue<Task>();

    /**
     * 输出队列
     */
    private Queue<Task> wQueue = new Queue<Task>();

    public Session(SelectionKey key) {
        this.key = key;
    }

    public Queue<Task> getReadQueue(){
        return this.rQueue;
    }

    public Queue<Task> getWriteQueue(){
        return this.wQueue;
    }

    public SelectionKey getKey(){
        return this.key;
    }

    /**
     * 处理读取的逻辑:
     * @param channel
     */
    public void doRead(SocketChannel channel){
        int code = 0;

        // 取消读取事件监听
        KeyUtil.unRegisterRead(this.key);

        try{
            System.out.println("队列是否为空:" + rQueue.empty());
            System.out.println("队列任务个数:" + rQueue.size());

            // 循环,如果队列不为空
            while(!rQueue.empty()){
                // 获取队头任务
                Task t = rQueue.peek();

                // 执行任务
                code = t.run(channel);
                System.out.println("执行返回值:" + code);

                if(code == 0){
                    // 出队任务
                    rQueue.deQueue();
                } else if(code == 1){
                    // 跳出循环
                    break;
                }
            }
        } catch(Exception e){
            e.printStackTrace();
        }

        // 如果返回值为1,添加读取监听
        if(code == 1){
            System.out.println("-------再次注册读取事件-----");
            KeyUtil.registerRead(this.key);
        }
    }

    /**
     * 执行写入任务
     * @param channel
     */
    public void doWrite(SocketChannel channel){
        try{
            System.out.println("取消写入监听");

            // 取消写入事件监听
            KeyUtil.unRegisterWrite(this.key);

            int code = 0;

            System.out.println("输出队列是否为空:" + wQueue.empty());
            System.out.println("输出队列任务个数:" + wQueue.size());

            // 循环,如果队列不为空
            while(!wQueue.empty()){
                // 获取队头任务
                Task t = wQueue.peek();

                // 执行任务
                code = t.run(channel);

                System.out.println("执行任务返回值:" + code);

                if(code == 0){
                    // 出队任务
                    wQueue.deQueue();
                } else if(code == 1){
                    // 跳出循环
                    break;
                }
            }

            // 如果返回值为1,添加读取监听
            if(code == 1){
                System.out.println("再次注册输出事件");
                KeyUtil.registerWrite(this.key);
            }
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}

(五)任务类

run(SocketChannel c)方法会多次调用,根据返回值判断:

返回1,代表不出队,要再次监听,等到下回处理。

返回0,代表出队,任务结束。

java 复制代码
package org.aio.entity;

import java.io.IOException;
import java.nio.channels.SocketChannel;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public interface Task {

    // 返回值:1代表需要下次监听;0代表结束
    int run(SocketChannel channel) throws IOException;
}

可以实现文件输出任务、字节序列输出任务、字符串输出任务、关闭任务。

(六)字节输出的任务

java 复制代码
package org.aio.entity;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @author 
 * @version 1.0.0
 * <p> 输出字节序列的任务
 * date: 2026/4/7
 **/
public class ByteTask implements Task {
    private ByteBuffer buff;

    public ByteTask(byte[] array) {
        this.buff = ByteBuffer.wrap(array);
    }

    // 写入字节序列
    public int run(SocketChannel channel) throws IOException {
        int code = 0;

        // 如果有剩余,就执行循环
        while (buff.hasRemaining()) {
            System.out.println("有剩余字节写入");
            int k = channel.write(this.buff);
            System.out.println("写入字节个数:" + k);

            // 如果有剩余,并且返回值为0
            if (k == 0 && buff.hasRemaining()) {
                code = 1;
                break;
            }
        }
        return code;
    }
}

三、测试案例

1、添加处理任务,处理HTTP请求

实现Handler接口。

java 复制代码
package org.aio.entity;

import org.aio.entity.Queue;
import org.aio.util.KeyUtil;

import java.io.File;
import java.io.UnsupportedEncodingException;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public class MReadTask implements Handler{

    @Override
    public void handle(HttpRequest req, Session session){
        System.out.println("-------------------");
        System.out.println("方法:" + req.getMethod());
        System.out.println("资源:" + req.getResource());
        System.out.println("版本:" + req.getVersion());
        System.out.println("Accept:" + req.getHeader("Accept"));

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

        // 注册写入
        System.out.println("----注册写入-----");
        KeyUtil.registerWrite(session.getKey());

        // 获得会话的输出任务队列
        Queue<Task> queue = session.getWriteQueue();

        System.out.println("------ 添加输出任务 ----");
        try {
            ResponseHead head = new ResponseHead(200, "OK");

            // 定义响应体
            byte[] buff = "<p>Hello World!</p>".getBytes("UTF-8");
            

            // 设置内容长度
            head.setContentLength(buff.length);
            
            // 添加响应头输出任务
            queue.enQueue(head);

            // 添加响应体字节序列输出任务
            queue.enQueue(new ByteTask(buff));
            
            // 添加关闭任务
            queue.enQueue(new CloseTask());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}

2、启动类

java 复制代码
import org.aio.NIOServer;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public class NIOTest {

    public static void main(String[] args) {
        NIOServer server = new NIOServer(80);
        System.out.println("监听在80端口:");

        server.start();
    }
}

四、文件内容输出的任务实现

java 复制代码
package org.aio.entity;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public class FileTask implements Task {

    private FileChannel fc = null;

    private ByteBuffer buff = ByteBuffer.allocate(8024);

    /**
     * 是否文件结束
     */
    private boolean isEOF = false;


    public FileTask(String path){
        try {
            this.fc = FileChannel.open(Paths.get(path));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public int run(SocketChannel channel) throws IOException {
        int code = 0;

        while (true) {
            // 如果文件没有结束
            if (!isEOF){
                // 阻塞读取
                int size = fc.read(buff);  

                // -1表示文件结束
                if (size == -1) {
                    fc.close();
                    isEOF = true;
                }

                // 切换为读取模式
                buff.flip();
            }

            // 如果有剩余,就执行循环
            while(buff.hasRemaining()){
                int k = channel.write(this.buff);

                // 如果有字节剩余,并且返回值为0,代表输出满了
                if(k == 0 && buff.hasRemaining()){
                    code = 1;
                    break;
                }          
            }

            // 如果有剩余数据,进行压缩,防止丢失
            if (buff.hasRemaining()){
                buff.compact();
            }

            // 如果是1,代表需要下回事件触发时进行处理
            if(code == 1){
                break;
            }

            // 如果读取结束,跳出循环
            if (isEOF){
                break;
            }

            // 清空数据
            buff.clear();
        }
        
        return code;
    }
}

五、关闭任务的实现

java 复制代码
package org.aio.entity;

import java.io.IOException;
import java.nio.channels.SocketChannel;

/**
 * @author 
 * @version 1.0.0
 * <p>
 * date: 2026/4/7
 **/
public class CloseTask implements Task {

    public int run(SocketChannel channel) throws IOException {
        channel.close();
        System.out.println("执行关闭-------------------");
        return 0;
    }
}

六、总结

1、基本可以使用。

2、每次处理事件时,首先要取消监听,防止再次触发。

3、需要在处理读取的时候,需要一个缓冲类,缓冲一部分数据,方便处理。

例如规定下列方法:

// 添加最近读取的数据

void add(ByteBuffer b)

// 返回缓冲的字节个数

int getBuffSize()

// 判断是否有一行

boolean hasLine()

// 读取一行字符

String readline()

// 读取指定个数的字节块

byte[] read(int 个数)

4、NIO的缺点

异步注册事件的时候会阻塞在register()方法处。

用wakeup()唤醒Selector的select()方法不生效。

相关推荐
wuxinyan1232 小时前
Java面试题46:一文深入了解JVM 核心知识体系
java·jvm·面试题
小江的记录本2 小时前
【JEECG Boot】 《JEECG Boot 数据字典使用教程》(完整版)
java·前端·数据库·spring boot·后端·spring·mybatis
Chase_______2 小时前
【Python基础 | 第5章】面向对象与异常处理:一文搞懂类、对象、封装、继承、多态
开发语言·python
啦啦啦!2 小时前
项目环境的搭建,项目的初步使用和deepseek的初步认识
开发语言·c++·人工智能·算法
小李云雾2 小时前
Python Web 路由详解:核心知识点全覆盖
开发语言·前端·python·路由
鲸渔2 小时前
【C++ 变量与常量】变量的定义、初始化、const 与 constexpr
java·开发语言·c++
i220818 Faiz Ul2 小时前
教育资源共享平台|基于springboot + vue教育资源共享平台系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·教育资源共享平台
玛卡巴卡ldf2 小时前
【Springboot7】ApachePOI文件导入导出
java·spring boot·sql
编程大师哥2 小时前
VSCode中如何搭建JAVA+MAVEN
java·vscode·maven