一:BIO
1、网络编程的基本模型是C/S模型,即两个进程间的通信。
2、服务端提供IP和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。
3、传统的同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
4、简单的描述一下BIO的服务端通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理没处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答通宵模型。
5、传统BIO模型图
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死-掉-了。
二、伪异步IO
1、为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程,实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。
2、伪异步模型图
3、相关代码
package io_ThreadPool;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author wangchi
* @version currentVersion(1.0)
* @description
* @createtime
*/
public class Server {
final static int PROT = 8765;
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(PROT);
Socket socket = null;
HandleExecutorPool handleExecutorPool = new HandleExecutorPool(50,1000);
while (true){
socket = server.accept();
handleExecutorPool.execute(new ServerHandle(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package io_ThreadPool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author wangchi
* @version currentVersion(1.0)
* @description
* @createtime
*/
public class HandleExecutorPool {
private ExecutorService executorService;
public HandleExecutorPool(int maxPoolSize, int queueSize) {
this.executorService = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue(queueSize)
);
}
public void execute(Runnable task){
this.executorService.execute(task);
}
}
三、NIO
1、NIO的本质就是避免原始的TCP建立连接使用3次握手的操作,减少连接的开销
2、NIO的模式并不是说直接就把数据传给你,它是利用一个channel管道(管道与流不同之处是它是双向的),或者buffer缓冲区的概念,我是先把数据储存在缓冲区,然后准备就绪之后,把这个数据一次性刷给你。并不是通过网络一个字节一个字节去传,它是先把数据传好,我们之间建立管道,然后把数据一次性刷给你。其实就相当于多了一个空间,bio是通过一个字节一个字节通过带宽把数据传给你,nio是依靠了一个中间的媒介。
3、将数据写到缓冲区,通过channel流转到客户端和服务端,Selector管理所有的channel
4、原来客户端和服务端建立连接直接通过tcp/ip(三次握手之后建立连接),现在没有握手机制,通过注册channel这种方式。
5、在服务端有一个selector多路复用器,它轮询所有注册的通道根据通道的状态去进行相关的操作,也就是说我们的客户端第一件事都要把自己的通道注册到服务端的多路复用器上,然后selector去轮询注册到它上面的channel,看他们的状态(连接,阻塞,可读)分别做相关的事情
6、非阻塞是因为这个数据只要写好了,准备就绪了我们就直接从这个通道里面获取数据了,最核心的是避免了原始的tcp建立连接使用的3次握手的操作,减少连接的开销
7、Bio和nio之间的区别:本质是阻塞和非阻塞,bio为同步阻塞,nio为同步非阻塞,aio为异步非阻塞
阻塞:线程在获取网络数据的时候,如果网络传输数据很慢,那么这个线程就一直等着,直到传输完毕为止;非阻塞:线程可以直接获取已经准备好的数据,无需进行等待;
同步和异步一般是面向os与应用程序对io操作的层面上来区别的;同步时,应用程序会直接参与io读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪:或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据;异步时所有的io操作则交给os来进行处理,当os完成了io读写操作时,会给我们的应用程序发送通知,应用程序拿走数据就Ok。
同步说的是server服务端的执行方式,阻塞说的是具体的技术,接收数据的方式、状态(io、nio)
8、相关代码
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* @author wangchi
* @version currentVersion(1.0)
* @description
* @createtime
*/
public class Server implements Runnable {
private Selector selector;
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
public Server(int port){
try {
this.selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(port));
ssc.register(this.selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try {
this.selector.select();
Iterator keys = this.selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
keys.remove();
if(key.isValid()){
if(key.isAcceptable()){
this.accept(key);
}
if(key.isReadable()){
this.read(key);
}
if(key.isReadable()){
//写数据
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void read(SelectionKey key){
try {
this.readBuf.clear();
SocketChannel sc = (SocketChannel)key.channel();
int count = sc.read(this.readBuf);
if(count == -1){
key.channel().close();
key.cancel();
return;
}
this.readBuf.flip();
byte [] bytes = new byte[this.readBuf.remaining()];
this.readBuf.get(bytes);
String body = new String(bytes).trim();
System.out.println(body);
} catch (IOException e) {
e.printStackTrace();
}
}
private void accept(SelectionKey key){
try {
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(this.selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new Server(8765)).start();
}
}
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @author wangchi
* @version currentVersion(1.0)
* @description
* @createtime
*/
public class Client {
public static void main(String[] args) {
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8765);
SocketChannel sc = null;
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
sc = SocketChannel.open();
sc.connect(inetSocketAddress);
while(true){
byte [] bytes = new byte[1024];
System.in.read(bytes);
buf.put(bytes);
buf.flip();
sc.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(sc != null){
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
四、AIO
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。
五、其他
Java对BIO、NIO、AIO的支持:
Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。