JDK1.0到JDK3.0中,Java IO类库中很多Unix网络编程中很多高级特性和接口都没有实现,如Pipe、Channel、Buffer和Selector等。
JDK1.4中,NIO以JSR-51的身份随JDK发布,其中增加了java.nio包。JDK1.7中对于原有的NIO类库进行了升级,被称为NIO2.0。
BIO通信
采用BIO模型的服务端,通常是用一个Acceptor线程监听客户端的连接,收到客户端请求后为每个客户端创建一个新的服务器端线程进行请求处理,处理完后将结果返回给客户端,销毁服务器端线程。
缺点:缺乏弹性伸缩能力。当客户端并发上升,服务器端线程会跟客户端线程成1:1的关系,导致服务端系统性能下降,最终导致系统宕机或奔溃。
public class BioClient {
public static void main(String[] args) {
int port = 8888;
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket =
new Socket("127.0.0.1", port);
in = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
out = new PrintWriter(
socket.getOutputStream()
, true);
out.println("hello world");
String resp = in.readLine();
System.out.println(resp);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO服务端
public class BioServer {
public static void main(String[] args) {
int port = 8888;
ServerSocket serverSocket = null;
try {
serverSocket
= new ServerSocket(port);
System.out.println("端口8888启动");
Socket socket = null;
while (true) {
socket = serverSocket.accept();
new Thread(
new ServerHandler(socket))
.start();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
System.out.println("关闭服务端");
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
BIO处理线程
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler (Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(
new InputStreamReader(
this.socket
.getInputStream()));
out = new PrintWriter(
this.socket
.getOutputStream(), true);
String current = null;
String body = null;
while (true) {
body = in.readLine();
if (body == null) {
break;
}
System.out.println(
"获取客户端输入:" + body);
out.println(
"当前时间是:" + new Date());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
既然BIO会为每个客户端在服务器端生成一个处理线程,服务器端可以用来做优化。当有新的客户端接入时,将客户端socket封装成一个任务,提交到线程池去执行。由于线程池有一系列参数(核心线程,最大线程,阻塞队列)约束,因此线程池占用的资源是可控的,即无论多少客户端连接到服务器端,都不会导致服务器资源耗尽而宕机。
public class FakeNioServer {
public static void main(String[] args) {
int port = 8888;
ServerSocket serverSocket = null;
ExecutorService executorService = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器端口8888启动");
Socket socket = null;
executorService = Executors
.newFixedThreadPool(10);
while (true) {
socket
= serverSocket.accept();
executorService
.submit(
new ServerHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
System.out.println("关闭服务端");
try {
serverSocket.close();
executorService.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
虽然伪异步IO避免了为每个客户端创建线程,但是因其底层通信仍然是同步阻塞模型,因此没有从根本是解决这个问题。
看下BIO中的InputStream的源码:
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;
当对socket的输入流进行读操作时,他会一直阻塞住,直到发生以下三种事件:
1.有数据可读
2.可读数据已经读取完毕
3.抛出异常
如果发送数据的一方处理速度缓慢,需要1分钟才能把数据发送完成,数据读取方必须同步阻塞1分钟。
对于BIO中的输出流也是会阻塞。
当数据接收方处理缓慢的时候,由于其不能及时的从TCP缓冲区读取数据,导致数据发送方的TCP window size不断减小,直到为0 ,双方处于Keep-Alive状态,此时消息发送方将不能再向缓冲区写入数据,write操作被无限期阻塞,直到window size大于0或者发生IO异常。
如何解决BIO带来的这些缺点呢?
Java中的NIO将解决这些问题