Java网络编程-深入理解BIO、NIO
2023-12-13 04:24:52
深入理解BIO与NIO
BIO
BIO 为 Blocked-IO(阻塞 IO),在 JDK1.4 之前建立网络连接时,只能使用 BIO
使用 BIO 时,服务端会对客户端的每个请求都建立一个线程进行处理,客户端向服务端发送请求后,先咨询服务端是否有线程响应,如果没有就会等待或者被拒绝
BIO 基本使用代码:
服务端:
public class TCPServer {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象
System.out.println("服务端 启动....");
System.out.println("初始化端口 7777 ");
ServerSocket ss = new ServerSocket(7777); //端口号
while (true) {
// 2.监听客户端
Socket s = ss.accept(); //阻塞
// 3.从连接中取出输入流来接收消息
InputStream is = s.getInputStream(); //阻塞
byte[] b = new byte[10];
is.read(b);
String clientIP = s.getInetAddress().getHostAddress();
System.out.println(clientIP + "说:" + new String(b).trim());
// 4.从连接中取出输出流并回话
OutputStream os = s.getOutputStream();
os.write("服务端回复".getBytes());
// 5.关闭
s.close();
}
}
}
客户端:
public class TCPClient {
public static void main(String[] args) throws Exception {
while (true) {
// 1.创建Socket对象
Socket s = new Socket("127.0.0.1", 7777);
// 2.从连接中取出输出流并发消息
OutputStream os = s.getOutputStream();
System.out.println("请输入:");
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
os.write(msg.getBytes());
// 3.从连接中取出输入流并接收回话
InputStream is = s.getInputStream(); //阻塞
byte[] b = new byte[20];
is.read(b);
System.out.println("客户端发送消息:" + new String(b).trim());
// 4.关闭
s.close();
}
}
}
BIO 缺点:
- Server 端会为客户端的每一个连接请求都创建一个新的线程进行处理,如果客户端连接请求数量太多,则会创建大量线程
NIO
从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),NIO 弥补了 BIO 的不足,在服务端不需要为客户端大量的请求而建立大量的处理线程,只需要用很少的线程就可以处理很多客户端请求
NIO 和 BIO 有着相同的目的和作用,但是它们的实现方式完全不同;
- BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 IO 的效率比流 IO 高很多。
- NIO 是非阻塞式的,这一点跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸缩性网络。
NIO 有三大核心部分
:
- Channel通道
- Buffer缓冲区
- Selector选择器
使用 NIO 时,数据是基于 Channel
和 Buffer
进行操作的,数据从 Channel
被读取到 Buffer
或者相反,Selector 用于监听多个 Channel
通道的事件(连接事件、读写事件),通过 Selector 就可以实现单个线程来监听多个客户端通道
NIO 中的 Channel 用来建立到目标的一个连接,在 BIO 中流是单向的,例如 FileInputStream 只能进行读取操作,而在 NIO 中 Channel 是双向的,既可以读也可以写
NIO 工作流程图如下:Server 端通过单线程来监听多个客户端 Channel 通道中的事件并进行处理
NIO 使用示例
服务端:
public class NIOServer {
public static void main(String[] args) throws Exception {
// 1. 开启一个ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2. 开启一个Selector选择器
Selector selector = Selector.open();
// 3. 绑定端口号8888
System.out.println("服务端 启动....");
System.out.println("初始化端口 8888 ");
serverSocketChannel.bind(new InetSocketAddress(8888));
// 4. 配置非阻塞方式
serverSocketChannel.configureBlocking(false);
// 5. Selector选择器注册ServerSocketChannel通道,绑定连接操作
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6. 循环执行:监听连接事件及读取数据操作
while (true) {
// 6.1 监控客户端连接:selecto.select()方法返回的是客户端的通道数,如果为0,则说明没有客户端连接。
if (selector.select(2000) == 0) {
System.out.println("服务端等待客户端连接中~");
continue;
}
// 6.2 得到SelectionKey,判断通道里的事件
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
// 遍历所有SelectionKey
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 客户端连接请求事件
if (key.isAcceptable()) {
System.out.println("服务端处理客户端连接事件:OP_ACCEPT");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 服务端建立与客户端之间的连接通道 SocketChannel,并且将该通道注册到 Selector 中,监听该通道的读事件
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
// 读取客户端数据事件
if (key.isReadable()) {
// 数据在通道中,先拿到通道
SocketChannel channel = (SocketChannel) key.channel();
// 取到一个缓冲区,nio读写数据都是基于缓冲区。
ByteBuffer buffer = (ByteBuffer) key.attachment();
// 从通道中将客户端发来的数据读到缓冲区
channel.read(buffer);
System.out.println("客户端数据长度:" + buffer.array().length);
System.out.println("客户端发来数据:" + new String(buffer.array()));
}
// 6.3 手动从集合中移除当前key,防止重复处理
keyIterator.remove();
}
}
}
}
客户端:
public class NIOClient {
public static void main(String[] args) throws Exception {
// 1. 得到一个网络通道
SocketChannel channel = SocketChannel.open();
// 2. 设置非阻塞方式
channel.configureBlocking(false);
// 3. 提供服务器端的IP地址和端口号
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
// 4. 连接服务器端,如果用connect()方法连接服务器不成功,则用finishConnect()方法进行连接
if (!channel.connect(address)) {
// 因为接需要花时间,所以用while一直去尝试连接。在连接服务端时还可以做别的事,体现非阻塞。
while (!channel.finishConnect()) {
// nio 作为非阻塞式的优势,如果服务器没有响应(不启动服务端),客户端不会阻塞,最后会报错,客户端尝试连接服务器连不上。
System.out.println("客户端等待连接建立时,执行其他任务~");
}
}
// 5. 得到一个缓冲区并存入数据
String msg = "客户端发送消息:hello";
ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes());
// 6. 发送数据
channel.write(writeBuf);
// 阻止客户端停止,否则服务端也会停止。
System.in.read();
}
}
AIO
JDK 7 引入了 Asynchronous IO,即 AIO,叫做异步不阻塞的 IO,也可以叫做 NIO2
在进行 IO 编程中,常用到两种模式:Reactor模式 和 Proactor 模式
- NIO采用 Reactor 模式,当有事件触发时,服务器端得到通知,进行相应的处理
- AIO采用 Proactor 模式,引入异步通道的概念,简化了程序编写,一个有效的请求才启动一个线程,它的特点是先由操作系统完成后,才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
文章来源:https://blog.csdn.net/qq_45260619/article/details/134884590
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!