多路复用--Selector选择器

多路复用的概念

选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。 使用它可以节省CPU资源,提高程序的运行效率。
"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。
服务器端的非多路复用效果
如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造 成系统性能下降。
服务器端的多路复用效果
使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间, 在高并发、高频段业务环境下有非常重要的优势

选择器Selector

Selector被称为:选择器,也被称为:多路复用器,它可以注册到很多个Channel上,监听各个 Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个 Channel,就可以处理大量网络连接了。 有了Selector,我们就可以利用一个线程来处理所有的Channels。线程之间的切换对操作系统来说代价 是很高的,并且每个线程也会占用一定的系统资源。所以,对系统来说使用的线程越少越好。

如何创建一个Selector

Selector 就是您注册对各种 I/O 事件兴趣的地方,而且当那些事件发生时,就是这个对象告诉您所发生 的事件

Selector selector = Selector.open();
注册Channel到Selector

为了能让Channel和Selector配合使用,我们需要把Channel注册到Selector上。通过调用 channel.register()方法来实现注册:

channel.configureBlocking(false); 
SelectionKey key =channel.register(selector,SelectionKey.OP_READ);

注意,注册的Channel 必须设置成异步模式才可以,否则异步IO就无法工作,这就意味着我们不能把一 个FileChannel注册到Selector,因为FileChannel没有异步模式,但是网络编程中的SocketChannel是 可以的。

register()方法的第二个参数:是一个int值,意思是在通过Selector监听Channel时对什么事件感兴 趣。可以监听四种不同类型的事件,而且可以使用SelectionKey的四个常量表示:
1. 连接就绪–常量:SelectionKey.OP_CONNECT
2. 接收就绪–常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使用此项)
3. 读就绪–常量:SelectionKey.OP_READ
4. 写就绪–常量:SelectionKey.OP_WRITE
注意:对于ServerSocketChannel在注册时,只能使用OP_ACCEPT,否则抛出异常。

示例:下面的例子,服务器创建3个通道,同时监听3个端口,并将3个通道注册到一个选择器中

public class Server {
    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocketChannel server1 = ServerSocketChannel.open();
        server1.configureBlocking(false);
        server1.bind(new InetSocketAddress(7777));

        ServerSocketChannel server2 = ServerSocketChannel.open();
        server2.configureBlocking(false);
        server2.bind(new InetSocketAddress(8888));

        ServerSocketChannel server3 = ServerSocketChannel.open();
        server3.configureBlocking(false);
        server3.bind(new InetSocketAddress(9999));

        //获取一个“多路复用器”
        Selector selector = Selector.open();

        //将端口注册到selector上
        server1.register(selector, SelectionKey.OP_ACCEPT);
        server2.register(selector, SelectionKey.OP_ACCEPT);
        server3.register(selector, SelectionKey.OP_ACCEPT);
    }
}

接下来,就可以通过选择器selector操作三个通道了。

多路连接

1).Selector的keys()方法 此方法返回一个Set集合,表示:已注册通道的集合。每个已注册通道封装为一个 SelectionKey对象。
2).Selector的selectedKeys()方法 此方法返回一个Set集合,表示:当前已连接的通道的集合。每个已连接通道同一封装为一个 SelectionKey对象。
3).Selector的select()方法 此方法会阻塞,直到有至少1个客户端连接。 此方法会返回一个int值,表示有几个客户端连接了服务器。

示例: 客户端:启动两个线程,模拟两个客户端,同时连接服务器的7777和8888端口:

    public static void main(String[] args) {
        new Thread(()->{
            try (SocketChannel socket = SocketChannel.open()){
                System.out.println("7777客户端连接服务器......");
                socket.connect(new InetSocketAddress("127.0.0.1",7777));
                System.out.println("7777客户端连接成功!");
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("7777客户端异常重连");
            }
        }).start();

        new Thread(()->{
            try (SocketChannel socket = SocketChannel.open()){
                System.out.println("8888客户端连接服务器......");
                socket.connect(new InetSocketAddress("127.0.0.1",8888));
                System.out.println("8888客户端连接成功!");
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("8888客户端异常重连");
            }
        }).start();
    }

服务器端:

public static void main(String[] args) throws IOException {
        //创建3个通道,同时监听3个端口
        ServerSocketChannel channelA = ServerSocketChannel.open();
        channelA.configureBlocking(false);
        channelA.bind(new InetSocketAddress(7777));

        ServerSocketChannel channelB = ServerSocketChannel.open();
        channelB.configureBlocking(false);
        channelB.bind(new InetSocketAddress(8888));

        ServerSocketChannel channelC = ServerSocketChannel.open();
        channelC.configureBlocking(false);
        channelC.bind(new InetSocketAddress(9999));

        //获取选择器
        Selector selector = Selector.open();

        //注册三个通道
        channelA.register(selector, SelectionKey.OP_ACCEPT);
        channelB.register(selector, SelectionKey.OP_ACCEPT);
        channelC.register(selector, SelectionKey.OP_ACCEPT);

        Set<SelectionKey> keys = selector.keys();//获取已注册通道的集合
        System.out.println("注册通道数量:"+keys.size());

        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        System.out.println("已连接的通道数量:"+selectionKeys.size());
        System.out.println("===================");

        System.out.println("服务器等待链接...");
        int selectCount = selector.select();
        System.out.println("连接数量:"+selectCount);
        System.out.println("===================");

        Set<SelectionKey> keys1 = selector.keys();
        System.out.println("注册通道数量:"+keys1.size());
        Set<SelectionKey> selectionKeys1 = selector.selectedKeys();
        System.out.println("已连接的通道数量:"+selectionKeys1.size());
    }

实现交互

服务器:

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, InterruptedException {
        ServerSocketChannel server1 = ServerSocketChannel.open();
        server1.configureBlocking(false);
        server1.bind(new InetSocketAddress(7777));

        ServerSocketChannel server2 = ServerSocketChannel.open();
        server2.configureBlocking(false);
        server2.bind(new InetSocketAddress(6666));

        ServerSocketChannel server3 = ServerSocketChannel.open();
        server3.configureBlocking(false);
        server3.bind(new InetSocketAddress(9999));

        //获取一个“多路复用器”
        Selector selector = Selector.open();

        //将端口注册到selector上
        server1.register(selector, SelectionKey.OP_ACCEPT);
        server2.register(selector, SelectionKey.OP_ACCEPT);
        server3.register(selector, SelectionKey.OP_ACCEPT);

        //接下来,操作selector
        while (true) {
            System.out.println("注册完毕,等待链接...");

            int count = selector.select();

            System.out.println("有链接到达,链接数量是:" + count);

            //获取当前所有的selector对象
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> it = selectionKeys.iterator();
            while (it.hasNext()){
                SelectionKey sk = it.next();
                ServerSocketChannel channel = (ServerSocketChannel)sk.channel();
                System.out.println("通道:"+channel.getLocalAddress());

                SocketChannel socket = channel.accept();

                //对socket操作,可接受信息,也可向他发送信息。
                //获取信息
                ByteBuffer buf = ByteBuffer.allocate(1024);
                int len = socket.read(buf);
                String msg = new String(buf.array(),0,len);
                System.out.println("服务器收到消息:"+msg);

                //发送消息
                socket.write(ByteBuffer.wrap("我已收到你的请求,等待处理。".getBytes()));

                it.remove();
                System.out.println("set集合大小:"+selectionKeys.size());

            }

            //处理selector()不阻塞的问题

            Thread.sleep(1000*2);
            System.out.println();
        }

    }
}

客户端:

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

public class Client {
    public static void main(String[] args) {
        new Thread(()->{
            try {
                SocketChannel socket = SocketChannel.open();
                socket.connect(new InetSocketAddress("127.0.0.1",7777));
                socket.write(ByteBuffer.wrap(("你好服务器,我是"+socket).getBytes()));

                ByteBuffer buf = ByteBuffer.allocate(1024);
                int read = socket.read(buf);
                String msg = new String(buf.array(),0,read);
                System.out.println("客户"+socket+"收到消息:"+msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+"执行完毕!");
        }).start();

        new Thread(()->{
            try {
                SocketChannel socket = SocketChannel.open();
                socket.connect(new InetSocketAddress("127.0.0.1",6666));
                socket.write(ByteBuffer.wrap(("你好服务器,我是"+socket).getBytes()));

                ByteBuffer buf = ByteBuffer.allocate(1024);
                int read = socket.read(buf);
                String msg = new String(buf.array(),0,read);
                System.out.println("客户"+socket+"收到消息:"+msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+"执行完毕!");
        }).start();

        new Thread(()->{
            try {
                SocketChannel socket = SocketChannel.open();
                socket.connect(new InetSocketAddress("127.0.0.1",9999));
                socket.write(ByteBuffer.wrap(("你好服务器,我是"+socket).getBytes()));

                ByteBuffer buf = ByteBuffer.allocate(1024);
                int read = socket.read(buf);
                String msg = new String(buf.array(),0,read);
                System.out.println("客户"+socket+"收到消息:"+msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+"执行完毕!");
        }).start();
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值