java BIO(阻塞IO,即传统IO)分析

对于java 传统的BIO来说,究竟存在哪些缺点呢?
首先需要理解的是,对于传统的java io来说,总体上是一个连接一个线程,都会说这样的服务器处理方式效率不高而且浪费资源,那么究竟是怎么回事儿呢?

源码地址:https://github.com/50mengzhu/learnIo

解读一下BIO的流程——

  • 首先由服务器端开启一个Socket监听固定端口,等待客户端连接
  • 等到和客户端线程建立连接,从连接中的数据流中等待读取数据
  • 客户端下线之后,服务器的线程随之终止
package com.dx.bio;
import static com.dx.io.NetConstants.NET_BUFFER;
import static com.dx.io.NetConstants.SERVER_PORT;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
 * Bio 的一个服务端。
 *
 * @author daixiao
 */
public class BioServer {
    /** 日志记录对象。 */
    private static Log log = LogFactory.getLog(BioServer.class);
    public static void main(final String[] args) {
        // 创建一个线程池
        ThreadFactory factory = new ThreadFactoryBuilder()
                .setNameFormat("nio-pool-test-%d").build();
        ExecutorService pool = new ThreadPoolExecutor(5, 200,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<Runnable>(NET_BUFFER), factory);
        // 创建一个线程响应客户端
        try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)) {
            log.info(String.format("server is listening on port %d", SERVER_PORT));
            while (true) {
                // 针对进来连接的客户端进行处理
                final Socket socket = serverSocket.accept();
                log.info(String.format("a client connect to server: %s:%d",
                        socket.getInetAddress(), socket.getLocalPort()));
                pool.execute(new Runnable() {
                    @Override
                    public void run() {
                        handleMsg(socket);
                    }
                });
            }
        } catch (IOException e) {
            log.warn("create server failed OR accept client failed", e);
        }
    }
    /**
     * 处理客户端信息的方法
     *
     * @param socket 客户端的 socket
     */
    public static void handleMsg(Socket socket) {
        log.info(String.format("current pid is %s, and thread name is %s",
                Thread.currentThread().getId(), Thread.currentThread().getName()));
        byte[] buffer = new byte[NET_BUFFER];
        try (InputStream inputStream = socket.getInputStream()) {
            while (true) {
                if (inputStream.read(buffer) != -1) {
                    log.info(new String(buffer, 0, buffer.length, StandardCharsets.UTF_8));
                } else {
                    break;
                }
            }
        } catch (IOException e) {
            log.warn(String.format("require input stream from client %s:%d failed!",
                    socket.getInetAddress(), socket.getLocalPort()), e);
        } finally {
            log.info(String.format("close client %s:%d socket!",
                    socket.getInetAddress(), socket.getLocalPort()));
            try {
                socket.close();
            } catch (IOException e) {
                log.warn(String.format("close client %s:%d socket failed!",
                        socket.getInetAddress(), socket.getLocalPort()), e);
            }
        }
    }
}

代码中的实现——首先是由服务器端创建一个ServerSocket等待客户端连接(注意此时就存在一个等待也就是serverSocket.accpet这个方法会一直阻塞等待客户端线程的连接),和客户端建立连接之后服务器端就会等待客户端的数据(此时是socket.getInputStream.read这个方法会一直阻塞等待客户端线程传送数据)。

/**
* Listens for a connection to be made to this socket and accepts
* it. The method blocks until a connection is made.
*
* <p>A new Socket {@code s} is created and, if there
* is a security manager,
* the security manager's {@code checkAccept} method is called
* with {@code s.getInetAddress().getHostAddress()} and
* {@code s.getPort()}
* as its arguments to ensure the operation is allowed.
* This could result in a SecurityException.
*
* @exception  IOException  if an I/O error occurs when waiting for a
*               connection.
* @exception  SecurityException  if a security manager exists and its
*             {@code checkAccept} method doesn't allow the operation.
* @exception  SocketTimeoutException if a timeout was previously set with setSoTimeout and
*             the timeout has been reached.
* @exception  java.nio.channels.IllegalBlockingModeException
*             if this socket has an associated channel, the channel is in
*             non-blocking mode, and there is no connection ready to be
*             accepted
*
* @return the new Socket
* @see SecurityManager#checkAccept
* @revised 1.4
* @spec JSR-51
*/
public Socket accept() throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!isBound())
        throw new SocketException("Socket is not bound yet");
    Socket s = new Socket((SocketImpl) null);
    implAccept(s);
    return s;
}

查看源码中accept的注释——The method blocks until a connection is made,也就是说如果没有连接那么这个方法将会一直阻塞在这里。还有一个阻塞的地方——

/**
* Reads some number of bytes from the input stream and stores them into
* the buffer array <code>b</code>. The number of bytes actually read is
* returned as an integer.  This method blocks until input data is
* available, end of file is detected, or an exception is thrown.
*
* <p> If the length of <code>b</code> is zero, then no bytes are read and
* <code>0</code> is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at the
* end of the file, the value <code>-1</code> is returned; otherwise, at
* least one byte is read and stored into <code>b</code>.
*
* <p> The first byte read is stored into element <code>b[0]</code>, the
* next one into <code>b[1]</code>, and so on. The number of bytes read is,
* at most, equal to the length of <code>b</code>. Let <i>k</i> be the
* number of bytes actually read; these bytes will be stored in elements
* <code>b[0]</code> through <code>b[</code><i>k</i><code>-1]</code>,
* leaving elements <code>b[</code><i>k</i><code>]</code> through
* <code>b[b.length-1]</code> unaffected.
*
* <p> The <code>read(b)</code> method for class <code>InputStream</code>
* has the same effect as: <pre><code> read(b, 0, b.length) </code></pre>
*
* @param      b   the buffer into which the data is read.
* @return     the total number of bytes read into the buffer, or
*             <code>-1</code> if there is no more data because the end of
*             the stream has been reached.
* @exception  IOException  If the first byte cannot be read for any reason
* other than the end of the file, if the input stream has been closed, or
* if some other I/O error occurs.
* @exception  NullPointerException  if <code>b</code> is <code>null</code>.
* @see        java.io.InputStream#read(byte[], int, int)
*/
public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}

在服务端的线程中存在以下几个问题——

  • 服务端线程在读取数据的时候,如果流中目前不存在数据,那么服务端处理线程将会空等
  • 操作系统切换线程也需要耗费大量的时间
  • 在进行网络数据传输的时候,采用的是流的方式,效率不高
        因此基于以上的两个原因,传统的IO才会对服务器造成很大的压力,适合处理请求数量较小的客户端请求。
posted @ 2020-01-18 21:33  Mica_Dai  阅读(632)  评论(0编辑  收藏  举报