对于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才会对服务器造成很大的压力,适合处理请求数量较小的客户端请求。
来源:https://www.cnblogs.com/micadai/p/12210313.html