针对同步阻塞I/O模型效率底下的问题,java在2002年的JDK1.4之后引入了非阻塞I/O模型,但是操作比较复杂,Netty就是封装了底层非阻塞I/O操作而给用户提供可以相对简单使用的API库。
java早期版本对I/O支持不完善,性能低下,主要问题如下:
- 无数据缓冲区,I/O性能低下
- 没有C/C++中Channel概念,只有输入和输出流
- 同步阻塞式I/O通信(BIO),通常会导致通信线程被长时间阻塞
- 支持的字符集有限,硬件可移植性不好
Linux网络I/O模型
linux内核将所有设备都看做文件来操作,通过文件描述符file desvriptor来对文件进行相应的操作,fd就是一个数字,指向内核中的一个结构体。
Unix提供了5中I/O模型:
- 阻塞I/O模型
- 非阻塞I/O模型
- I/O复用模型
- 信号驱动I/O模型
- 异步I/O
其中这里着重介绍一下异步I/O,首先通过一个图片了解一下异步I/O操作的使用例子:
用户应用层告诉内核某个操作后,不是像阻塞I/O一样阻塞等待内核完成操作后返回数据,而是立即返回,让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。
这与信号驱动型是有区别的,信号驱动型业务流程是这样的:
1. 首先开启信号驱动I/O
2. 通过系统调用sigaxtion执行一个信号处理函数(这个函数调用立即返回,进程继续工作,这是非阻塞的)
3. 内核根据用户的请求开始等待数据,数据准备好后通过信号通知用户
4. 用户通过系统调用将数据进行拷贝,完成后返回一个结果。
用一个图表示:
BIO
BIO通信模型图如下:
BIO通信模型的服务端通常由一个独立的Acceptor线程负责监听客户端的连接,接收到客户端的请求后为每个客户端创建一个新的线程进行链路处理,处理完成后通过输出流返回应答给客户端,然后销毁线程。这是典型的一请求一应答通信模型。
该模型的缺点是缺少弹性伸缩能力,客户端访访问量巨大时,服务端线程和客户端并发访问数是1:1的正比关系。java线程是java虚拟机非常宝贵的资源,线程占用过多时,系统的性能会几句下降,当系统发生线程堆栈溢出、创建新线程失败时最终会导致宕机或者僵死的结果,不能对外提供服务。
下面以一个客户端获取当前时间的例子对BIO通信进行分析:
- 服务端程序(TimeServer)
package time.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TimeServer {
public static void main(String[] args) {
// TODO Auto-generated method stub
int port = 8080;
if(args != null && args.length > 0){
port = Integer.valueOf(args[0]);
}
ServerSocket server = null;
try {
server = new ServerSocket(port);
System.out.println("The time server is start in port" + port);
Socket socket = null;
while(true){
socket = server.accept();
new Thread(new TimeServerHandler(socket)).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(server != null){
System.out.println("The time server close");
try {
server.close();
server = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
其中TimeServerHandler是新创建的一个java线程,用来处理客户端发来的请求,注意:一个线程只能处理客户端的一个请求,TimeServerHandler的具体代码如下:
package time.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class TimeServerHandler implements Runnable {
private Socket socket;
public TimeServerHandler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
// TODO Auto-generated method stub
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(
this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String currentTime = null;
String body = null;
while(true){
body = in.readLine();
if(body == null)
break;
System.out.println("The time server receive order:" + body);
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new
java.util.Date(
System.currentTimeMillis()).toString() : "BAD ORDER";
out.println(currentTime);
}
} catch (Exception e) {
if(in != null){
try {
in.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
in = null;
}
if(out != null){
out.close();
out = null;
}
if(this.socket != null){
try {
this.socket.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
其实TimeServerHandler主要负责处理客户端发来的请求,如果是客户端发来的是“QUERY TIME ORDER”,则把当前时间返回给客户端,这是通过PrintWriter操作流来实现的,具体原理请查看 流 的概念和操作。线程最后自动销毁,又java虚拟机回收。
客户端通过Socket创建、发送查询时间服务器的指令,然后将服务器的响应打印出来,随后关闭链接,释放资源。
package time.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class TimeClient {
public static void main(String[] args) {
// TODO Auto-generated method stub
int port = 8080;
if(args != null && args.length > 0){
port = Integer.valueOf(args[0]);
}
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("QUERY TIME ORDER");
System.out.println("Send order 2 server succeed");
String resp = in.readLine();
System.out.println("Now is:" + resp);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// in = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
socket = null;
}
}
}
}