网络IO

网络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 多平台发布

posted @ 2023-11-08 10:09  拾光师  阅读(6)  评论(0编辑  收藏  举报  来源