1. BIO
1.1 BIO概念
即 Blocking IO 同步阻塞IO
阻塞:在单线程的环境下,IO操作没有完成的话,当前线程不能进行其他操作(流的一个特性是阻塞的)
1.2 BIO原理
BIO模型中,accept得到一个Socket之后,需要创建线程对每一个Socket进行处理。
1.3 BIO问题
- 当客户端多了之后,服务端对应的处理线程也会增加,每一个线程都会占用栈空间和CPU资源
- 针对每一个Socket连接创建一个对应的线程,Socket是连接上了,但是它不一定进行IO操作。IO是发生在Socket连接之后,也就是说Socket连接并不一定会发生IO操作,这样会造成资源浪费。
总结来说
Socket开启了一个线程,服务端就会进行一个CPU资源的争抢,这种争抢是无意义的
线程之间的切换运行,会频繁的切换上下文,性能效率会受到影响
并发很多的时候,CPU的主要任务好像就成了处理这些无意义的Socket线程了
1.4 BIO优化
- 使用多线程池,实现BIO操作的伪非阻塞IO
这样处理的好处有
- 不需要频繁的开启销毁线程
- 初始化一定数目的线程,放入线程池,多余的线程会进入到一个队列
- 针对线程多了,对服务器CPU、资源的争夺问题
思路:多线程是必然,问题是线程创建的时机与数量,尽量让线程的数量少一点
优化方案:Socket连接到来时,不创建线程;需要IO操作的时候,才会创建线程
2. NIO
2.1 NIO概念
即 Non-Blocking IO 同步非阻塞IO
非阻塞:在单线程的环境下,IO操作没有完成的话,当前线程能进行其他操作
2.2 NIO原理
不要针对每个Socket请求去创建一个线程,而是当进行IO操作的时候,再创建一个对应的线程
与BIO相比,NIO多了以下三种优化处理
- Selector选择器
每当有客户端的连接过来,就会先把它注册到Selector中 - Buffer缓冲区
提高读写性能 - Channel多路复用
预先创建好链路,客户端连接时直接使用Channel中的链路即可。传输过程中的所有数据都是从Channel中走的。
2.4 代码实现
Java代码实现NIO的server端如下:
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
private static int port = 8000;
private static InetSocketAddress address = null;
private static Selector selector;
private static ByteBuffer buffer = ByteBuffer.allocate(1024);
public static void main(String[] args) throws IOException {
address = new InetSocketAddress(port);
// Channel多路复用
ServerSocketChannel server = ServerSocketChannel.open();
//监听一个端口
server.bind(address);
// 设置为非阻塞
server.configureBlocking(false);
// 初始化Selector
selector = Selector.open();
// 把路和selector关联上
server.register(selector, SelectionKey.OP_ACCEPT);// 表示客户端可以进行连接请求了
// 使用selector进行循环判断,看哪一个key被选中,哪些key可以进行操作
// 轮询操作
while (true) {
int count = selector.select();
if (count == 0) {
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//对key进行后续操作
process(key);
iterator.remove();
}
}
}
// 接下来根据key进行连接的创建,读写操作
private static void process(SelectionKey key) throws IOException {
// 判断这个key的状态,可接受、可读写?
if (key.isAcceptable()) {
// 创建连接对象,客户端对象 下面代表服务端的channel
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 将key注册为可读
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 创建连接对象,客户端对象 下面代表服务端的channel
SocketChannel client = (SocketChannel) key.channel();
// 根据客户端进行接下来的操作 将客户端传来的数据读到buffer中
int len = client.read(buffer);
if (len > 0) {
buffer.flip();// 复位 对缓冲区对象中的数据进行锁定
String text = new String(buffer.array(), 0, len);
System.out.println(text);
}
// 读完之后,将key注册为可写
client.register(selector, SelectionKey.OP_WRITE);
buffer.clear();
} else if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
client.write(buffer.wrap("获取数据成功".getBytes()));
client.close();
}
}
}
以上信息参考了咕泡学院的公开课相关资料并进行了一些修改。如有错误,欢迎指正。