NIO source code analysis (03) from BIO to NIO

NIO source code analysis (03) from BIO to NIO

Netty series catalog ( https://www.cnblogs.com/binarylei/p/10117436.html )

A, NIO three major components Channels, Buffers, Selectors

1.1 Channel 和 Buffer

Basically, all of the IO are beginning from a Channel in the NIO. Channel bit like flow. Data can be read from Channel Buffer can be written in the Buffer Channel. Here it is shown:

Channel 和 Buffer

Summary: Channel and in NIO Buffer is not a new thing: the essence Channel is Socket, nature Buffer is byte []. In the era of BIO, BufferedInputStream is a buffered stream.

1.2 Selector

Selector allows a plurality of single thread Channel. If your application opens multiple connections (channels), but each traffic connections are very low, use Selector comes in handy. For example, in a chat server.

This is a graphical representation of a three Channel Selector processing in a single thread:

Selector

Summary: Selector is at the core of NIO, with the Selector model, a thread can handle multiple Channel up.

1.3 Linux difference between IO and NIO programming

(1) Linux IO network programming

int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(listenfd, BACKLOG);

socklen_t cliaddr_len = sizeof(client_addr);
int clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &cliaddr_len);

(2) Linux NIO Network Programming

int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(listenfd, BACKLOG);

// select 模型处理过程  
// 1. 初始化套接字集合,添加监听 socket 到这个集合
FD_ZERO(&totalSet);
FD_SET(listenfd, &totalSet);
maxi = listenfd;

while(1) {
    // 2. 将集合的一个拷贝传递给 select 函数。当有事件发生时,select 移除未决的 socket 然后返回。
    //    也就是说 select 返回时,集合 readSet 中就是发生事件的 readSet
    readSet = totalSet;
    int nready = select(maxi + 1, &readSet, NULL, NULL, NULL);
    if (nready > 0) {
        if (FD_ISSET(listenfd, &readSet)) {
            cliaddr_len = sizeof(cliaddr);
            connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &cliaddr_len);
            printf("client IP: %s\t PORT : %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

            FD_SET(connfd, &totalSet);
            maxi = connfd;
            if (--nready == 0) {
                continue;
            }
        }
    }
}

Summary: comparison of Linux IO and NIO network programming can be found, NIO relatively BIO extra part is actually select part, and the rest (including the socket is created, data is read, etc.) are the same. So I said Channel and Buffer is converted JDK conceptual level, the core is the NIO Selector, the next source code analysis NIO Selector will pay more attention to the model, and Channel Buffer superficial.

Two and distinguishing, BIO and the NIO

2.1 BIO stream oriented, NIO facing buffer.

The first biggest difference is between Java NIO and IO, IO is a stream-oriented, NIO is facing the buffer.

Java IO stream means for each read from the stream one or more bytes, until all bytes are read, they are not cached anywhere. In addition, it can not be moved before and after the data stream. If the mobile data read from the stream before and after the need, it is required first to a buffer cache.

The method of Java NIO buffer guide is slightly different. A read data buffer to its later processing, movable back and forth in the buffer when required. This increases the flexibility of the process. However, you also need to check whether the buffer contains all the data you need to be addressed. Also, make sure that as more data is read into the buffer, do not overwrite the data in the buffer has not been processed.

Stream-oriented, for the buffer zone, which is the concept of Java, and operating system independent.

(1) Java IO 【SocketInputStream】

/**
 * Java IO 直接对读取的数据进行操作
 * 1. socketRead(native) 函数中获取相当长度的数据,然后直接对这块数据进行了操作
 */
int read(byte b[], int off, int length, int timeout) throws IOException {
    
    // acquire file descriptor and do the read
    FileDescriptor fd = impl.acquireFD();
    try {
        // native函数,这里从内核态中读取数据到数组 b 中
        n = socketRead(fd, b, off, length, timeout);
        if (n > 0) {
            return n;
        }
    } catch (ConnectionResetException rstExc) {
    } finally {
        impl.releaseFD();
    }
}

(2) Java NIO 【DatagramChannelImpl】

/**
 * Java NIO 每次读取的数据放在该内存中,然后对该内存进行操作,增加了处理数据的灵活性
 * 1. Util.getTemporaryDirectBuffer(newSize) 申请了一块堆外内存
 * 2. receiveIntoNativeBuffer(native) 将数据读取到堆外内存中
 * 3. dst.put(bb) 将数据从该内存中读取到内存块 dst 中
 * 4. dst 就一个共享的内存块,可以对该内存进行各种操作,但也要注意一些问题,如数据覆盖
 */
private int receive(FileDescriptor fd, ByteBuffer dst)
    throws IOException {
    int pos = dst.position();
    int lim = dst.limit();
    assert (pos <= lim);
    int rem = (pos <= lim ? lim - pos : 0);
    if (dst instanceof DirectBuffer && rem > 0)
        return receiveIntoNativeBuffer(fd, dst, rem, pos);

    // 申请一块 newSize 大小的缓冲区块  
    int newSize = Math.max(rem, 1);
    ByteBuffer bb = Util.getTemporaryDirectBuffer(newSize);
    try {
        // 数据读取到缓冲区中,buffer 可以做标记,操作指针等  
        int n = receiveIntoNativeBuffer(fd, bb, newSize, 0);
        bb.flip();
        if (n > 0 && rem > 0)
            dst.put(bb);
        return n;
    } finally {
        Util.releaseTemporaryDirectBuffer(bb);
    }
}

Can be seen, the first code-time data acquired from the considerable length native function, and then this data directly to the operation.

Code and the second piece Util.getTemporaryDirectBuffer (newSize); application of an external heap memory, each data read in the memory and the memory for operation.
General statement cache for stream-oriented with respect to the benefits of increased flexibility in that the process data, of course, increases the complexity of the operation, such as when more data is taken in, the whether or overwrite the previous data


The intentions of recording a little bit every day. Perhaps the content is not important, but the habit is very important!

Guess you like

Origin www.cnblogs.com/binarylei/p/11145070.html