zoukankan      html  css  js  c++  java
  • 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才会对服务器造成很大的压力,适合处理请求数量较小的客户端请求。
  • 相关阅读:
    服务器控件的 ID,ClientID,UniqueID 的区别
    GridView使用总结
    javascript对象
    如何对SQL Server 2005进行设置以允许远程连接(转载)
    Master Pages and JavaScript document.getElementById
    Linux paste命令
    linux脚本和代码的加密
    SDWAN的优势
    dsd
    ASA防火墙忘记密码之后的恢复步骤
  • 原文地址:https://www.cnblogs.com/micadai/p/12210313.html
Copyright © 2011-2022 走看看