Socket之非阻塞IO(NIO)

220 阅读3分钟

NIO --> New IO,提供了非阻塞模型(No -Blocked IO)

NIO这块参考文章:

同步非阻塞

Server端

public class MyNewIOServer {

    public static void main(String[] args) {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);//设置为非阻塞(非阻塞IO默认是阻塞的,需要这样设置一下才生效)
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            while (true){
                SocketChannel socketChannel = serverSocketChannel.accept();//监听客户端请求
                if(socketChannel != null){
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//分配缓冲区的大小
                    socketChannel.read(byteBuffer);//把数据读取到缓冲区
                    System.out.println(new String(byteBuffer.array()));

                    //写出数据
                    byteBuffer.flip();//反转
                    socketChannel.write(byteBuffer);//写出去
                }else{
                    //连接不阻塞,没有发现客户端有请求过来,睡眠一下,再次重试。
                    Thread.sleep(1000);
                    System.out.println("连接未就绪");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Client端:

public class MyNewIOClient {

    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
//            socketChannel.configureBlocking(false);//这里同样也要设置成 非阻塞
            socketChannel.connect(new InetSocketAddress("localhost",8080));
            /**
             * 如果SocketChannel在非阻塞模式下【设置的socketChannel.configureBlocking(false)】,
             * 此时调用connect(),该方法可能在连接建立之前就返回了.
             * 为了确定连接是否建立,可以调用finishConnect()的方法
             */
            if(socketChannel.isConnectionPending()){
                socketChannel.finishConnect();//与服务端建立连接(返回true,表示与服务端连接成功)
            }
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put("Hello,我是小风".getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);

            //读取数据
            byteBuffer.clear();//先清空一下缓冲区
//            Thread.sleep(2000); 如果设置了socketChannel.configureBlocking(false);,下面的read就读不到数据,这里可以增加2s的停顿时间,下面就读取到了
            int i = socketChannel.read(byteBuffer);//把服务端返回的数据读到缓冲区
            //注释:read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)
            if(i > 0){ //收到数据
                System.out.println("收到了服务端的数据" + new String(byteBuffer.array()));
            }else{
                System.out.println("没有收到数据");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

以上代码没有用 Selector(多路复用器),其实有很大的性能消耗...

多路复用

Selector是多路复用器

** 下面使用Selector重构 **

Server端

public class NewIoServer {

    static Selector selector;

    public static void main(String[] args) {
        try {
            selector=Selector.open();
            //selector 必须是非阻塞
            ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); //设置为非阻塞
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //每个客户端连接来了之后,就把连接事件注册到多路复用器上
            while(true){
                selector.select(); //阻塞机制
                Set<SelectionKey> selectionKeySet=selector.selectedKeys();
                Iterator<SelectionKey> iterable=selectionKeySet.iterator();
                while(iterable.hasNext()){
                    SelectionKey key=iterable.next();
                    iterable.remove();
                    if(key.isAcceptable()){ //连接事件。默认初始状态就是acceptable。
                        handleAccept(key);
                    }else if(key.isReadable()){ //读的就绪事件
                        handleRead(key);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleAccept(SelectionKey selectionKey){
        ServerSocketChannel serverSocketChannel=(ServerSocketChannel) selectionKey.channel();
        try {
            SocketChannel socketChannel=serverSocketChannel.accept() ;//这个是客户端的socketChannel
            System.out.println("客户端的channel来源:"+socketChannel.getRemoteAddress());
            socketChannel.configureBlocking(false);
            socketChannel.write(ByteBuffer.wrap("Hello Client,I'm NIO Server".getBytes()));
            socketChannel.register(selector,SelectionKey.OP_READ); //注册读事件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void handleRead(SelectionKey selectionKey){
        SocketChannel socketChannel=(SocketChannel)selectionKey.channel();
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);//分配缓冲区大小
        try {
        	//数据的交互是以buffer为中间桥梁的
            //如果要读取,先把数据读到buffer里,然后再进行打印
            socketChannel.read(byteBuffer); //把数据读取到缓冲区(这里是不是一定有值)
            //打印
            System.out.println("server receive msg:"+new String(byteBuffer.array()));
            //如果要返回数据的话,先把数据返回到buffer里,然后再返回出去
            String res = "这里是服务端获取的数据12345";
            channel.write(ByteBuffer.wrap(res.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client端

public class NewIOClient {

    static Selector selector;

    public static void main(String[] args) {
        try {
            selector=Selector.open();
            SocketChannel socketChannel=SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("localhost",8080));
            socketChannel.register(selector, SelectionKey.OP_CONNECT); //连接事件
            while(true){
                selector.select();
                Set<SelectionKey> selectionKeySet=selector.selectedKeys();
                Iterator<SelectionKey> iterator=selectionKeySet.iterator();
                while(iterator.hasNext()){
                    SelectionKey key=iterator.next();
                    iterator.remove();
                    if(key.isConnectable()){ //连接事件
                        handleConnect(key);
                    }else if(key.isReadable()){
                        handleRead(key);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void handleConnect(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel=(SocketChannel)selectionKey.channel();
        if(socketChannel.isConnectionPending()){
            socketChannel.finishConnect();
        }
        socketChannel.configureBlocking(false);
        socketChannel.write(ByteBuffer.wrap("Hello Server,I'm NIo Client".getBytes()));
        socketChannel.register(selector,SelectionKey.OP_READ); //
    }

    private static void handleRead(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel=(SocketChannel)selectionKey.channel();
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        socketChannel.read(byteBuffer);
        System.out.println("client receive msg:"+new String(byteBuffer.array()));
    }

}