头图

IO model

Two stages of IO request (Linux)

  • IO call phase : The user process initiates a system call to the kernel
  • IO execution stage : At this time, the user waits for the IO request processing to complete and return. This stage is divided into two steps

    1. Wait for the data to be ready and write to the kernel buffer
    2. Data from the kernel buffer to the user mode buffer

      • Kernel mode: run operating system programs and operate hardware
      • User mode: Run user program

Five Linux IO models

1. Synchronous blocking IO (BIO)

The kernel can only process one request at the same time, divided into two stages (i.e. the aforementioned IO execution stage ):

  1. System call
  2. Data is read from the kernel buffer to the user buffer

Both operations are blocking, so other IO can only be processed after these two operations are completed.

2. Synchronous non-blocking IO (NIO)

Process requests will not wait all the time but there are dedicated threads to poll these IO processes for data, but there will be up and down switching caused by system calls during the polling process. If there are too many requests, there will be serious system performance. Consume

3.IO multiplexing

Multiplexer means plurality of data channels , multiplexing refers one or more fixed threads to process each Socket connection, select poll epoll are achieved IO multiplexing, a thread can select multiple The data status of each data channel solves the problem of NIO performance consumption of 061c0a88ca2a5a

-File descriptor fd

File descriptor is a non-negative integer in the form of an index value that points to the record table of files opened by the process maintained by the kernel for each process.

- select

This function will monitor 3 types of file descriptors, which are writefds , readfds , exceptfds calling the select function, it will block until the select has the above 3 descriptor files ready or timeout. Once a descriptor is ready, the program will be notified to perform the related Because select poll epoll are synchronous IO, they all need to be responsible for reading and writing after the event is ready. That is, select will block and listen to related events, and will not unblock until the read and write operations are processed or after the timeout. The number of files that a single process can monitor is limited.Linux generally defaults to 1024

int select(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

- poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};

pollfd uses a structure of 061c0a88ca2bd8 to conduct events that need to be monitored and events to occur. In addition, the number of file descriptors monitored by

- epoll

​ No polling, time complexity is O(1)

epoll_create create a whiteboard store fd_events
epoll_ctl for the kernel register or a new descriptor for a file descriptor changes state. The registered descriptors will be maintained on a red-black tree in the kernel
epoll_wait by callback kernel will be I / O ready descriptor added to a list in management, process calls epoll_wait() can get complete event descriptor

​ Two trigger modes:
​ LT: Horizontal trigger
​ When epoll_wait() detects the arrival of a descriptor event, it will notify the process of this event. The process may not process the event immediately. The next call to epoll_wait() will notify the process again. It is the default mode, and supports Blocking and No-Blocking at the same time.
​ ET: Edge trigger
​ Unlike the LT mode, the process must process the event immediately after notification.
​ The next time you call epoll_wait() , you will no longer be notified of the arrival of the event. greatly reduces the number of times the epoll event is triggered repeatedly, so the efficiency is higher than LT mode . No-Blocking is supported to avoid starvation of the task of processing multiple file descriptors due to a blocked read/blocked write operation of a file handle.

4. Signal-driven model

The signal-driven model is not commonly used, it is a semi-asynchronous IO. When the data is ready, the kernel will send a SIGIO message to the application process, and the process will then start reading and writing messages.

5. Asynchronous IO

The system call will return the result immediately, and then the reading and writing of the message will be completed asynchronously.

BIO

BIO-Block-IO block synchronous communication method

BIO :

Blocking\synchronization, BIO is very dependent on the network, the network speed is not good, the blocking time will be very long; each request is executed and returned by the program, which is a synchronization defect

BIO workflow:

  • Server start
  • Block waiting for client connection
  • Client connection
  • Monitor client content
  • Client disconnected
  • Back to the first step

BioServer

public class BioServer {
    public static void main(String[] args) {
        try {
            // 服务端绑定端口
            ServerSocket server  = new ServerSocket(9000);
            while (true) {
                // 创建一个Socket接收连接 - 当没有时阻塞
                Socket socket = server.accept();
                // 获取输入流
                InputStream inputStream = socket.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                String message;
                while (null != (message = reader.readLine())) {
                    System.out.println(message);
                }
                inputStream.close();
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BioClient

public class BioClient {
    public static void main(String[] args) {
        try {
            // 创建socket
            Socket socket  = new Socket("localhost",9000);
            // 获取Socket输出流
            OutputStream  outputStream = socket.getOutputStream();
            // 输出流
            outputStream.write("hello socket".getBytes());
            // 关闭
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Multithreading solve BIO obstruction ask problem

  • solved by , other threads can still process the requests sent by other clients. It avoids the problem of a request blocking that causes other client requests to wait all the time.
  • is still a problem with added to the server. The given fixed number of threads is 10, and there are 10 clients who have created a connection but no one sends a message. Then all 10 threads will be blocked, or some clients will not operate for a long time. Necessary resource occupation.

Multi-threaded BioServer code

public class BioServer {
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);
    public static void main(String[] args) {
        ServerSocket serverSocket;
        try {
            serverSocket  = new ServerSocket(9000);
            while (true){
                //new Thread(new BioHandler(serverSocket.accept()){}).start();
                executorService.execute(new BioHandler(serverSocket.accept()));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class BioHandler implements Runnable {
    private Socket  socket;

    public BioHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            InputStream input  = socket.getInputStream();
            BufferedReader reader  = new BufferedReader(new InputStreamReader(input));
            String m;
            while (null != (m = reader.readLine())){
                System.out.println(m);
            }
            input.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

NIO

java 1.4版本引入,给予缓冲区面 \ 向通道的io操作
bionio
Flow orientedBuffer-oriented
Blocking ioNon-blocking io
SynchronizeSynchronize
none Selector (Selector)

Buffer

buffer introduction

The buffer is a container of a specific data type, defined by the java.nio package, and all buffers are subclasses of the Buffer abstract class

Buffer is mainly used to communicate with the NIO channel. Data is read from the channel to the buffer, and then read from the buffer to the channel

Buffer is like a piece of data that can hold multiple data of the same type

Subclass

ByteBuffer CharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer

Basic attributes

1. capacity (capacity): means that the maximum capacity of the buffer cannot be modified once it is created
2. limit (limit): The , that is, the data behind limit is unreadable
3. position (position): the index of the next data to be read or written
4. flip: sets the position at this time to limit and the position to 0. Generally, the data is read from the inputChannel to the buffer, and then the buffer is flipped to read the data from the buffer outputChannel
5. mark and recovery (reset): The mark is an index, and a specific position is specified by Buffer.mark(), and the reset method can be used to restore to this position

public class BufferSample {
    public static void main(String[] args) {

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        System.out.println("capacity:" + buffer.capacity());
        System.out.println("limit:" + buffer.limit(10));
        System.out.println("position:" +  buffer.position());
        /**
         * 结果:
         * capacity:1024
         * limit:java.nio.HeapByteBuffer[pos=0 lim=10 cap=1024]
         * position:0
         */

        System.out.println("==============================");
        String str = "hello";
        buffer.put(str.getBytes());
        System.out.println("position:" +  buffer.position());
        /**
         * 结果:
         * position:5
         */

        System.out.println("==============================");
        System.out.println("pos 和 limit之间元素的个数:" + buffer.remaining());
        buffer.mark();
        buffer.put("oo".getBytes());
        System.out.println("reset前position:" +  buffer.position());
        buffer.reset();
        System.out.println("reset后position:" +  buffer.position());
        /**
         * 结果:
         * pos 和 limit之间元素的个数:5
         * reset前position:7
         * reset后position:5
         */

        System.out.println("==============================");
        buffer.rewind();
        System.out.println("position:" + buffer.position());
        /**
         * 结果:
         * position:0
         */

        System.out.println("==============================");
        byte[] dst = new byte[3];
        buffer.get(dst);
        System.out.println(new String(dst));
        System.out.println("position:" + buffer.position());
        /**
         * 结果:
         * hel
         * position:3
         */

        System.out.println("==============================");
        //将此时的position转为limit,并将position置为0 - 一般flip以后就是开始读取缓冲区类
        buffer.flip();
        System.out.println("capacity:" + buffer.capacity());
        System.out.println("limit:" + buffer.limit());
        System.out.println("position:" +  buffer.position());
        byte[] b = new byte[buffer.limit()];
        buffer.get(b,0,2);
        System.out.println(new String(b,0,2));
        /**
         * 结果:
         * capacity:1024
         * limit:3
         * position:0
         * he
         */
    }
}

direct/indirect buffer

  • Direct buffer: the program directly manipulates the physical mapping file
  • Indirect buffer: jvm-operating system-physical memory

Channel

Channel: similar to a stream, but the Channel cannot directly access the data and can only interact with the buffer

Channel body implementation class

1. FileChannel : channel used to read, write, and manipulate files
2. DataGramChannel : read the data channel in the network via UDP
3. SocketChannel : Read and write channel data through Tcp
4. ServerSocketChannel : It can monitor the newly entered Tcp connection and create a SocketChannel for each new connection

The class that provides the getChannel() method

1.FileInputStream
2.FileOutputStream
3.RandomAccessFile
4.Socket
5.ServerSocket
6.DataGramSocket

Channel direct transmission

1.transferFrom()
2.transferTo()

public class ChannelSimple {
    /**
     * 利用通道完成文件复制(非直接缓冲区)
     */
    public static void FileNoDirectBufferTest(){
        try {
            //创建输入输出流
            FileInputStream inputStream = new FileInputStream("../test.txt");
            FileOutputStream outputStream = new FileOutputStream("../test2.txt");
            //根据流获取通道
            FileChannel inputChannel = inputStream.getChannel();
            FileChannel outputChannel = outputStream.getChannel();
            //创建缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            //从通道读取数据到缓冲区
            while (-1 != inputChannel.read(byteBuffer)){
                //limit - position,position - 0
                byteBuffer.flip();
                //将缓冲区中的数据写出
                outputChannel.write(byteBuffer);
                byteBuffer.clear();
            }
            outputChannel.close();
            inputChannel.close();
            outputStream.close();
            inputStream.close();
        } catch (IOException  e) {
            e.printStackTrace();
        }
    }

    /**
     * 利用直接缓冲区完成文件复制(内存映射文件)
     * @throws IOException
     */
    public static void FileMpDirectBufferTest() throws IOException{
        //创建通道
        FileChannel inputChannel = FileChannel.open(Paths.get("../test.txt"), StandardOpenOption.READ);
        FileChannel outputChannel = FileChannel.open(Paths.get("../test2.txt"),StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.READ);

        //内存映射文件
        MappedByteBuffer inputBuffer = inputChannel.map(FileChannel.MapMode.READ_ONLY,0,inputChannel.size());
        MappedByteBuffer outputBuffer =  outputChannel.map(FileChannel.MapMode.READ_WRITE,0,inputChannel.size());

        //直接对缓冲区进行数据读写操作
        byte [] dst = new byte[inputBuffer.limit()];
        inputBuffer.get(dst);
        outputBuffer.put(dst);

        outputChannel.close();
        inputChannel.close();
    }

    /**
     * 利用直接缓冲区复制
     * @throws IOException
     */
    public static void FileDirectBufferTest() throws IOException {
        //创建通道
        FileChannel inputChannel = FileChannel.open(Paths.get("../test.txt"), StandardOpenOption.READ);
        FileChannel outputChannel = FileChannel.open(Paths.get("../test2.txt"),StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.READ);

        //inputChannel.transferTo(0,inputChannel.size(),outputChannel);
        //等同 上面的注释
        outputChannel.transferFrom(inputChannel,0,inputChannel.size());
        outputChannel.close();
        inputChannel.close();
    }
}

Decentralized read and aggregate write

  • Scatter reading (Scatter): Channel the data in one Buffer
  • Gathering write (Gather): write multiple Buffer to the same Channel
public class ScatterAndGather {
    public static void main(String[] args) {
        try {
            //创建输入输出流
            FileInputStream inputStream = new FileInputStream("../test.txt");
            FileOutputStream outputStream = new FileOutputStream("../test2.txt");
            //根据流获取通道
            FileChannel inputChannel = inputStream.getChannel();
            FileChannel outputChannel = outputStream.getChannel();
            //创建缓冲区
            ByteBuffer byteBuffer1 = ByteBuffer.allocate((int)inputChannel.size()/2);
            ByteBuffer byteBuffer2 = ByteBuffer.allocate((int)inputChannel.size()/2);

            ByteBuffer[] byteBuffers = new ByteBuffer[]{byteBuffer1,byteBuffer2};
            //从通道读取数据到缓冲区 - 分散写入
            while (-1 != inputChannel.read(byteBuffers)){
                for (ByteBuffer buffer:byteBuffers){
                    //limit - position,position - 0
                    buffer.flip();
                }
                //聚集写出
                for (ByteBuffer buffer:byteBuffers) {
                    //将缓冲区中的数据写出
                    outputChannel.write(buffer);
                    buffer.clear();
                }
            }
            outputChannel.close();
            inputChannel.close();
            outputStream.close();
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Selector

Selector generally called a selector, also known as a multiplexer. It is used to check whether one or more channels are readable\writable, so that one thread can manage multiple Channel

using Selector

Use fewer threads to process Channel , which can prevent performance consumption caused by context switching

Channel can be multiplexed

Channel that can be selected (multiplexed) are inherited from SelectableChannel

                         SelectableChannel
                               ||
                    AbstractSelectableChannel
                  ||           ||            ||
      DataGramChannel    SocketChannel     ServerSocketChannel

So FileChannel does not adapt to Selector , that is, it cannot be switched to non-blocking mode

Selector uses basic steps

1. Create Selector: Selector selector = Selector.open();
2. Set to non-blocking as:

`channel.configureBlocking(false);`

3. Register Channel to Selector :

/**
* 参数-1:要注册到的多路复用器
* 参数-2:是一个"interest集合",即要监听事件的集合(有以下四种)
* OP_CONNECT 连接    
* OP_ACEEPT  接收
* OP_READ    读
* OP_WRITE   写
*/
SelectionKey key = channel.register(selector,SelectionKey.OP_READ);
如果要监听多种事件如下:
SelectionKey key = channel.register(selector,SelectionKey.OP_CONNECT | SelectionKey.OP_READ); 

4. Then it is ready to connect | ready to receive | ready to read | ready to write

Selector main method

methoddescribe
Set<SelectKey> keys()Return all SelectionKey collections, on behalf of registered in this on the Selector Channel
Set<SelectKey> selectedKeys()Return the selected one (that is, the one with io operation) SelectionKey
int select()Monitor all registered Channel , if there is an operation that requires io, the corresponding selectKey added to the selectedKeys collection, and the number returned is the number of selected (with io operation) Channel . During this operation, only the selected ones are blocked. Channel quantity>=1 before returning
int select(timeout)There is a timeout period, Channel that has not been operated by io appears, and it will automatically return after the timeout appears
int selectNow()Return immediately without blocking
Selector wakeUp()Make the select() return immediately
void close()closure

SelectionKey main method

SelectionKey represents the relationship between Channel and Selector Channel to Selector will generate a SelectionKey

methoddescribe
int interestOps()Collection of events of interest boolean isInterested = interestSet & SelectionKey.OP_CONNECT ...
int readyOps()Get the channel ready for operation
SelectableChannel channel()Get registered channel
Selector selector()Get selector
boolean isConnectable()Check if there is a connection event ready in Channel
boolean isAcceptable()Check if there is a receive event ready in Channel
boolean isReadaable()Check if there is a read event ready in Channel
boolean isWriteable()Check if there is a write event ready in Channel
Object attach()Attach an object to SelectionKey , mainly for identification information
Object attachment()For registration information may also be in Channel attached registration information when SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
void cancel()Request to cancel the registration of this key's channel to its selector

NioServer

public class NioServer {
    public static void main(String[] args) throws IOException {
        Integer flag = 0;
        //创建服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9021));
        //创建选择器
        Selector selector = Selector.open();
        //注册 接收
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //有一个事件时就操作
        while (selector.select() > 0) {
            //获取事件集合
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //如果是接收就绪
                if (selectionKey.isAcceptable()) {
                    //获取客户端连接
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //切换成非阻塞
                    socketChannel.configureBlocking(false);
                    //注册在多路复用器上 读
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    //读事件
                } else if (selectionKey.isReadable()) {
                    //获取客户端连接
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    //设置缓存
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int len = 0;
                    while (-1 != (len = socketChannel.read(byteBuffer))) {
                        flag = 0;
                        byteBuffer.flip();
                        System.out.println(new String(byteBuffer.array(),0,len));
                        byteBuffer.clear();
                    }
                    flag++;
                    //判断此时是否有io事件,陷入空轮询 - 连续空轮询100次
                    //请求取消此键的通道在其选择器的注册,也就是 selector.select();的数量 -1
                    if(flag == 100){
                        selectionKey.cancel();
                        socketChannel.close();
                    }
                }
            }
            iterator.remove();
        }
    }
}

NioClient

package com.yuan.nio.selector;

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

public class NioClient {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost",9021));

            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put("hello".getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);

            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

eacape
208 声望8 粉丝

JAVA 攻城狮