在通信框架中经常使用到的三种通信模式,即BIO NIO AIO .
BIO,全程Block-IO,是一种阻塞同步的通信模式,也就是我们常说的SocketIO一般指的是就是BIO
2.2 优点模式简单,使用方便
2.3 缺点并发处理能力低,通信耗时
2.4 原理 2.5 小结BIO模型中通过Socket和ServerSocket完成套接字通道的实现,阻塞,同步,建立连接耗时
服务器提供IP地址和监听的端口,客户端使用服务器提供的IP地址和端口通过TCP三次握手与服务器,连接成功之后,通过Socket套接字通信。
BIO服务器代码:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.springboot.demo.io.Constants;
public class BIOServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
ThreadPoolExecutor threadPoolExecutor = null;
try {
// 启动服务监听
serverSocket = new ServerSocket(Constants.BIO_PORT);
System.out.println("BIO server starting.....");
threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1000,
TimeUnit.SECONDS, new ArrayBlockingQueue(50));
while (true) {
// 服务器监听:阻塞,等待Client请求
socket = serverSocket.accept();
threadPoolExecutor.execute(new BIOServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null) {
socket.close();
}
if (serverSocket != null) {
serverSocket.close();
System.out.println("BIO Server closed !!!!");
}
threadPoolExecutor.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class BIOServerHandler implements Runnable {
private Socket socket;
public BIOServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
printWriter = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
// 若客户端用的是 writer.print() 传值,那readerLine() 是不能获取值的
body = bufferedReader.readLine();
if (body == null) {
break;
}
System.out.println(Thread.currentThread().getName() + ", BIO server receive data:" + body);
// 返回客户端数据
printWriter.println(body + " data is received" );
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (printWriter != null) {
printWriter.close();
}
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (this.socket != null) {
this.socket.close();
this.socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO服务器主要实现了以下逻辑:
1) 服务启动,阻塞服务,端口监听客户端请求,为客户端请求分配线程处理
2) 从Socket获取请求数据,然后将返回数据写入,返回Socket套接字给客户端
BIO客户端代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import com.springboot.demo.io.Constants;
public class BIOClient {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
clientRequest(i);
}
}
private static void clientRequest(int i){
Socket socket = null;
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
socket = new Socket(Constants.HOST, Constants.BIO_PORT);
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
printWriter = new PrintWriter(socket.getOutputStream(),true);
String data = "客户端" + i + "数据";
// 向服务端发送数据
printWriter.println(data);
System.out.println(i + " 客户端请求返回数据 : " + bufferedReader.readLine());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(socket != null){
socket.close();
}
if(bufferedReader != null){
bufferedReader.close();
}
if(printWriter != null){
printWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO客户端主要实现了以下逻辑:
1)启动客户端,即通过服务器IP地址和端口和服务器建立Socket连接,发起请求
2) 从Socket套接字中获取请求返回的数据
NIO 全称 New IO, 也叫Non-Block IO 是一种非阻塞同步的通信方式
3.2 优点并发能力好,吞吐量大
3.3 缺点模式相对复杂,使用起来复杂
3.4 原理服务端与客户端通过Channel通信,NIO在Channel上进行数据读写操作,而这些Channel都会被注册在Selector多路复用器上,Selector通过一个线程不停的轮询这些已经注册的Channel,筛选成已经准备好的Channel进行IO操作
3.5 小结NIO模型中通过SocketChannel和ServerSockerChannel完成套接字通道的实现,非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销
NIO通过一个线程轮询Channel达到处理很多客户端请求
NIO通过Channekl通信,NIO的数据操作都是在缓冲区中进行的,使用Selector多路复用器轮询,下面分别介绍一下这些名词。
Channel(通道) Buffer(缓冲区) Selector(多路复用器) 3.7 示例demoNIO 服务端代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import com.springboot.demo.io.Constants;
public class NIOServer {
public NIOServer() {
}
private Selector startNIOServer() {
try {
// 1、打开多路复用器
Selector selector = Selector.open();
// 2、打开服务器通道(网络读写通道)
ServerSocketChannel socketChannel = ServerSocketChannel.open();
// 3、设置服务器通道为非阻塞模式,true 为阻塞,false 为非阻塞
socketChannel.configureBlocking(false);
// 4、绑定端口
socketChannel.bind(new InetSocketAddress(Constants.NIO_PORT));
// 5、把通道注册到多路复用器上,并监听阻塞事件
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO server starting......");
return selector;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
Selector selector = new NIOServer().startNIOServer();
new Thread(new NIOServerHandler(selector)).start();
}
}
import java.io.IOException;
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;
import com.springboot.demo.io.Constants;
public class NIOServerHandler implements Runnable {
// 多路复用器,NIO编程的基础,负责管理通道Channel
private Selector selector;
// 缓冲区buffer
private ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
public NIOServerHandler(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
while (true) {
try {
// 1、多路复用器监听阻塞
selector.select();
System.out.println("阻塞了吗?");
// 2、获取多路复用器已经选择的结果集
Iterator selectionKeys = selector.selectedKeys().iterator();
// 3、不停地轮询所有的Channel
while (selectionKeys.hasNext()) {
// 4、获取当前选中的key
SelectionKey key = selectionKeys.next();
// 5、获取后便将其从容器中移除
selectionKeys.remove();
if (!key.isValid()) {
// 只获取有效地key
continue;
}
// 新来的请求
if (key.isAcceptable()) {
accept(key);
}
// 可读状态处理
if (key.isReadable()) {
System.out.println("开始读取数据");
read(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void read(SelectionKey key) {
try {
// 1、清空缓冲区数据
byteBuffer.clear();
// 2、获取在 selector 上注册的channel
SocketChannel socketChannel = (SocketChannel) key.channel();
// 3、读取数据
int index = socketChannel.read(byteBuffer);
if (index == -1) {
// -1 标示无任何数据
socketChannel.close();
key.cancel();
return;
}
// 4、有数据则在读取数据前进行复位操作
byteBuffer.flip();
// 5、根据缓冲区大小创建一个相应大小的bytes数组,用来获取值
byte[] bytes = new byte[byteBuffer.remaining()];
// 6、接收缓冲区数据
byteBuffer.get(bytes);
String requestMsg = new String(bytes);
System.out.println("NIO server received data:" + requestMsg);
// 返回响应数据给客户端
write(socketChannel, requestMsg + " is response");
} catch (IOException e) {
e.printStackTrace();
}
}
private void write(SocketChannel socketChannel, String s) {
try {
// 创建ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
// 将返回数据放入缓存区
byteBuffer.put(s.getBytes());
// 缓存区数据复位
byteBuffer.flip();
// 发送缓冲区数据
socketChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
// 设置阻塞,等待Client请求。在传统IO编程中,用的是ServerSocket和Socket。在NIO中采用的ServerSocketChannel和SocketChannel
private void accept(SelectionKey key) {
try {
// 1、获取通道服务
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 2、执行阻塞方法
SocketChannel socketChannel = serverSocketChannel.accept();
// 3、设置通道服务为非阻塞,true 为阻塞,false 为非阻塞
socketChannel.configureBlocking(false);
// 通道注册到 selector 上去,并设置读取标识
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO服务端主要实现了一下逻辑:
1)开启多路复用器Selector,打开通道ServerSockerChannel,通道绑定端口,注册通道
2) Selector轮询通道,处理请求请求事件,返回响应数据
NIO 客户端代码:
import java.util.Scanner;
public class NIOClient {
private static NIOClientHandler nioClientHandler;
public static void startClient(){
nioClientHandler = new NIOClientHandler();
new Thread(nioClientHandler).start();
}
public static void main(String[] args) {
// 开启客户端
NIOClient.startClient();
// 发送消息
while(nioClientHandler.sendMsg(new Scanner(System.in).nextLine()));
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import com.springboot.demo.io.Constants;
public class NIOClientHandler implements Runnable {
// 多路复用器,NIO编程的基础,负责管理通道Channel
private Selector selector;
private SocketChannel socketChannel;
// 缓冲区buffer
private ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
public NIOClientHandler() {
try {
// 1、打开多路复用器
selector = Selector.open();
// 2、打开 SocketChannel 通道
socketChannel = SocketChannel.open();
// 3、设置为非阻塞模式,true 为阻塞,false 为非阻塞
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
doConnect();
while (true) {
try {
// 1、多路复用器监听阻塞
selector.select();
// 2、获取多路复用器已经选择的结果集
Iterator selectionKeys = selector.selectedKeys().iterator();
// 3、不停地轮询所有的Channel
while (selectionKeys.hasNext()) {
// 4、获取当前选中的key
SelectionKey key = selectionKeys.next();
// 5、获取后便将其从容器中移除
selectionKeys.remove();
dealEvent(key);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void dealEvent(SelectionKey key) {
// 只获取有效地key
if (key.isValid()) {
// 连接事件
if (key.isConnectable()) {
System.out.println("连接事件来了");
connect(key);
}
// 可读状态处理
if (key.isReadable()) {
System.out.println("开始读取数据");
read(key);
}
}
}
private void connect(SelectionKey key) {
try {
SocketChannel socketChannel = (SocketChannel) key.channel();
if (!socketChannel.finishConnect()) {
System.out.println("finish connect?");
System.exit(1);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void doConnect() {
try {
System.out.println("连接了。。。。");
InetSocketAddress socketAddress = new InetSocketAddress(Constants.HOST,Constants.NIO_PORT);
if(!socketChannel.connect(socketAddress)){
System.out.println("connect is false?");
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void read(SelectionKey key) {
try {
// 1、清空缓冲区数据
byteBuffer.clear();
// 2、获取在 selector 上注册的channel
SocketChannel socketChannel = (SocketChannel) key.channel();
// 3、读取数据
int index = socketChannel.read(byteBuffer);
if (index == -1) {
// -1 标示无任何数据
socketChannel.close();
key.cancel();
return;
}
// 4、有数据则在读取数据前进行复位操作
byteBuffer.flip();
// 5、根据缓冲区大小创建一个相应大小的bytes数组,用来获取值
byte[] bytes = new byte[byteBuffer.remaining()];
// 6、接收缓冲区数据
byteBuffer.get(bytes);
String requestMsg = new String(bytes);
System.out.println("NIO client received data:" + requestMsg);
} catch (IOException e) {
e.printStackTrace();
}
}
private void write(String content) {
try {
// 创建ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
// 将返回数据放入缓存区
byteBuffer.put(content.getBytes());
// 缓存区数据复位
byteBuffer.flip();
// 发送缓冲区数据
socketChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean sendMsg(String content){
if("stop".equals(content)){
return false;
}
try {
socketChannel.register(selector, SelectionKey.OP_READ);
write(content);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
return true;
}
}
NIO 客户端主要实现了一下逻辑:
1)开启多路复用Selector,打开通道SocketChannl,注册通道,连接服务器
2)发送请求,发送数据
3)Selector轮询通道,处理请求响应数据事件
AIO也叫NIO2.0,是一种非阻塞异步的通信模式,在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现
4.2 优点相比NIO,并发能力更好,吞吐量更大
4.3 缺点模式相对复杂,使用起来复杂
4.4 原理AIO并没有采用NIO的多路复用器Selector,而是使用异步通道,其read,write方法的返回类型都是Future对象。而Future模型是异步的,弃核心思想是:在主函数等待数据返回即可。
AIO 并没有采用NIO的多路复用器Selector,而是使用异步通道,其read write方法返回类型都是Future对象。而Future模型是异步的,其核心思想是:在主函数等待数据返回即可
AIO 模型中通过AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的实现,非阻塞异步
4.6 示例demoAIO 服务端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.springboot.demo.io.Constants;
public class AIOServer {
// 线程池
private ExecutorService executorService;
// 通道组
private AsynchronousChannelGroup channelGroup;
// 服务器通道
public AsynchronousServerSocketChannel serverSocketChannel;
public AIOServer() {
}
public void startAIOServer() {
try {
// 1、创建一个缓存线程池
executorService = Executors.newCachedThreadPool();
// 2、创建通道组
channelGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
// 3、创建服务器通道
serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
// 4、绑定网络
serverSocketChannel.bind(new InetSocketAddress(Constants.AIO_PORT));
System.out.println("AIO server starting......");
// 等待客户端请求
serverSocketChannel.accept(this, new AIOServerHandler());
// 一直阻塞 不让服务器停止,真实环境是在tomcat下运行,所以不需要这行代码
TimeUnit.MILLISECONDS.sleep(Integer.MAX_VALUE);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new AIOServer().startAIOServer();
}
}
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import com.springboot.demo.io.Constants;
public class AIOServerHandler implements CompletionHandler {
@Override
public void completed(AsynchronousSocketChannel result, AIOServer attachment) {
// 保证多个客户端都可以阻塞
attachment.serverSocketChannel.accept(attachment,this);
read(result);
}
// 读取数据
private void read(final AsynchronousSocketChannel socketChannel) {
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
socketChannel.read(byteBuffer, byteBuffer, new CompletionHandler() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 读取之后重置标记位
attachment.flip();
// 获取读取的数据
String resultData = new String(attachment.array()).trim();
System.out.println("AIO server received data:" + resultData);
//
String responseContent = resultData + " response";
write(socketChannel, responseContent);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
private void write(AsynchronousSocketChannel socketChannel, String responseContent) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
// 数据放入缓冲区中
byteBuffer.put(responseContent.getBytes());
byteBuffer.flip();
// 缓冲区中数据写入到通道中
socketChannel.write(byteBuffer).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, AIOServer attachment) {
exc.printStackTrace();
}
}
AIO服务端主要实现了以下逻辑:
1)创建服务器通道AsynchronousServerSocketChannel,绑定端口,等待客户端请求
2)读取请求数据,处理数据,写入返回数据
AIO客户端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import com.springboot.demo.io.Constants;
public class AIOClient implements Runnable {
private AsynchronousSocketChannel socketChannel;
private Integer index;
public AIOClient(Integer index) {
try {
this.index = index;
// 打开通道
socketChannel = AsynchronousSocketChannel.open();
// 创建连接
socketChannel.connect(new InetSocketAddress(Constants.HOST,Constants.AIO_PORT));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String content = index + " -> 客户端数据";
try {
socketChannel.write(ByteBuffer.wrap(content.getBytes())).get();
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
while (true){
int count = socketChannel.read(byteBuffer).get();
byteBuffer.flip();
if(count == -1){
continue;
}
byte[] responseBytes = new byte[byteBuffer.remaining()];
// 将缓冲区的数据放入到 byte数组中
byteBuffer.get(responseBytes);
System.out.println(index+" 客户端 receive response :" + new String(responseBytes).trim());
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
AIOClient aioClient = new AIOClient(i);
new Thread(aioClient,"t-"+i).start();
}
}
}
AIO客户端主要实现了以下逻辑: 1)打开通道AsynchronousSocketChannel, 连接服务器 2) 发送请求,读取请求响应返回数据 # 5.BIO NIO 和 AIO区别 BIO: IO阻塞同步通信模式,客户端和服务器连接需要三次握手,使用简单,但吞吐量小 NIO: 非阻塞同步通信模式,客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel,提高吞吐量和可靠性 AIO: 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法 ## 6. 总结 BIO模型通过Socket 和 ServerSocket完成套接字通道实现。阻塞,同步,连接耗时 NIO模型通过SocketChannel 和 ServerSocketChannel完成套接字通道实现。非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销。 AIO模型中通过AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道实现。非阻塞,异步



