Python 官方文档:入门教程 => 点击学习
目录前言正文一. Bio二. Non Blocking IO三. IO多路复用四. NIO总结前言 本篇文章会对Java中的网络IO模型的概念进行解释,并给出具体的Java代码实现
本篇文章会对Java中的网络IO模型的概念进行解释,并给出具体的Java代码实现,主要涉及如下部分。
在开始本篇文章内容之前,有一个简单的关于Socket的知识需要说明:在进行网络通信的时候,需要一对Socket,一个运行于客户端,一个运行于服务端,同时服务端还会有一个服务端Socket,用于监听客户端的连接。下图进行一个简单示意。
那么整个通信流程如下所示。
需要知道的就是,在客户端与服务端通信的过程中,出现了三种socket,分别是。
(注:上述中的socket,可以被称为套接字,也可以被称为文件描述符。)
BIO,即同步阻塞IO模型。用户进程调用read时发起IO操作,此时用户进程由用户态转换到内核态,只有在内核态中将IO操作执行完后,才会从内核态切换回用户态,这期间用户进程会一直阻塞。
BIO示意图如下。
简单的BIO的Java编程实现如下。
服务端实现
public class BiOServer {
public static void main(String[] args) throws IOException {
// 创建listen-Socket
ServerSocket listenSocket = new ServerSocket(8080);
// 进入监听状态,是一个阻塞状态
// 有客户端连接时从监听状态返回
// 并创建代表这个客户端的client-socket
Socket clientSocket = listenSocket.accept();
// 获取client-socket输入流
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
// 读取客户端发送的数据
// 如果数据没准备好,会进入阻塞状态
String data = bufferedReader.readLine();
System.out.println(data);
// 获取client-socket输出流
BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(clientSocket.getOutputStream()));
// 服务端向客户端发送数据
bufferedWriter.write("来自服务端的返回数据\n");
// 刷新流
bufferedWriter.flush();
}
}
客户端实现
public class Bioclient {
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 8080;
public static void main(String[] args) throws IOException {
// 客户端创建connect-socket
Socket connectSocket = new Socket(SERVER_IP, SERVER_PORT);
// 获取connect-socket输出流
BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(connectSocket.getOutputStream()));
// 客户端向服务端发送数据
bufferedWriter.write("来自客户端的请求数据\n");
// 刷新流
bufferedWriter.flush();
// 获取connect-socket输入流
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(connectSocket.getInputStream()));
// 读取服务端发送的数据
String returnData = bufferedReader.readLine();
System.out.println(returnData);
}
}
BIO的问题就在于服务端在accept时是阻塞的,并且在主线程中,一次只能accept一个Socket,accept到Socket后,读取客户端数据时又是阻塞的。
Non Blocking IO,即同步非阻塞IO。是用户进程调用read时,用户进程由用户态转换到内核态后,此时如果没有系统资源数据能够被读取到内核缓冲区中,返回read失败,并从内核态切换回用户态。也就是用户进程发起IO操作后会立即得到一个操作结果。
Non Blocking IO示意图如下所示。
简单的Non Blocking IO的Java编程实现如下。
public class NonbioServer {
public static final List<SocketChannel> clientSocketChannels = new ArrayList<>();
public static void main(String[] args) throws Exception {
// 客户端创建listen-socket管道
// 管道支持非阻塞模式和同时读写
ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞模式
listenSocketChannel.configureBlocking(false);
// 绑定监听的端口号
listenSocketChannel.socket().bind(new InetSocketAddress(8080));
// 在子线程中遍历clientSocketChannels并读取客户端数据
handleSocketChannels();
while (true) {
// 非阻塞方式监听客户端连接
// 如果无客户端连接则返回空
// 有客户端连接则创建代表这个客户端的client-socket管道
SocketChannel clientSocketChannel = listenSocketChannel.accept();
if (clientSocketChannel != null) {
// 设置为非阻塞模式
clientSocketChannel.configureBlocking(false);
// 添加到clientSocketChannels中
// 用于子线程遍历并读取客户端数据
clientSocketChannels.add(clientSocketChannel);
} else {
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
}
public static void handleSocketChannels() {
new Thread(() -> {
while (true) {
// 遍历每一个client-socket管道
Iterator<SocketChannel> iterator = clientSocketChannels.iterator();
while (iterator.hasNext()) {
SocketChannel clientSocketChannel = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int read = 0;
try {
// 将客户端发送的数据读取到ByteBuffer中
// 这一步的操作也是非阻塞的
read = clientSocketChannel.read(byteBuffer);
} catch (IOException e) {
// 移除发生异常的client-socket管道
iterator.remove();
e.printStackTrace();
}
if (read == 0) {
System.out.println("客户端数据未就绪");
} else {
System.out.println("客户端数据为:" + new String(byteBuffer.array()));
}
}
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}).start();
}
}
上述是Non Blocking IO的一个简单服务端的实现,相较于BIO,服务端在accept时是非阻塞的,在读取客户端数据时也是非阻塞的,但是还是存在如下问题。
在上述的BIO和Non Blocking IO中,一次系统调用,只会获取一个IO的状态,而如果采取IO多路复用机制,则可以一次系统调用获取多个IO的状态。
也就是获取多个IO的状态可以复用一次系统调用。
最简单的IO多路复用方式是基于select模型实现,步骤如下。
换言之,IO多路复用中,只需要一次系统调用,IO多路复用器就可以告诉用户进程,哪些IO已经准备就绪可以进行操作了,而如果不采用IO多路复用,则需要用户进程自己遍历每个IO并调用accept() 或者read() 方法去判断,且一次accept() 或者read() 方法调用只能判断一个IO。
NIO,即New IO。关于NIO,有如下三大组件。
NIO的代码实现如下所示。
服务端实现
public class NioServer {
private static Selector selector;
public static void main(String[] args) {
try {
// 开启并得到多路复用器
selector = Selector.open();
// 服务端创建listen-socket管道
ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞模式
listenSocketChannel.configureBlocking(false);
// 为管道绑定端口
listenSocketChannel.socket().bind(new InetSocketAddress(8080));
// 将listen-socket管道注册到多路复用器上,并指定监听ACCEPT事件
listenSocketChannel.reGISter(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 获取发生的事件,这个操作是阻塞的
selector.select();
// 拿到有事件发生的SelectionKey集合
// SelectionKey表示管道与多路复用器的绑定关系
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍历每个发生的事件,然后判断事件类型
// 根据事件类型,进行不同的处理
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isAcceptable()) {
// 处理客户端连接事件
handlerAccept(selectionKey);
} else if (selectionKey.isReadable()) {
// 处理客户端数据可读事件
handlerRead(selectionKey);
}
}
LockSupport.parkNanos(1000 * 1000 * 1000);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handlerAccept(SelectionKey selectionKey) {
// 从事件中获取到listen-socket管道
ServerSocketChannel listenSocketChannel = (ServerSocketChannel) selectionKey.channel();
try {
// 为连接的客户端创建client-socket管道
SocketChannel clientSocketChannel = listenSocketChannel.accept();
// 设置为非阻塞模式
clientSocketChannel.configureBlocking(false);
// 将client-socket管道注册到多路复用器上,并指定监听READ事件
clientSocketChannel.register(selector, SelectionKey.OP_READ);
// 给客户端发送数据
clientSocketChannel.write(ByteBuffer.wrap("连接已建立\n".getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handlerRead(SelectionKey selectionKey) {
// 从事件中获取到client-socket管道
SocketChannel clientSocketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
// 读取客户端数据
int read = clientSocketChannel.read(byteBuffer);
if (read <= 0) {
// 关闭管道
clientSocketChannel.close();
// 从多路复用器移除绑定关系
selectionKey.cancel();
} else {
System.out.println(new String(byteBuffer.array()));
}
} catch (IOException e1) {
try {
// 关闭管道
clientSocketChannel.close();
} catch (IOException e2) {
e2.printStackTrace();
}
// 从多路复用器移除绑定关系
selectionKey.cancel();
e1.printStackTrace();
}
}
}
客户端实现
public class NioClient {
private static Selector selector;
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 8080;
public static void main(String[] args) {
try {
// 开启并得到多路复用器
selector = Selector.open();
// 创建connect-socket管道
SocketChannel connectSocketChannel = SocketChannel.open();
// 设置为非阻塞模式
connectSocketChannel.configureBlocking(false);
// 设置服务端IP和端口
connectSocketChannel.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT));
// 将connect-socket管道注册到多路复用器上,并指定监听CONNECT事件
connectSocketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
// 获取发生的事件,这个操作是阻塞的
selector.select();
// 拿到有事件发生的SelectionKey集合
// SelectionKey表示管道与多路复用器的绑定关系
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍历每个发生的事件,然后判断事件类型
// 根据事件类型,进行不同的处理
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isConnectable()) {
// 处理连接建立事件
handlerConnect(selectionKey);
} else if (selectionKey.isReadable()) {
// 处理服务端数据可读事件
handlerRead(selectionKey);
}
}
LockSupport.parkNanos(1000 * 1000 * 1000);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handlerConnect(SelectionKey selectionKey) throws IOException {
// 拿到connect-socket管道
SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
if (connectSocketChannel.isConnectionPending()) {
connectSocketChannel.finishConnect();
}
// 设置为非阻塞模式
connectSocketChannel.configureBlocking(false);
// 将connect-socket管道注册到多路复用器上,并指定监听READ事件
connectSocketChannel.register(selector, SelectionKey.OP_READ);
// 向服务端发送数据
connectSocketChannel.write(ByteBuffer.wrap("客户端发送的数据\n".getBytes()));
}
private static void handlerRead(SelectionKey selectionKey) {
// 拿到connect-socket管道
SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
// 读取服务端数据
int read = connectSocketChannel.read(byteBuffer);
if (read <= 0) {
// 关闭管道
connectSocketChannel.close();
// 从多路复用器移除绑定关系
selectionKey.cancel();
} else {
System.out.println(new String(byteBuffer.array()));
}
} catch (IOException e1) {
try {
// 关闭管道
connectSocketChannel.close();
} catch (IOException e2) {
e2.printStackTrace();
}
// 从多路复用器移除绑定关系
selectionKey.cancel();
e1.printStackTrace();
}
}
}
本篇文章中所讨论的IO模型,都是同步IO模型,而何谓同步异步,何谓阻塞非阻塞,可以总结如下。
实际上在Java中是有对异步IO(AIO)做支持,但是AIO依赖操作系统的底层实现,而目前Linux对AIO的支持不成熟,所以AIO的使用并不多,像主流的网络应用框架Netty也都没有使用到AIO。
以上就是Java IO网络模型实现解析的详细内容,更多关于Java IO网络模型的资料请关注编程网其它相关文章!
--结束END--
本文标题: Java IO网络模型实现解析
本文链接: https://www.lsjlt.com/news/200247.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0