上一篇文章我们介绍了传统的阻塞式IO在java中的体现,本篇文章我们开始介绍另一种通信模型---非阻塞式通信模型。
1.实现原理:阻塞式通信模型是因为服务器端在accept和read方法的时候,如果没有外部网络连接请求或者外部网络的数据传输,那么就会处于等待状态。非阻塞式通信模型,主要是通过为accept和read方法设置等待时间,在超过等待时间之后可以让CPU暂时做一些其他处理,过一段时间再去监测accept方法和read方法。
2.服务端代码:
(1)为accept方法设置等待时间:
package BIO;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
OutputStream os = null;
try {
//服务器端接收客户端请求并回写信息
serverSocket = new ServerSocket(9999);
serverSocket.setSoTimeout(3000);
while(true){
try {
socket = serverSocket.accept();
is = socket.getInputStream();
os = socket.getOutputStream();
byte[] receiveByte = new byte[1024];
StringBuffer receiveMsg = new StringBuffer();
//接收客户端发送的数据
while(is.read()!=-1){
is.read(receiveByte);
receiveMsg.append(new String(receiveByte));
//收到“over”表示客户端已经发送完完整的信息,服务器可以处理请求并在完成之后写回客户端
if(receiveMsg.indexOf("。")!=-1){
System.out.println("客户端发送的信息为:"+receiveMsg);
break;
}
}
/*System.out.println(receiveMsg);
byte[] sendByte = new byte[1024];
String sendMsg = "服务端已经收到请求over";
sendByte = sendMsg.getBytes();
os.write(sendByte);
os.flush();*/
} catch (SocketTimeoutException e) {
// TODO: handle exception
System.out.println("CPU执行其他操作"+System.currentTimeMillis());
continue;
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
os.close();
is.close();
socket.close();
serverSocket.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
(2)为read方法设置等待时间:
package BIO;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class Server1 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
OutputStream os = null;
try {
serverSocket = new ServerSocket(9999);
//accept方法设置等待时间
serverSocket.setSoTimeout(5000);
while(true){
//accept等待超时以后CPU进行其他操作
try {
socket = serverSocket.accept();
} catch (SocketTimeoutException e) {
System.out.println("accept方法CPU执行其他操作"+System.currentTimeMillis());
continue;
}
System.out.println("来自客户端的请求已经建立");
is = socket.getInputStream();
os = socket.getOutputStream();
byte[] receiveByte = new byte[1024];
//read方法设置等待时间
socket.setSoTimeout(3000);
while(true){
//read方法等待超时CPU做其他操作
try {
while(is.read()!=-1){
is.read(receiveByte);
String receiveMsg = new String(receiveByte);
System.out.println(receiveMsg);
//收到“。”表示客户端已经发送完完整的信息,服务器可以处理请求并在完成之后写回客户端
if(receiveMsg.indexOf("。")!=-1){
System.out.println("客户端发送的信息为:"+receiveMsg);
break;
}
}
} catch (Exception e) {
System.out.println("read方法CPU执行其他操作"+System.currentTimeMillis());
continue;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
os.close();
is.close();
socket.close();
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.客户端代码:
package BIO;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
Socket socket = null;
InputStream is = null;
OutputStream os = null;
try {
//客户端请求建立连接
socket = new Socket("localhost", 9999);
is = socket.getInputStream();
os = socket.getOutputStream();
byte[] sendBuffer = new byte[1024];
Scanner sc = new Scanner(System.in);
System.out.println("请输入要发送到服务端的消息:");
while(true){
String sendMsg = sc.next();
if(sendMsg.indexOf("。")!=-1){
sendBuffer = sendMsg.getBytes();
os.write(sendBuffer);
os.flush();
System.out.println("'"+sendMsg+"发送成功'");
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
is.close();
os.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.实验结果:
(1)为accept方法设置等待时间:
先启动服务端(不启动客户端),此时没有客户端连接请求。服务器等待3秒,若没有客户端请求过来,则服务器可以在这个时候让CPU去做其他处理。运行结果如下图:
此时启动客户端,服务器收到客户端请求,处理客户端请求,并将处理结果返回给客户端。
服务器端运行结果如下图:
客户端运行结果如下图:
注:因为编码的不同,所以客户端发送的消息到了服务端可能会乱码。比如我在客户端输入的是“nihao。”,到服务端就成了“ihao。”。如果输入的是汉字的话就可以明显的看出来。所以在测试之前还需要调整编码,我这里就不调了。
(2)为read方法设置等待时间:
先启动服务端(不启动客户端),此时因为没有连接请求,所以accept方法在监测超时后CPU会进行一段时间的其他处理。
启动客户端,此时客户端发送连接请求,服务端收到连接请求以后,read方法开始监测数据传输,因为客户端没有发送任何数据,所以在read方法监测超时以后CPU也会进行一段时间的其他处理。
此时客户端发送数据,服务端read方法接收到数据,CPU先去处理read接收到的数据,处理完以后如果客户端不发送数据,那么read方法在等待超时之后,CPU会继续先进行一段时间的其他处理。
注:(1)可以看到因为没有设置编码的问题,确实是乱码的。(2)因为本例是单线程的,所以在有客户端连接以后,进入read方法,那么accept方法就不会再执行,也就不会再阻塞,所以不会再有“accept方法CPU执行其他操作“。
5.总结:(1)阻塞型通信模型到非阻塞型通信模型的转变,就是为accept和read方法设置等待时间。
(2)虽然采用多线程和非阻塞通信模型会优化系统间通信,但是这只是程序级别的优化,操作系统底层的IO模型仍是“同步”的,即很多客户端请求到达服务端的时候,服务端在accept的时候还是一个一个的accept的,read也是同样的。