网络IO

作者: 墨线宝 | 来源:发表于2023-11-07 10:08 被阅读0次

    网络IO

    阻塞模型

    在之前网络通信都是阻塞模型

    • 客户端向服务端发出请求后,客户端会一直处于等待状态,直到服务器端返回结果或网络出现问题
    • 服务器端也是如此,在处理某个客户端A发来的请求时,另一个客户端B发来的请求会等待,直到服务器端的处理线程线程上一个请求的处理

    在服务端使用ServerSocket来建立套接字,accept方法会进行阻塞等待客户端的连接

    try(
            // 创建一个ServerSocket对象
            ServerSocket serverSocket = new ServerSocket(9090);
            // accept方法,返回Socket对象,这里会进行阻塞,应用程序向操作系统请求接收已准备好的客户端连接的数据信息
            Socket s = serverSocket.accept();
            // 获取输入流,这里读取数据也会阻塞
            InputStream is = s.getInputStream();
            // 输出流,给客户端返回消息
            OutputStream os = s.getOutputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader reader = new BufferedReader(isr);
    ){
        String str;
        while ((str = reader.readLine()) != null){
            System.out.print(str);
        }
        os.write("我已收到消息".getBytes());
        
    } catch (IOException e){
        e.printStackTrace();
    }
    

    serverSocket.accept()阻塞

    服务器端发起一个accept动作,询问操作系统是否有新的Socket套接字信息从端口发送过来,如果没有则serverSocket.accept()会一直等待

    阻塞模型的问题:

    • 同一时间,服务器只能接收一个客户端的请求信息,第二个客户端需要等待服务器接收完第一个请求数据后才会被接收
    • 服务器一次只能处理一个客户端请求,处理完成并返回后才能进行第二次请求的处理

    多线程阻塞模型

    由于阻塞模型的弊端,高并发时会导致请求太慢,所以提出了使用多线程来解决上述阻塞问题

    • 服务器收到客户端A的请求后,开启线程去进行数据处理。主线程可以继续接收客户端B的请求

    但是这样在进行serverSocket.accept();操作时还是单线程运行,只有业务处理才会使用多线程,对于接收数据的并发能力并没有提升

    同步非阻塞模型

    这里先说一下同步和非同步的概念

    同步和非同步是操作系统级别的,主要描述操作系统在收到程序请求网络IO操作后,如果网络IO资源没有准备好,该如何响应程序

    • 同步IO不响应程序,直到网络IO资源准备好
    • 非同步IO返回一个标记,当网络IO资源准备好后,用事件机制通知给程序

    再说一下阻塞和非阻塞的概念

    阻塞和非阻塞是程序级别的,主要描述程序请求操作系统IO操作后,如果网络IO资源没有准备好,程序如何处理

    • 阻塞IO会进行等待
    • 非阻塞IO会继续执行,且使用线程一直轮询,直到IO资源准备好
    {
            boolean flag = true;
            try {
                ServerSocket serverSocket = new ServerSocket(6666);
                // 使用超时时间来设置为非阻塞状态,超过该时间会抛出SocketTimeoutException
                serverSocket.setSoTimeout(100);
    
                while (true){
                    Socket socket = null;
                    try{
                        // 设置了超时时间后accept就不会阻塞了
                        socket  = serverSocket.accept();
                    } catch (SocketTimeoutException e){
                        synchronized (obj){   // 100ms内没有接收到任何数据,可以在这里做一些别的操作
                            System.out.println("没接收到数据,先歇一歇吧");
                            try {
                                obj.wait(10);
                            } catch (InterruptedException interruptedException) {
                                interruptedException.printStackTrace();
                            }
                        }
                        continue;
    
                    }
                  // 开线程处理数据
                    new Thread(socket).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    serverSocket.setSoTimeout可以使accept方法不一直阻塞,而是到了超时时间后抛出SocketTimeoutException异常,此时就可以用主线程做别的事情了,虽然实际还是使用的accept阻塞模型,但是有所改善

    多路复用模型

    多路复用模型(也就是NIO)不在使用操作系统级别的同步IO,目前主要实现有select、poll、epoll、kqueue

    {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 绑定8080端口
        serverSocketChannel.bind(new InetSocketAddress(8080));
    
        // 注册监听的事件
        // ServerSocketChannel只能注册OP_ACCEPT
        // SocketChannel可注册OP_READ、OP_WRITE、OP_CONNECT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
    
        while(true){
            // 询问selector中准备好的事件
            selector.select();
            // 获取到上述询问拿到的事件类型
            Set<SelectionKey> selectionKeys =  selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                if(selectionKey.isAcceptable()){
                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                    // 接收到服务端的请求
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    sc.register(selector,SelectionKey.OP_READ);
                    // 处理过了就要移除掉,否则再次select()还会拿到该事件
                    iterator.remove();
                } else if(selectionKey.isReadable()){
                    SocketChannel sc = (SocketChannel) selectionKey.channel();
                    byteBuffer.clear();
                    int n = sc.read(byteBuffer);
                    if(n > 0){
                        byteBuffer.flip();
                        Charset charset = StandardCharsets.UTF_8;
                        String message = String.valueOf(charset.decode(byteBuffer).array());
                        System.out.println(message);
                    }
                    sc.register(selector,SelectionKey.OP_WRITE);
                    iterator.remove();
                } else if(selectionKey.isWritable()){
                    SocketChannel sc = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    buffer.put("已接收到消息".getBytes());
                    buffer.flip();
                    sc.write(buffer);
                    iterator.remove();
                }
            }
        }
    
    }
    

    多路复用显然绕过了accept方法的阻塞问题,使得操作系统可以在一个端口上能够同时接收多个客户端的IO事件

    https://zhhll.icu/2022/java基础/IO/4.网络IO/

    本文由mdnice多平台发布

    相关文章

      网友评论

          本文标题:网络IO

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