简单的多路复用io实现

BossEventLoop

public class BossEventLoop implements Runnable{


    private Selector boss;
    private volatile boolean isStart = false;
    private AtomicInteger index = new AtomicInteger();
    private WorkerEventLoop worker;


    public void register() throws IOException {
      // 防止重复初始化
        if (!isStart) {
           // 可以理解为创建一个服务器
            ServerSocketChannel ssc = ServerSocketChannel.open();
           // 监听9999端口
            ssc.bind(new InetSocketAddress(9999));
            // 设置为非阻塞模式
            ssc.configureBlocking(false);
            // 创建一个selector
            boss = Selector.open();
            // 将channel注册到selector中。
            ssc.register(boss, SelectionKey.OP_ACCEPT);
            new Thread(this, "boss").start();
            worker = new WorkerEventLoop(0);
            isStart = true;
        
    }


    @Override
    public void run() {
        while (true) {
            try {
               // 阻塞。 当selector注册的通道上有事件发生时继续向下运行。
                boss.select();
                Set<SelectionKey> selectionKeys = boss.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (key.isAcceptable()) {
                        // 拿到key对应的channel(其实就是上面创建的)
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                       // 建立连接后,通过ServerSocketChannel获取到SocketChannel,接收后续的读事件
                        SocketChannel sc = channel.accept();
                        sc.configureBlocking(false);
                        // 将SocketChannel交给下一级的selector(worker)来管理。(将该SocketChannel注册到selector上)
                        workers.register(sc);
                    }

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

        }
    }
}

WorkerEventLoop

public class WorkerEventLoop implements Runnable{
    Selector worker;
    private volatile boolean isStart = false;
    Thread thread;
    int index;

    public WorkerEventLoop(int index) {
        this.index = index;
    }


    public void register(SocketChannel sc) throws IOException {
        if (!isStart) {
            worker = Selector.open();
            new Thread(this, "worker" + index).start();
            sc.register(worker, SelectionKey.OP_READ);
            isStart = true;
        }

    }

    @Override
    public void run() {
        while (true) {
            try {
                worker.select();
                Set<SelectionKey> selectionKeys = worker.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (key.isReadable()) {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer allocate = ByteBuffer.allocate(128);
                        int read = channel.read(allocate);
                        if (read == -1) {
                            key.cancel();
                            channel.close();
                        } else {
                            allocate.flip();
                            System.out.print("worker" + index + "-");
                            System.out.println(Charset.defaultCharset().decode(allocate));
                        }
                    }

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

问题剖析

问题出在WorkerEventLoop上

 public void register(SocketChannel sc) throws IOException {
        if (!isStart) {
            worker = Selector.open();
            new Thread(this, "worker" + index).start();
            sc.register(worker, SelectionKey.OP_READ);
            isStart = true;wo
        }

    }
 @Override
    public void run() {
        while (true) {
            try {
                worker.select();
              .......

这两个代码分别由主线程和 new Thread()出来的子线程执行, 因此顺序不可预测,

① 若worker.select()先执行, sc.register()后执行,则会阻塞,导致两边都不能继续往下走。

② 若sc.register()先执行,worker.select()后执行,则第一个客户端来连接并传送数据时可以正常运转。 而第二个客户端来时, worker,select()再次阻塞, 导致之后的程序还是无法正常执行。

解决方法–队列

private final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();

   

public void register(SocketChannel sc) throws IOException {
        if (!start) {
            worker = Selector.open();
            new Thread(this, "worker-" + index).start();
            start = true;
        }
      // 通过队列来保证同步。
        queue.add(() -> {
            try {
                SelectionKey sckey = sc.register(worker, 0, null);
                sckey.interestOps(SelectionKey.OP_READ);
                worker.selectNow();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
   			// 唤醒selector
        worker.wakeup();
    }



 @Override
        public void run() {
            while (true) {
                try {
                    worker.select();
                  // 因为queue为ConcurrentLinkedQueue,所以不会阻塞
                    Runnable task = queue.poll();
                    if (task != null) {
                        task.run();
                    }
         ...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值