NIO基础

NIO基础

一个简单的通信,需要三个Socket,一个用于服务端监听,一个用于服务端连接,一个用于客户端连接。

在不考虑多线程的情况下,BIO是无法处理并发的。

public class QQService {
    static byte[] bytes = new byte[1024];
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        //阻塞
        serverSocket.bind(new InetSocketAddress(8080));
        while (true) {
            System.out.println("等待连接");
            //阻塞网络连接
            Socket socket = serverSocket.accept();
            System.out.println("连接成功");
            System.out.println("等待数据");
            //read也会阻塞
            int read = socket.getInputStream().read(bytes);
            System.out.println("接收到数据");
            String content = new String(bytes);

            System.out.println(content);
        }


    }
}

接收数据的时候有两个地方会阻塞,一个是等待连接,一个是接收数据。

可以通过多线程解决 ,在多线程里面去等待多个连接。得到一个新的Socket则开一个新的线程。

多线程不好的原因,多线程浪费资源,会存在很多不活跃的线程。很多不活跃的线程最好就用单线程来处理。

NIO

最关键的就是这两个阻塞。

最核心的额就是把Read变成非阻塞。

1576426655901.png

进入waitconnetion为什么不能读到:

  1. 因为serverSocket.accept阻塞
  2. 已经把第一个链接的Socket丢掉了

所以需要保存之前连上的Scoket连接。

循环,让之前两次的阻塞不阻塞。每次循环都去读取列表里面保存的Socket连接,判断是否有数据连接。如果有新的连接就把新连接加到列表里面去。

BIO多线程解决

在子线程里面阻塞。

public class QQService {
    static byte[] bytes = new byte[1024];
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        //阻塞
        serverSocket.bind(new InetSocketAddress(8080));
        while (true) {
            System.out.println("等待连接");
            //阻塞网络连接
            Socket socket = serverSocket.accept();
            System.out.println("连接成功");
            System.out.println("等待数据");
//            //read也会阻塞
//            int read = socket.getInputStream().read(bytes);
//            System.out.println("接收到数据");
//            String content = new String(bytes);
//
//            System.out.println(content);
            Thread thread = new Thread(new ExecuteSocket(socket));
            thread.run();
        }
    }
    public static class ExecuteSocket implements Runnable {
        byte[] bytes = new byte[1024];
        Socket socket;
        public ExecuteSocket(Socket socket) {
            this.socket =  socket;
        }

        @Override
        public void run() {
            try {
                //read也会阻塞
                int read = socket.getInputStream().read(bytes);
                System.out.println("接收到数据");
                String content = new String(bytes);
                System.out.println(content);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

不阻塞的方式——NIO雏形

Socket有一个设置是否阻塞的方法。比如socket.setblock(false)那么这个socket就不会往下执行了。这个时候acceptread都需要设置不阻塞,加入只设置read那么只要没人来连接就阻塞了,不能处理了。

  static byte[] bytes = new byte[1024];
    static List<Socket> list = new ArrayList<>();
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        //阻塞
        serverSocket.setBlock(fasle);//伪代码 不阻塞
        serverSocket.bind(new InetSocketAddress(8080));

        while (true) {
            System.out.println("等待连接");
            //阻塞网络连接
            Socket socket = serverSocket.accept();

            //不管有没有人连接都需要判断当前的socket是否有发送数据

            if (socket == null) {
                //继续循环

            } else {
                //有连接
                System.out.println("连接成功");
                list.add(socket);
                socket.setBlock(fasle);//伪代码 不阻塞
            }

            for (Socket s:
                 list) {

                System.out.println("等待数据");
                //read也会阻塞
                int read = s.getInputStream().read(bytes);
                if (read == 0) {
                    //没有数据
                } else {
                    //有数据处理数据
                    System.out.println("接收到数据");
                    String content = new String(bytes);

                    System.out.println(content);
                }
            }

//            Thread thread = new Thread(new ExecuteSocket(socket));
//            thread.run();
        }
    }

NIO 的实现

public class QQNIOService {
    public static void main(String[] args) {
        List<SocketChannel> list = new ArrayList<>();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        try {
            //对应BIO中的ServerSocket
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(9091));
            serverSocketChannel.configureBlocking(false);//非阻塞

            while (true) {
                Selector selector = Selector.open();
                //对应BIO中的Socket
                SocketChannel socketChannel = serverSocketChannel.accept();

                if (socketChannel == null) {
                    Thread.sleep(1000);
                    System.out.println("没人连接");

                    //得到Socket,循环,读取数据
                    for (SocketChannel channel :
                            list) {
                        int k = channel.read(byteBuffer);
                        if (k != 0) {
                            byteBuffer.flip();
                            System.out.println(new String(byteBuffer.array()));
                        }
                    }
                } else {
                    socketChannel.configureBlocking(false);
                    list.add(socketChannel);
                    System.out.println("有人连接");
                    //得到Socket,循环,读取数据
                    for (SocketChannel channel :
                            list) {
                        int k = channel.read(byteBuffer);
                        if (k != 0) {
                            byteBuffer.flip();
                            System.out.println(new String(byteBuffer.array()));
                        }
                    }
                }
            }

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

关于Linux

Linux中只要你是文件,就可以通过系统函数read,write。

把ServerSocket当成一种文件,当客户端write了数据,则服务端只要read就可以读到内容。

一共两个SOCKET,也就是两个文件。

  1. 两个操作系统之间建立连接,但是不代表发了数据。连接和发数据是两个过程。如下面的代码。
public class QQClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",  8080);
//        socket.connect(new InetSocketAddress(8080));
        //这种方式不便于理解阻塞
//        socket.getOutputStream().write("111".getBytes());
        //模拟发数据
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入内容");
        while (true) {
            String next = scanner.next();
            socket.getOutputStream().write(next.getBytes());
        }
    }
}

Select

循环list的功能就是判断这些socket有没有数据发送过来。

把list扔给select,让select循环,不在java层循环。


  转载请注明: Weslyxl NIO基础

 上一篇
数据结构——红黑树 数据结构——红黑树
简介红黑树的目的是为了高效地进行增删改查,本质是一种二叉查找树,其次也是一种完美平衡二叉树。所以本文从二叉树,完美平衡二叉树开始介绍。 除了红黑树,后面也会陆续介绍其他几种在实际工作中常用到的树。 AVL树,平衡二叉树 B/B+树,用在磁
2019-11-08
下一篇 
JVM内存模型 JVM内存模型
JVM内存模型ClassLoader JVM运行时数据库 执行引擎 GC垃圾回收 JVM运行时数据区堆 栈 方法区 java堆区 java栈区 本地方法区 程序计数区 区分:JVM内存模型及java内存模型 java虚拟机:hotspo
  目录