BIO(Blocking IO)
BIO(Blocking IO):同步阻塞模型,一个客户端连接对应一个处理线程,即:一个线程处理一个客户端请求。
单线程版本: 服务端在处理完第一个客户端的所有事件之前,无法为其他客户端提供服务。
多线程版本:如果出现大量只连接不发数据的话,那么就会一直占用线程,浪费服务器资源。
线程池版本:用线程池根本不能根本的解决问题。假如我们有500个线程组成一个线程池,我们用这500个线程去为客户端服务。那么,假设前500个客户端请求进来,被占满了这500个线程,并且这500个客户端连接只连接不发数据,那么我们的服务端不就崩掉了吗?因为我们服务端最多只能处理500个客户端连接,而你后面进来的,不管多少都会被我们的服务端拒之门外,所以用线程池也不能解决这个问题。
public class SocketServer {
public static void main(String[] args) throws Exception {
//创建了服务端,绑定到了9001端口
ServerSocket serverSocket = new ServerSocket(9001);
while (true) {
System.out.println("等待连接..");
//阻塞方法
Socket clientSocket = serverSocket.accept(); //监听客户端的连接,有客户端的连接代码才会往下走,没有连接就会阻塞在这里
System.out.println("有客户端连接了..");
//1.单线程版本
//handler(clientSocket);
//2.多线程版本
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(clientSocket);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
//3.线程池版本
ExecutorService executorService = Executors.newFixedThreadPool(100);
executorService.execute(new Runnable() {
@Override
public void run() {
try {
handler(clientSocket);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
//handler方法是处理客户端事件的方法,比如客户端发来数据,在这里我们就做打印处理
private static void handler(Socket clientSocket) throws Exception {
byte[] bytes = new byte[1024];
System.out.println("准备read..");
//接收客户端的数据,阻塞方法,没有数据可读时就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read完毕。。");
if (read != -1) {
System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
}
}
}
NIO(Non Blocking IO)
NIO模型1.0版本(早期版本)
一个线程无限循环去list(存放着客户端连接)轮训,检查是否有读写请求,如果有则处理,如果没有跳过。这个版本如果连接数特别多的话,会有大量的无效遍历,假如有1000个客户端连接,只有其中100个有读写事件,那么还是会循环1000次,其中的900次循环都是无效的。
public class NioServer {
// 保存客户端连接
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException {
// 创建NIO ServerSocketChannel,与BIO的serverSocket类似,即创建了服务端并绑定了9001端口
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9001));
// 设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
System.out.println("服务启动成功");
while (true) {
// 非阻塞模式accept方法不会阻塞,否则会阻塞
// NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数
SocketChannel socketChannel = serverSocket.accept();
if (socketChannel != null) { // 如果有客户端进行连接
System.out.println("连接成功");
// 设置SocketChannel为非阻塞
socketChannel.configureBlocking(false);
// 保存客户端连接在List中
channelList.add(socketChannel);
}
// 遍历连接进行数据读取 读写事件
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()) {
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// 非阻塞模式read方法不会阻塞,否则会阻塞
int len = sc.read(byteBuffer);
// 如果有数据,把数据打印出来,整个过程是一个main线程在处理
if (len > 0) {
System.out.println(Thread.currentThread().getName() + " 接收到消息:" + new String(byteBuffer.array()));
} else if (len == -1) { // 如果客户端断开,把socket从集合中去掉
iterator.remove();
System.out.println("客户端断开连接");
}
}
}
}
}
NIO模型2.0版本(启用多路复用器,jdk1.4以上)
客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理,JDK1.4开始引入。这个版本进行了很大程度的优化,当客户端连接以后,如果有读写事件,则会加入一个队列里,处理事件的线程会阻塞等待这个队列里有新的元素之后处理事件。
步骤:
- 创建ServerSocketChannel服务端
- 创建多路复用器Selector( 每个操作系统创建出来的是不一样的,Centos创建的是EPollSelectorProviderImpl,Windows创建的是WindowsSelectorImpl,其实就是Linux的Epoll实例EPollArrayWrapper)
- ServerSocketChannel将建立连接事件注册到Selector中(register方法往EPollArrayWrapper中添加元素)
- 处理事件
- 如果是建立连接事件,则把客户端的读写请求也注册到Selector中
- 如果是读写事件则按业务处理
public class NioSelectorServer {
public static void main(String[] args) throws IOException {
//指定感兴趣的操作类型为 OP_ACCEPT
int OP_ACCEPT = 1 << 4;
System.out.println(OP_ACCEPT);
// 创建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9001));
// 设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
// 打开Selector处理Channel,即创建epoll,这里启用了多路复用器是与NIO1.0版本最大的区别
Selector selector = Selector.open();
// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣(把连接事件注册到多路复用器里面)
SelectionKey selectionKey = serverSocket.register(selector, SelectionKey.OP_ACCEPT);//通过 SelectionKey.OP_ACCEPT 来识别和处理接受连接事件。
System.out.println("服务启动成功");
while (true) {
// 阻塞等待需要处理的事件发生 已注册事件发生后,会执行后面逻辑
selector.select();
// 获取selector中注册的全部事件的 SelectionKey 实例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍历SelectionKey对事件进行处理
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是OP_ACCEPT事件(连接事件),则进行连接获取和事件注册
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接成功");
} else if (key.isReadable()) { // 如果是OP_READ事件(读事件),则进行读取和打印
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
// 如果有数据,把数据打印出来
if (len > 0) {
System.out.println(Thread.currentThread().getName() + "接收到消息:" + new String(byteBuffer.array()));
} else if (len == -1) { // 如果客户端断开连接,关闭Socket
System.out.println("客户端断开连接");
socketChannel.close();
}
}
//从事件集合里删除本次处理的key,防止下次select重复处理
iterator.remove();
}
}
}
}
Redis线程模型文章来源:https://www.toymoban.com/news/detail-492721.html
Redis就是典型的基于epoll的NIO线程模型(nginx也是),epoll实例收集所有事件(连接与读写事件),由一个服务端线程连续处理所有事件命令。文章来源地址https://www.toymoban.com/news/detail-492721.html
到了这里,关于BIO、NIO线程模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!