首页 > 编程语言 > 详细

java --- IO/NIO介绍,网络编程模型

时间:2020-08-20 16:35:07      阅读:85      评论:0      收藏:0      [点我收藏+]

1、阻塞I/O模型

  阻塞I/O模型是常见的I/O模型,在读写数据时客户端会发生阻塞。阻塞I/O模式的工作流程为:当用户发出I/O请求之后,内核会检查数据是否就绪,此时用户线程会一直阻塞等待内存数据就绪,在内存数据就绪后,内核将数据复制到用户线程中,并返回I/O执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据。

      典型的阻塞I/O模型的如下:

      data=socket.read()

 

2、非阻塞I/O模型

  非阻塞I/O模型是指用户在发起一个I/O操作后,无需阻塞便马上可以得到内核返回的结果。如果内核返回的结果为false,则表示内核数据还没准备好,需要稍后再发起I/O操作。一旦内核中数据准备好了,并且再次收到用户的请求,内核就会立刻将数据复制到用户线程中并将结果通知用户线程。

      典型的非阻塞I/O模型如下:

1     while (true){
2             data = socket.read();
3             if(data == true){
4                 //获取并处理内核数据,通知用户线程
5                 break;
6             }else {
7                 //数据没有准备好,此时处理用户线程其他实际
8             }
9         }

3、多路复用I/O模式

  多路复用模型是多线程并发编程用得比较多的模型,java NIO就是基于多路复用I/O模型实现的。在多路复用I/O模型中会有一个被称为selector的线程不断轮询多个socket的状态,只有在socket有读写事件时,才会通知用户线程进行I/O读写操作。

  因为在多路复用I/O模型中只需要一个selector线程就可以管理多个socket,并且只有在真正有socket读写操作的时候才会使用操作系统的I/O资源,大大节约了系统资源。

  java NIO在用户的每个线程中都通过selector.select()查询当前通道是否有事件到达,如果没有,用户线程会一直阻塞。而多路复用I/O模型通过一个线程管理多个socket通道,在socket有读写事件触发的时候才会通知用户线程进行I/O读写操作。

  非阻塞I/O模型在每个用户线程中都进行socket状态检查,而多路复用I/O模型中是在系统内核中进行socket状态检查,这也是多路复用I/O模型比非阻塞I/O模型效率高的原因。

  多路复用I/O模型通过在一个selector线程上以轮询的方式检测在多个socket上是否有事件到达,并啄个进行事件处理和响应。因为对于多路复用I/O模型来说,在事件响应体很大时,selector线程就会成为性能瓶颈,导致后续的事件迟迟得不到处理,影响下一轮的事件轮询。在事件应用中,在多路复用方法体内一般不建议做复杂逻辑运算,只做数据的接收和转发。

4、信号驱动I/O模型

  在信号驱动I/O模型中,在用户发起一个I/O请求操作时,系统会为该请求对应的socket注册一个信号函数,然后用户线程可以继续执行其他业务逻辑,在内核数据就绪时,系统会发送一个信号到用户线程,用户线程收到信号时,会在信号函数中调用对应的I/O读写操作完成实际的I/O请求操作。

5、异步I/O模型

  在异步I/O模型中,用户线程会发起一个asynchronous read操作到内核,内核在接收到请求后会立马返回一个状态,来说明请求是否成功发起,在此过程中用户线程不会发生任何阻塞。接着,内核会等待数据准备完成并将数据复制到用户线程中,在复制完成后会发个信号给用户线程,通知用户线程asynchronous读操作已完成。在异步I/O模型中,用户线程不需要关心整个I/O操作是如何进行的,只需要发一个请求,在接收到内核返回的成功或者失败时,说明I/O请求已经完成,直接使用数据即可。

6、java I/O

  具体使用参考JDK api

7、Java NIO

  Java NIO 的主要实现涉及三大核心内容:Selector、Channel、Buffer。Selector用于监听多个Channeld的事件,比如链接打开或数据到达,因此,一个线程可以实现对多个Channel的管理。传统的I/O是基于流的读写操作,而java NIO基于Channel和Buffer进行I/O读写操作,并且数据总是被从Channel读取到Buffer中,或者从Buffer读取到Channel中。

  java NIO跟传统I/O最大的区别如下:

    1、I/O是面向流的,NIO是面向缓冲区的,在面向流的操作中,数据只能在一个流中连续进行读写,数据没有缓冲,因此字节流无法前后移动。而在NIO中每次都是将数据从一个Channel读取到一个Buffer中,再从Buffer读取到Channel中,因此可以方便的在缓冲区进行数据的前后移动等操作。该功能在应用层主要用于数据的粘包,拆包等操作,在网络不可靠的环境下尤为重要。

    2、传统I/O的流操作是阻塞的,NIO流的操作是非阻塞的。在传统的I/O下,用户线程在调用read()或write()时,用户线程将一直被阻塞,直到数据被完全读取或写入。NIO通过selector监听Channel上的事件变化,在Channel上有数据变化时,通知该线程进行数据的读写操作。对于读取操作而言,在Channel上有数据时,线程将进行读Buffer操作,在没有数据时,线程可以执行其他业务逻辑。对于写操作而言,在使用线程执行写操作将一些数据写入某channel时,只需将channel上的数据异步写入Buffer即可,Buffer上的数据会被异步写入目标Channel上,用户不需要等待整个数据被完全写入目标Channel就可以继续执行其他业务逻辑。

    技术分享图片

 

  1、Channel介绍:

    Channel和I/O中的Stream(流)类似,只不过Stream是单向的,而Channel是双向的,既可以操作读操作,也可以操作写操作。

  2、Buffer

    Buffer实际上是一个容器,其内部通过一个连续的字节数组存储I/O上的数据,在NIO中,Channel文件、网络上对数据的读取或写入都必须经过Buffer。

 技术分享图片

 

  3、Selector

    Selector用于检测在多个注册的Channel上是否有I/O事件发生,并检测到的I/O事件进行相应的响应和处理。因此通过一个Selector线程就可以实现对多个Channel的管理,不必为每个链接都创立一个线程,避免线程资源的浪费和多线程上下文切换导致的开销。同时,Selector只有在Channel上有读写事件发生时,才会调用I/O函数进行读写操作,可极大的减少开支,提高系统并发量。

  4、java NIO的使用

  MyServer类:

    

package cn.com.yitong.ares.A20200818;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class MyServer {
    private int size = 1024;
    private ServerSocketChannel serverSocketChannel;
    private ByteBuffer byteBuffer;
    private Selector selector;
    private int remoteClientNum = 0;
    public MyServer(int port){
        try {
            initChannel(port);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
    //channel初始化
    private void initChannel(int port) throws Exception{
        //打开channel
        serverSocketChannel = ServerSocketChannel.open();
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(port));
        System.out.println("port is :" + port);
        //选择器的创建
        selector = Selector.open();
        //向选择器注入通道
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //分配缓冲区的大小
        byteBuffer = ByteBuffer.allocate(size);
    }
    //监听器,用于监听channel上的数据变化
    private void listrner() throws Exception{
        while(true){
            //返回的int值表示有多个channel处于就绪状态
            int n = selector.select();
            if(n == 0){
                continue;
            }
            //每个selector 对应多个selectionKey,每个selectionKey对应一个channel
            Iterator<SelectionKey> iterable = selector.selectedKeys().iterator();
            while (iterable.hasNext()){
                SelectionKey key = iterable.next();
                //如果selectionKey处于就绪状态,则开始接收客户端的链接
                if(key.isAcceptable()){
                    //获取channel
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    //channel接收链接
                    SocketChannel channel = server.accept();
                    //channel注册
                    registerChannel(selector,channel,SelectionKey.OP_READ);
                    //远程客户端的链接数
                    remoteClientNum++;
                    System.out.println("online client num = " + remoteClientNum);
                    write(channel,"hello world".getBytes());
                }
                //如果通道已经处于就绪状态
                if(key.isReadable()){
                    read(key);
                }
                iterable.remove();
            }
        }

    }

    private void read(SelectionKey key) throws Exception{
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int count;
        byteBuffer.clear();
        //从通道中读取数据到缓冲区
        while ((count = socketChannel.read(byteBuffer)) >0){
            //byteBuffer写模式变为读模式
            byteBuffer.flip();
            StringBuffer stringBuffer = new StringBuffer();
            boolean firstLine = true;
            while(byteBuffer.hasRemaining()){
                if(firstLine){
                    firstLine = false;
                }else {
//                    stringBuffer.append("\n");
                }
                stringBuffer.append((char)byteBuffer.get());
                System.out.println("+++++++" + stringBuffer.toString());
            };
            System.out.println("+++++++" + stringBuffer.toString());
            byteBuffer.clear();
        }
        if(count < 0 ){
            socketChannel.close();
        }
    }

    private void write(SocketChannel channel, byte[] bytes) throws Exception{
        byteBuffer.clear();
        byteBuffer.put(bytes);
        byteBuffer.flip();
        //将缓冲区的数据写入通道中
        channel.write(byteBuffer);
    }

    private void registerChannel(Selector selector, SocketChannel channel, int opRead) throws Exception{

        if(channel == null){
            return;
        }
        channel.configureBlocking(false);
        channel.register(selector,opRead);
    }
    public static void main(String[] args){
        MyServer server = new MyServer(9000);
        try {
            server.listrner();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

   Myclient类:

 

package cn.com.yitong.ares.A20200818;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Myclient {
    private int size = 1024;
    private ByteBuffer byteBuffer;
    private SocketChannel socketChannel;

    public void connectServer() throws IOException {
        socketChannel = socketChannel.open();
        socketChannel.connect(new InetSocketAddress("10.2.189.172",9000));
        socketChannel.configureBlocking(false);
        byteBuffer = ByteBuffer.allocate(size);
        receive();
    }

    private void receive() throws IOException {
        while (true){
            byteBuffer.clear();
            int count;
            //如果没有数据,则read方法一直阻塞,直到读取到新的数据
            while ((count = socketChannel.read(byteBuffer)) > 0){
                byteBuffer.flip();
                while (byteBuffer.hasRemaining()){
                    System.out.println((char)byteBuffer.get());
                }
                send2Server("<head></head>".getBytes());
                byteBuffer.clear();
            }
        }
    }

    private void send2Server(byte[] bytes) throws IOException {
        byteBuffer.clear();
        byteBuffer.put(bytes);
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
    }
    public static void main(String[] args){
        try {
            new Myclient().connectServer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  此文本非原创!!

 

    

java --- IO/NIO介绍,网络编程模型

原文:https://www.cnblogs.com/xueyan4040/p/13531432.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!