BIO
网络IO中比较经典的场景就是http服务器,在java中通过socket也就是BIO实现过程大致如下:
1.创建一个ServerSocket监听一个端口
2.通过accept方法阻塞服务器并等待客户端的连接
3.客户端发起请求,服务器通过accept方法获取一个客户端的socket
4.启动一个新线程来处理我们客户端的请求
5.处理请求的线程通过socket获取输入流并读取流中的数据
6.获取字节数据根据http协议解码数据,获取http请求
7.处理http请求,根据请求构建响应数据
8.根据http的编码协议对响应数据编码并写入客户端的socket
9.socket调用系统write函数将数据发送到物理设备网卡返回给客户端
10.重复接收请求从第3步执行
大概代码实现如下:
package com.crs.echo;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author administrator
* @version 1.0
* @date 2021/7/2 22:29
**/
public class BIOServer implements Server{
@Override
public void start(int port) throws IOException {
// 创建serversocket并监听端口
final ServerSocket serverSocket = new ServerSocket(port);
System.out.println("打开socket连接并绑定端口"+port);
try {
while (true){
// 利用accept函数阻塞并接收前端请求
Socket socket =serverSocket.accept();
System.out.println("接收到请求来自: " + socket.getInetAddress());
// 有请求到来时开启新线程处理请求
new Thread(()->{
OutputStream out;
InputStream in;
try {
// socket的输出流,向socket写入数据
out = socket.getOutputStream();
// socket的输入流,从socket中读取数据
in = socket.getInputStream();
DataInputStream dis = new DataInputStream(in);
// 读取请求头,http的请求头:Get / HTTP/1.1
int len;
String response = "";
byte[] bytes = new byte[124];
while ((len=dis.read(bytes))!=-1&&dis.available()>0){
response=response+new String(bytes);
}
System.out.println("准备将数据写回前端,返回的数据为");
System.out.println(response);
// 通过OutputStream构建字符输出流
PrintWriter writer = new PrintWriter(socket.getOutputStream());
// 利用字符输出流向前端返回数据
writer.println("HTTP/1.0 200 OK");
writer.println("Content-Type:application/json");
writer.println();
writer.println(response);
// 将流中的数据从缓冲区刷新到内存中
writer.flush();
// 关闭流
writer.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
上述代码描述的就是使用BIO的形式来实现http服务器的过程,缺点是每当有新的请求时服务器都要创建一个新的线程来处理请求,创建线程是一个比较消耗资源的事情
NIO
在NIO模型中不需要创建多个线程,利用操作系统的IO多路复用模型来建立连接和读取数据
1.创建一个服务器socket通道并绑定监听端口
2.创建选择器Selector
3.将服务器socket通道注册到Selector中
4.通过选择器选择就绪事件并处理
5.通过就绪事件的通道键值获取客户端socket通道
6.从客户端socket通道中读取数据并处理
7.处理完数据后将响应数据写回客户端socket通道
8.在数据处理过程必须使用缓冲区ByteBuffer
通过java的NIO模式编写一个回显服务器
package com.crs.echo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* @author 刘小江
* @version 1.0
* @date 2021/8/10 23:26
**/
public class NIOServer implements Server{
@Override
public void start(int port) throws IOException {
// 初始化服务器socket通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定主机端口
serverSocketChannel.socket().bind(new InetSocketAddress("localhost", port));
// 设置通道为非阻塞的
serverSocketChannel.configureBlocking(false);
// 创建selector
Selector selector =Selector.open();
// 将通道的连接事件注册到selector中
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 通过无线循环处理连接
while (true){
// selector选择就绪事件,此处会阻塞
int num = selector.select();
if(num == 0){
continue;
}
// 获取到已准备就绪的通道的选择键值,返回的是一个就绪事件集合
Set<SelectionKey> selectKeys=selector.selectedKeys();
// 迭代处理就绪事件
Iterator iter = selectKeys.iterator();
while (iter.hasNext()){
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
// 处理连接请求
if(key.isAcceptable()){
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 将客户端socket通道注册到读事件中
socketChannel.register(selector,SelectionKey.OP_READ);
}
// 处理读通道请求
else if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
// 创建缓冲区默认为写模式
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将通道中的数据读取到缓冲区中
channel.read(buffer);
// 翻转缓冲区将缓冲区切换为读模式
buffer.flip();
byte[] bts = new byte[buffer.limit()];
buffer.get(bts);
System.out.println("接收到的前端数据为"+new String(bts,"utf8"));
// 构建返回的数据
// 构建请求头响应码
String head1="HTTP/1.0 200 OK"+"\r\n";
// 构建请求头返回数据格式
String head2="Content-Type:application/json"+"\r\n";
// 构建返回的数据为前端请求的数据
String content = new String(bts,"utf8");
String resStr = head1 +"\r\n"+ head2+"\r\n"+content;
buffer = ByteBuffer.allocate(resStr.length());
buffer.put(head1.getBytes());
buffer.put(head2.getBytes());
// http协议中响应头和响应数据中有一个空行
buffer.put("\r\n".getBytes());
buffer.put(content.getBytes());
buffer.flip();
channel.write(buffer);
channel.close();
}
}
}
}
}
AIO
package com.crs.echo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @author 刘小江
* @version 1.0
* @date 2021/8/15 13:47
**/
public class AIOServer implements Server{
@Override
public void start(int port) throws IOException {
// 打开aio套接字通道
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(port));
System.out.println("服务器打开连接,端口为:"+port+",等待客户端的连接");
while (true){
// 服务器等待连接
Future<AsynchronousSocketChannel> accept = serverSocketChannel.accept();
try {
AsynchronousSocketChannel socketChannel = accept.get();
System.out.println("服务器与" + socketChannel.getRemoteAddress() + "建立连接");
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> read = socketChannel.read(buffer);
while (!read.isDone()){
// 此处可以继续执行其他的业务逻辑
Thread.sleep(10);
}
// 数据准备好后线程继续处理网络数据
buffer.flip();
byte[] bts = new byte[buffer.limit()];
buffer.get(bts);
System.out.println("接收到的前端数据为"+new String(bts,"utf8"));
// 构建返回的数据
// 构建请求头响应码
String head1="HTTP/1.0 200 OK"+"\r\n";
// 构建请求头返回数据格式
String head2="Content-Type:application/json"+"\r\n";
// 构建返回的数据为前端请求的数据
String content = new String(bts,"utf8");
String resStr = head1 +"\r\n"+ head2+"\r\n"+content;
buffer = ByteBuffer.allocate(resStr.length());
buffer.put(head1.getBytes());
buffer.put(head2.getBytes());
// http协议中响应头和响应数据中有一个空行
buffer.put("\r\n".getBytes());
buffer.put(content.getBytes());
buffer.flip();
Future<Integer> write = socketChannel.write(buffer);
while (!write.isDone()){
Thread.sleep(0);
}
socketChannel.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
BIO指的是同步阻塞IO,就是在读取数据时必须阻塞,一个线程只能处理一个连接,要实现多连接时就必须利用多线程的方式来处理,但是在IO的这一块的处理只能是阻塞的方式,不能高效的利用计算机资源,且连接数过多创建更多线程时也需要消耗大量计算机资源
NIIO指的是同步非阻塞IO,在socket建立连接时不需要阻塞,通过selector选择来轮询就绪事件,多个连接无需再创建多个线程,读取数据方面通过通道channel和缓冲区bytebuffer来读取不用进行数据阻塞,在内核交互角度来说BIO和NIO都是同步的,在编程角度来说同步阻塞的IO的操作就是执行连接和读取数据都只能顺序完成,在执行IO操作时程序不能执行其他事情只能等待,而使用非阻塞的操作则在内核执行IO操作时应用程序可以先做其他事情
AIO指的是异步IO,异步IO下不再是应用程序去向系统内核要数据了,而是内核将数据准备好之后通知应用进程,因此在执行过程中也就没有了阻塞的概念,异步IO在程序代码上的体现就是用socketChannel.read读取数据时是一个异步操作,内核立即返回给你一个结果,数据没有准备好时你可以先做其他事情,内核告诉你数据准备好了你再继续处理
需要强调的是我们的java程序和操作系统之间还隔着java虚拟机,在理解IO模型的时候需要考虑IO模型是针对jvm的操作模型,而jvm封装对IO模型的使用并提供给我们比较方便使用的api
本文详细介绍了Java中三种不同的网络IO模型:BIO、NIO和AIO。BIO是同步阻塞模型,每个连接都需要一个新线程,资源消耗大。NIO则是同步非阻塞,通过Selector处理多个连接,避免了线程创建开销。AIO则是异步IO,内核在数据准备好后主动通知应用,提高了效率。通过示例代码展示了三种模型的实现过程。

2389

被折叠的 条评论
为什么被折叠?



