导语
随着JavaI/O库的不断迭代升级,基于Java 语言的网络编程也开始变的简单,随着异步I/O功能的增强,基于JavaNIO开发的网络服务器也越来越多。很多的互联网大厂都在不断的优化JavaNIO的开发。
对于网络编程来讲,最为基本的模型就是Server/Client模型,简单的说就是两个线程之间的通信。其中,Server服务端提供的是IP地址以及端口号,客户端通过TCP三次握手连接到服务端指定的IP地址与端口号。然后双方就可以通过Socket进行通信。
在之前的Java网络开发模型中,ServerSocket主要是负责提供服务端的IP端口等信息。而Socket则是作为客户端向服务端建立连接然后进行输入输出流的同步阻塞方式通信。
下面就来通过一个小例子来看看关于同步阻塞通信BIO相关的内容。如图所示
BIO通信模型,在服务端通常会有一个独立的Acceptor线程负责监听客户端的连接,收到客户端发送的连接之后,为每个客户端建立一个新的线程进行处理链路请求,处理完成之后,通过输出流的方式返回给客户端,然后线程进行销毁。这就是比较经典的一请求一应答的通信模型。
这个模型最大的问题就是不能够弹性伸缩,当客户端的访问量增加之后,服务端的线程数将会与客户端的数量呈1:1的正比关系,由于线程是JVM中比较宝贵的系统资源,在线程数暴涨之后,系统的性能也将下降,随着并发量的逐渐增大,系统会发生线程堆栈溢出、创建新的线程失败等问题,最终就会导致JVM不能提供服务。
下面就通过一个小例子来进行测试
public class TimeServer {
public static void main(String[] args) {
int port = 8080;
ServerSocket serverSocket = null;
try{
serverSocket = new ServerSocket(port);
System.out.println("The time server is start in port : "+ port);
Socket socket = null;
while (true){
socket = serverSocket.accept();
new Thread(new TimeServerHandler(socket)).start();;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (serverSocket!=null){
System.out.println("The time server close");
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
serverSocket = null;
}
}
}
}
运行代码之后,会看到这线程会阻塞到TimeServer类的第25行代码上,也就是 socket = serverSocket.accept(); 。通过一个无限循环来监听客户端的连接,如果没有客户端接入,则主线程阻塞在ServerSocket的accept方法上。
public class TimeServerHandler implements Runnable {
private Socket socket;
public TimeServerHandler(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 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 Date(System.currentTimeMillis()).toString():"BAD ORDER";
out.println(currentTime);
}
} catch (IOException e) {
if (in!=null){
try{
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (out!=null){
out.close();
out = null;
}
if (this.socket!=null){
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
通过BufferedReader读取数据,如果读取到输入流的结束,则返回null,退出循环,如果读取到非空的值,则会对内容进行判断。然后根据具体的请求要求进行操作。
TimeClient客户端通过建立Socket连接,发送查询时间到服务端,然后读取到服务端的响应结果并打印出来,然后关闭连接,释放资源。
public class TimeClient {
public static void main(String[] args) {
int port = 8080;
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 (Exception e) {
}finally {
if (out!=null){
out.close();
out = null;
}
if (in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket=null;
}
}
}
}
通过整个的实验会发现,BIO主要的问题就是在于每当有一个新的客户端请求接入的时候,服务端就必须创建一个新的线程处理新的连接,并且一个线程只能处理一个客户端的连接请求。这在高性能的互联网领域是行不通的,在高性能互联网领域往往会是同时有成千上万的客户端进行并发请求,这种显然不满足高性能的互联网接入方式。
为了改进这种模型,结合池化技术和消息队列技术就实现了1个或者多个线程处理多个客户端的模型,但是它的底层还是采用了BIO的方式,所以就被称为是伪异步的实现方式。