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才会对服务器造成很大的压力,适合处理请求数量较小的客户端请求。