NIO学习之Selector,SelectionKey与客户端与服务端通信简单实现(1)

来源:互联网 发布:怎么通过网络挣钱 编辑:程序博客网 时间:2024/05/17 19:58

选择器(Selector)的 作用:将通道感兴趣的事件放入队列中,而不是马上提交给应用程序,等已注册的通道自己来请求处理这些事件。换句话说,就是选择器将会随时报告已经准备好了的通道,而且是按照先进先出的顺序。

Selector类定义如下:
这里写图片描述

open()方法,静态方法,用于获取1个Selector对象

keys()方法,用于获取所有注册到Selector对象上的SelectionKey

selectedKeys()方法,用于获取已注册通道发出的准备好了的SelectionKey

select()方法,调用此方法的线程会一直阻塞,直到注册的通道中关注的事件发生了才会返回。 例:服务端的ServerSocketChannel 注册了OP_ACCEPT事件,如果没有客户端连接服务端,那么服务端将一直阻塞在调用select()方法的地方

select(long)方法,此方法与不带参数的差不多,参数的含义是当阻塞的时间超过给定的时间参数,该方法将返回继续执行

wakeup()方法,会唤醒当前被阻塞的线程,使其 select() 立即返回;如果当前线程没有阻塞,那么执行了wakeUp() 方法之后,下一个线程的 select() 方法会被立即返回,不再被阻塞下去

close()方法,能够关闭当前的选择器。当一个线程当前呈阻塞状态,那么中止这种状态需要先调用Selector的 wakeUp() 方法。close()方法在实现类中先调用wakeUp()方法唤醒被阻塞的线程,然后置空所有的通道、所有就绪的SelectionKey,让这个选择器上的轮询组件也闲置下来。


选择键(SelectionKey)的作用是表明哪个通道已经做好了相应的准备,相应的准备指SelectionKey类里定义的OP_READ,OP_WRITE,OP_CONNECT,OP_ACCEPT中某个某个事件发生了。Selector不断轮询是否有事件准备好了,如果有事件准备好了则获取事件相应的SelectionKey,进入事件处理

SelectionKey定义如下:
这里写图片描述

SelectionKey主要说两点:
attach(Object)主要用于绑定一个对象(对象可以任意类型),绑定后可以在不同线程间共享,对应的attachment()方法用于获取绑定的对象

interestOps()方法用于获取通道“感兴趣”的操作或事件


说了SelectorSelectionKey 的基础知识及主要方法的使用,现在就来实践一下。


服务端
伪代码

1.打开1个ServerSocketChannel通道2.把ServerSocketChannel设置为非阻塞的3.为ServerSocketChannel通道绑定ip地址与端口4.获取Selector对象5.把ServerSocketChannel通道感兴趣的事件注册到Selector对象里6.监听端口,等待已注册的通道关注的事件触发7.获取事件触发的通道进行相应的处理
public class NIOServer {    private Selector selector;    public void initServer(int port) throws IOException{        // 打开ServerSocket通道        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();        serverSocketChannel.configureBlocking(false);        serverSocketChannel.socket().bind(new InetSocketAddress(port));        // 获取一个选择器        this.selector = Selector.open();        // 将通道管理器与该通道进行绑定,并为该通道注册SelectionKey.OP_ACCEPT事件        // 注册事件后,当该事件触发时会使selector.select()返回,        // 否则selector.select()一直阻塞        serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);    }    public void listen() throws IOException{        System.out.println("启动服务器!");        while (true) {            // select()方法一直阻塞直到有注册的通道准备好了才会返回            selector.select();            Iterator<?> iterator = selector.selectedKeys().iterator();            while (iterator.hasNext()) {                SelectionKey key = (SelectionKey) iterator.next();                // 删除已选的key,防止重复处理                iterator.remove();                handler(key);            }        }    }    public void handler(SelectionKey key)throws IOException{        if (key.isAcceptable()) {            handlerAccept(key);        }else if (key.isReadable()){            handlerRead(key);        }else if (key.isWritable()){            System.out.println("can write!");        }else if (key.isConnectable()){            System.out.println("is connectable");        }    }    public void handlerAccept(SelectionKey key) throws IOException{        // 从SelectionKey中获取ServerSocketChannel        ServerSocketChannel server = (ServerSocketChannel) key.channel();         // 获取SocketChannel        SocketChannel socketChannel = server.accept();        // 设置成非阻塞        socketChannel.configureBlocking(false);        System.out.println("与客户端建立连接");        // 为socketChannel通道建立 OP_READ 读操作,使客户端发送的内容可以被读到        socketChannel.register(selector, SelectionKey.OP_READ);   // 往客户端发送发送信息         socketChannel.write(ByteBuffer.wrap("connected\n".getBytes()));    }    public void handlerRead(SelectionKey key)throws IOException{        SocketChannel socketChannel = (SocketChannel) key.channel();        // 创建读取缓冲区        ByteBuffer byteBuffer = ByteBuffer.allocate(512);        // 从通道读取可读取的字节数        try {            int readcount = socketChannel.read(byteBuffer);            if (readcount > 0) {                byte[] data = byteBuffer.array();                String msg = new String(data);                System.out.println("服务端收到的信息为:\n" + msg);                ByteBuffer outBuffer = ByteBuffer.wrap("收到\n".getBytes());                socketChannel.write(outBuffer);            } else {                System.out.println("客户端异常退出");            }        } catch (IOException e) {            System.out.println("异常信息:\n" + e.getMessage());            key.cancel();        }    }    public static void main(String[] args) throws IOException {        NIOServer server = new NIOServer();        server.initServer(8888);        server.listen();    }}

客户端

public class NIOClient1 {    private Selector selector;    private SocketChannel socketChannel;    private SocketAddress address;    public NIOClient1(String host, int port) throws IOException {        address = new InetSocketAddress(host, port);        selector = Selector.open();        socketChannel = SocketChannel.open(address);        socketChannel.configureBlocking(false);        socketChannel.register(selector, SelectionKey.OP_READ);    }    public static void main(String[] args) {        Scanner scanner = new Scanner(System.in);        System.out.print("输入服务器ip地址:\t");        String host = scanner.next();        System.out.print("输入端口号:\t");        int port = scanner.nextInt();        try {            NIOClient client = new NIOClient(host, port);            Selector selector = client.getSelector();            String msg = scanner.next();            while (true) {                if (msg.equals("q")) {                    break;                }                client.getSocketChannel().write(ByteBuffer.wrap(msg.getBytes()));                if (selector.select() > 0) {                    Set<SelectionKey> selectionKeys = selector.selectedKeys();                    Iterator<SelectionKey> iterator = selectionKeys.iterator();                    while (iterator.hasNext()) {                        SelectionKey key = iterator.next();                        if (key.isReadable()) {                            SocketChannel socketChannel = (SocketChannel) key.channel();                            ByteBuffer buffer = ByteBuffer.allocate(1024);                            int size = socketChannel.read(buffer);                            if (size > 0) {                                buffer.flip();                                byte[] data = new byte[size];                                buffer.get(data);                                System.out.println("来自服务端的消息:\t" + new String(data));                            }                        }                        iterator.remove();                    }                }                msg = scanner.next();            }        } catch (IOException e) {            e.printStackTrace();        }    }    public Selector getSelector() {        return selector;    }    public SocketChannel getSocketChannel() {        return socketChannel;    }    public SocketAddress getAddress() {        return address;    }    public void setSelector(Selector selector) {        this.selector = selector;    }    public void setSocketChannel(SocketChannel socketChannel) {        this.socketChannel = socketChannel;    }    public void setAddress(SocketAddress address) {        this.address = address;    }}

运行效果
1. 先启动服务端
2. 启动客户端


这里写图片描述

参考博客

阅读全文
0 0
原创粉丝点击