美文网首页
BIO,NIO,AIO

BIO,NIO,AIO

作者: 念䋛 | 来源:发表于2021-09-22 22:13 被阅读0次

    BIO介绍
    同步阻塞模型,一个客户端需要一个线程来处理,服务端处理客户端信息是阻塞的,如果客户端通讯慢,那服务端的线程就一直不会释放,造成资源的浪费,适用于通讯不多的场景,不过代码简单易于维护,一个线程对应一个通讯,执行效率高,不像NIO需要轮询的来处理通讯.
    客户端

    public class SocketClient {
        public static void main(String[] args) throws IOException {
            //创建链接,创建链接之后,服务端的serverSocket.accept ();阻塞结束
            Socket socket = new Socket ("127.0.0.1",9000);
            System.out.println ("开始发送数据");
            OutputStream outputStream = socket.getOutputStream ();
            //向客户端发送消息,服务端read阻塞结束
            outputStream.write ("hellosocketClient".getBytes ());
            outputStream.flush ();
            System.out.println ("开始读取数据");
            byte[] bytes = new byte[1024];
            InputStream inputStream = socket.getInputStream ();
            inputStream.read (bytes);
            System.out.println ("服务端返回消息"+new String (bytes));
            socket.close ();
        }
    }
    
    

    服务端

    public class SocketServer {
        public static void main(String[] args) throws IOException {
            //server端socket
            ServerSocket serverSocket = new ServerSocket (9000);
            //无限循环,服务端一直处理请求
            while (true) {
                System.out.println ("等待链接");
                //阻塞,等待客户端链接,new Socket ("127.0.0.1",9000);实例化之后代表建立链接,此阻塞结束
                Socket socket = serverSocket.accept ();
                System.out.println ("客户端链接成功");
                //使用多线程处理请求,需要考虑线程安全问题,如果使用单线程,性能低
                new Thread (() -> {
                    try {
                        handler (socket);
                    } catch (IOException e) {
                        e.printStackTrace ();
                    }
                }).start ();
            }
        }
        private static void handler(Socket socket) throws IOException {
            //inputStream 获取输入信息
            byte[] bytes = new byte[1024];
            InputStream inputStream = socket.getInputStream ();
            //read方法阻塞方法,等待客户端发送消息
            int read = inputStream.read (bytes);
            System.out.println ("服务端接受消息"+new String (bytes,0 ,read));
            //outputStream 服务端返回信息
            OutputStream outputStream = socket.getOutputStream ();
            outputStream.write ("hellosocketServer".getBytes ());
            outputStream.flush ();
        }
    }
      
    

    可以发现BIO(Blocking)服务端等待链接阻塞,等待信息传入也是阻塞,服务端只有一个线程来处理,首先阻塞accept方法,等待被链接,链接之后创建一个线程处理通讯,如果同时一万个请求,那就要创建一万个线程,当然系统本身和tomcat容器对线程有限制,不会同时创建一万个线程,但是如果应用可能处理通讯不多,而且是通讯时间比较长可以考虑使用BIO

    NIO介绍
    同步非阻塞模型,服务端一个线程可以处理多个客户端的请求,适用于连接时间短,连接数据量大的场景,聊天室,弹幕系统.
    NIO三个核心组件 Channle(通道) Buffer(缓冲) Selector(多路复用器)
    Channel通道,类似管道,负责通讯
    Buffer 缓存区,每个通道对应一个buffer,
    Selector channel注册进selector, selector根据channel的发生的事件,交给线程去处理

    代码
    服务端

    /**
     * selector的多路复用器,解释
     *
     * 服务器a
     * 客户端b
     * 客户端c
     *
     * 首先将a链接事件注册进selector中,起名叫key-a
     * 当b链接到a时,被监听到,从selector的key集合返回满足条件的key-a
     * 处理key-a,并将b客户端注册进selector中并监听读事件 起名叫key-b
     * 此时selector中有key-a key-b
     *
     * 当很短的事件内同时发生了客户端c链接a,b发消息给a,c也发送消息给a
     * (假设很短的时间满足系统底层收集事件的时间,系统不是监听到一个事件就马上解除阻塞,会等很短的时间,如果这段时间有几个事件被监听到,就一起返回)
     * 那么selector就会返回key-a 和key-b
     * 遍历得到的key
     * 首先处理key-a的链接事件,将key-c的读事件注册进selector中
     * 再处理key-b的读事件
     * 处理结束后,由于while(true),马上就执行 selector.select ();等待监听,发现c发消息过来了
     * 处理key-c发过来的消息
     * 经过上面的流程发现,c链接并发送消息,不会马上的执行,会轮询key,如果有事件处理事件,而c的发消息事件是第二轮遍历的时候处理
     *
     */
    public class SocketServer {
        public static void main(String[] args) throws IOException, InterruptedException {
            ServerSocketChannel ssc = ServerSocketChannel.open ();
            //设置为非阻塞模式
            ssc.configureBlocking (false);
            //绑定9000端口
            ssc.bind (new InetSocketAddress (9000));
            //创建多路复用器
            Selector selector = Selector.open ();
            //将服务端的channel注册进selector中,可以看到返回的是SelectionKey,就是将这个key注册进selector中,该代码是注册服务端accept事件,关心的事客户端链接到服务端事件
            //一般在客户端链接之后,获取channel,将该channel也注册进selector中,并赋于读写事件
            SelectionKey register = ssc.register (selector, SelectionKey.OP_ACCEPT);
            while (true) {
                System.out.println ("等待事件被注册进selector");
                //轮询channel中selectionKey,第一次只有一个就是刚才注册进的OP_ACCEPT,也就是目前是等待被客户端注册进selection,阻塞状态
                //当多路复用器获取到事件的发生,比如注册了OP_ACCEPT事件,当有客户端链接过来的时候就会取消阻塞,向下执行
                selector.select ();
                System.out.println ("有客户端注册进selector");
                //当检测事件的发生,将发生事件的selectedKeys获取到,比如selector中有服务端链接事件的key和客户端的读写事件的key的集合,当监听到客户端链接或者客户端发过来消息的时候,返回对应key的集合
                //客户端链接过来返回服务端accept事件的key,客户端发消息过来返回客户端read事件的key,如果在很短时间同时发生就两个key同时返回
                //这里有个疑问就是 selector.select ();方法监听到事件发生的时候就会解除阻塞,那岂不是这里只能收集到一个key,这就涉及到系统底层原理,因为事件监听是系统完成的,当监听到一个事件发生的时候
                //并不会马上的解除阻塞,会等一会,时间非常的短,不会马上的解除阻塞,这就解释了这里用key的集合,
                //这里要区分 获取到的key集合并不是注册进selector的key集合, 获取到的集合是注册进selector的key集合,根据发生的事件筛选出来的key集合
                Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys ().iterator ();
                System.out.println ("共有多少个链接要处理" + selector.selectedKeys ().size ());
                while (selectionKeyIterator.hasNext ()) {
                    SelectionKey selectionKey = selectionKeyIterator.next ();
                    //防止事件的重复解析
                    selectionKeyIterator.remove ();
                    //处理key
                    handle (selectionKey);
                }
            }
        }
    
        private static void handle(SelectionKey selectionKey) throws IOException, InterruptedException {
            //客户端链接
            if (selectionKey.isAcceptable ()) {
                System.out.println (("有客户端链接进来"));
                //当客户端链接时,这里返回的服务端channel
                ServerSocketChannel ssc = ( ServerSocketChannel ) selectionKey.channel ();
                //这个方法也是阻塞的,但是既然能进到这里,证明就是客户端链接过来,所以下面方法虽说时阻塞,但是会马上执行,注意这里返回的是SocketChannel,就是客户端的channel
                //而且客户端与之对应也会生成一个channel
                SocketChannel channel = ssc.accept ();
                //设置非阻塞
                channel.configureBlocking (false);
                System.out.println ("链接执行结束");
                //将刚才获取到的客户端channel组策进selector中,当下一次客户端发消息的时候,selector就可以监听到了
                channel.register (selectionKey.selector (), SelectionKey.OP_READ);
                //这里的判断,就是再客户端的读事件注册进selector后,当客户端发送消息的时候就可以监听到
            } else if (selectionKey.isReadable ()) {
                System.out.println ("服务端读取客户端发送的消息");
                //获取客户端的channele
                SocketChannel socketChannel = ( SocketChannel ) selectionKey.channel ();
                //创建buffer
                ByteBuffer buffer = ByteBuffer.allocate (1024);
                int let = socketChannel.read (buffer);
                //读取buffer
                if (let != -1) {
                    System.out.println ("读取客户端的信息" + new String (buffer.array (), 0, let));
                }
                ByteBuffer byteBuffer = ByteBuffer.wrap ("helloClient".getBytes ());
                //channel是双向的,对于同一个客户端来说
                // 这个channel和else之前注册的channel是同一个对象,
                // 因为是双向的可以读也可以写,当客户端的selector注册了读事件,就可以监听到服务端返回的消息
                socketChannel.write (byteBuffer);
                //刚注册的是read事件,这里将channel赋予读写的事件监听
                selectionKey.interestOps (SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                socketChannel.close ();
            }
        }
    }
    
    

    客户端

    public class SocketClient {
        public Selector selector;
    
        public static void main(String[] args) throws IOException {
            SocketClient socketClient = new SocketClient ();
            socketClient.initClient ("127.0.0.1", 9000);
            socketClient.connect ();
        }
    
        private void initClient(String ip, int port) throws IOException {
            //创建客户端channel,注意区分服务端的ServerSocketChannel
            SocketChannel socketChannel = SocketChannel.open ();
            //非阻塞模式
            socketChannel.configureBlocking (false);
            //获取selector
            Selector open = Selector.open ();
            this.selector = open;
            //链接服务端
            socketChannel.connect (new InetSocketAddress (ip, port));
            //将客户端注册进selector中,这个channel和服务端监听到客户端链接服务端产生的channel是一对
            socketChannel.register (selector, SelectionKey.OP_CONNECT);
        }
    
        public void connect() throws IOException {
            while (true) {
                //阻塞,等带事件被监听,从而解除阻塞
                selector.select ();
                Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys ().iterator ();
                while (selectionKeyIterator.hasNext ()) {
                    SelectionKey selectionKey = selectionKeyIterator.next ();
                    selectionKeyIterator.remove ();
                    //如果通道是连接事件
                    if (selectionKey.isConnectable ()) {
                        SocketChannel channel = ( SocketChannel ) selectionKey.channel ();
                        //如果链接正在进行中
                        if (channel.isConnectionPending ()) {
                            //完成链接
                            channel.finishConnect ();
                        }
                        channel.configureBlocking (false);
                        //创建缓冲区
                        ByteBuffer byteBuffer = ByteBuffer.wrap ("HelloNIOServer".getBytes ());
                        channel.write (byteBuffer);
                        //让通道注册进selector中,赋值read事件,用来读取服务端发过来的消息
                        channel.register (selector, SelectionKey.OP_READ);
                        //读取服务端返回的消息
                    } else if (selectionKey.isReadable ()) {
                        SocketChannel channel = ( SocketChannel ) selectionKey.channel ();
                        //创建缓冲区
                        ByteBuffer byteBuffer = ByteBuffer.allocate (1024);
                        int len = channel.read (byteBuffer);
                        if (len != -1) {
                            System.out.println ("客户端接收到消息" + new String (byteBuffer.array (), 0, len));
                        }
                    }
                }
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:BIO,NIO,AIO

          本文链接:https://www.haomeiwen.com/subject/wgoggltx.html