BIO与NIO
IO与NIO区别:其本质就是阻塞和非阻塞的区别。
阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,就会一直等待,直到传输完毕为止。
非阻塞概念:应用程序直接可以获取已经准备就绪好的数据,无需等待。
IO为同步阻塞形式,NIO为同步非阻塞形式,NIO并没有实现异步,在JDK1.7升级NIO库包,支持异步非阻塞。
BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到选择器上,选择器轮询到连接有IO请求时才启动一个线程进行处理。
AIO:异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由OS先完成在通知服务器应用去启动线程进行处理。
同步时,应用程序会直接参与IO读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪,或者采用轮训的策略实时检查数据的就绪状态,如果就绪则获取数据。
异步时,则所有的IO读写操作交给操作系统,与我们的应用程序没有直接关系,我们程序不需要关系IO读写,当操作系统完成了IO读写操作时,会给我们应用程序发送通知,我们的应用程序直接拿走数据即可。
伪异步
由于BIO一个客户端需要一个线程去处理,因此我们进行优化,后端使用线程池来处理多个客户端的请求接入,形成客户端个数M,线程池最大的线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
//服务器端
public class TCPServer {
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(8989);
try {
while(true) {
Socket accept = serverSocket.accept();
es.execute(new Runnable() {
public void run() {
try {
InputStream is = accept.getInputStream();
byte[] bs = new byte[1024];
int length = is.read(bs);
System.out.println(new String(bs,0,length));
} catch (Exception e) {
}
}
});
}
} catch (Exception e) {
}finally {
serverSocket.close();
}
}
}
//客户端
public class TCPClient {
public static void main(String[] args) throws Exception {
Socket s = new Socket("127.0.0.1", 8989);
OutputStream os = s.getOutputStream();
os.write("你好".getBytes());
s.close();
}
}
什么是阻塞
应用程序在获取网络数据的时候,如果网络传输很慢,那么程序就一直等着,直接到传输完毕。
什么是非阻塞
应用程序直接可以获取已经准备好的数据,无需等待。
IO为同步阻塞形式,NIO为同步非阻塞形式,NIO没有实现异步,在JDK1.7之后,升级NIO库包,支持异步非阻塞通讯模型。
//异步非阻塞
public class Client {
public static void main(String[] args) throws Exception {
//创建通道
SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));
//切换异步非阻塞模式
sc.configureBlocking(false);
//设置缓冲去大小
ByteBuffer bb = ByteBuffer.allocate(1024);
//获取键盘输入的值
Scanner s = new Scanner(System.in);
while(s.hasNext()) {
String str = s.next();
//把获取的值写入缓冲区中
bb.put(str.getBytes());
bb.flip();
//把缓冲区中的值写入通道中
sc.write(bb);
bb.clear();
}
sc.close();
}
}
public class Server {
public static void main(String[] args) throws Exception {
//创建通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//切换到异步非阻塞模式
ssc.configureBlocking(false);
//绑定链接
ssc.bind(new InetSocketAddress(8080));
//获取选择器
Selector open = Selector.open();
//将通道注册到选择器,并指定监听接受事件
ssc.register(open, SelectionKey.OP_ACCEPT);
//轮训式获取选择已经准备就绪的事件
while(open.select() > 0) {
//获取当前选择器所有注册的监听事件
Iterator<SelectionKey> it = open.selectedKeys().iterator();
while(it.hasNext()) {
//获取准备就绪的事件
SelectionKey sk = it.next();
//判断是什么事件准备就绪
if(sk.isAcceptable()) {
//接受就绪,获取客户端连接
SocketChannel sc = ssc.accept();
//设置非阻塞异步模式
sc.configureBlocking(false);
//将通道注册到服务器上
sc.register(open, SelectionKey.OP_READ);
} else if(sk.isReadable()) {
//获取当前选择器就绪的通道
SocketChannel s = (SocketChannel) sk.channel();
ByteBuffer bb = ByteBuffer.allocate(1024);
int len = 0;
while((len = s.read(bb)) > 0) {
bb.flip();
System.out.println(new String(bb.array(),0,len));
bb.clear();
}
}
}
it.remove();
}
}
}
选择KEY
SelectionKey.OP_CONNECT 可连接
SelectionKey.OP_ACCPT 可接受连接
SelectionKey.OP_READ 可读
SelectionKey.OP_WRITE 可写
如果要监听多种事件,可以用 | 操作符将常量连接起来。