关于IO模式,线程模型?
Java的io模型分为三种,(BIO,NIO,AIO),Netty现在主要推荐的(NIO),剩下的两个曾经实现,现在不推荐使用。IO模型于线程模型之间的关系,如下表:
BIO | NIO | AIO |
---|---|---|
Thread-Per-Connection | Reactor | Proactor |
- Thread-Per-Connection :一个客户端对应一个线程处理 读>业务逻辑>写。
- Reactor,因为是多路复用,所以有一个select选择器,可以注册事件和获取相应事件,则其核心流程:注册感兴趣的事件 -> 扫描是否有感兴趣的事件发生 -> 事件发生之后做出相应的逻辑处理。具体不同注册的事件如下表:
client/server | SocketChannel/ServerSocketCannel | OP_ACCEPT | OP_CONNECT | OP_WRITE | OP_READ |
---|---|---|---|---|---|
client | SocketChannel | - | 1 | 1 | 1 |
server | ServerSocketChannel | 1 | - | - | - |
server | SocketChannel | - | - | 1 | 1 |
- Proactor【现在还不知道】
还是说说Reactor把,毕竟是Netty总结:
Reactor开发模式有三种:
具体可以从线程种类(线程的工作不同分为不同的种类如:++处理连接请求的线程++,++处理业务逻辑的线程++==自己瞎理解的==),线程数量两个维度来展开。Reactor的三种模型设计:
Reactor单线程模式
线程不分种类,线程数量一个。即:一个线程搞定客户端的连接请求和客户端的读写。
NIO代码实现:
/**
* 顺便说下 OP_WRITE 的情况。
* 在写数据到 SocketChannel 之前,没必要先调用 key.isWritable(),而是应该在调用 channel.write(buffer) 的时候,
* 如果返回值是 0 或者和实际的缓存区大小不一致的值,这个时候才去注册监听 OP_WRITE 事件,因为此时往往是由于数据接收方的问题或网络问题导致写操作挂起了。
* @throws IOException
*/
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//配置非阻塞
serverSocketChannel.configureBlocking(false);
//绑定
serverSocketChannel.bind(new InetSocketAddress(9898));
//选择器,绑定选择器,和选择的事件
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//轮询获取选择器上已经准备就绪的事件>0有一个key准备就绪
while (selector.select() > 0) {
//获取当前选择器中所有注册的已经就绪的监听事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
//接收状态就绪
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
//可以给这个channel关联一个buffer
//socketChannel.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
//1--------------------------------
//读就绪状态的通道
SocketChannel socketChannel = (SocketChannel) key.channel();
//读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = socketChannel.read(buffer)) != 0) {
if (len == -1) {
System.out.println("-1");
//key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); 与 key.cancel();同意,不删除就一直响应可读事件。输出-1
key.cancel();
break;
}
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
//2---------------------------------
}
//取消选择键SelectionKey
iterator.remove();
}
}
serverSocketChannel.close();
}
Netty中代码体现:部分
NioEventLoopGroup eventGroup = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup);
出现的问题:业务处理很慢的情况下,连接请求进来可能超时处理不到。
非主从Reactor多线程模式
一个线程处理链接请求和读写请求,只不过这里的读写的逻辑处理方法其他线程中去处理了。
Nio代码,在上面代码修改,把//1---- //2---这部分逻辑放在其他线程中处理就可以了。
Netty中代码体现:
NioEventLoopGroup eventGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup);
主从Reactor多线程模式
从这里开起区分线程种类了:一类线程处理连接请求,一类是处理读写请求的。
NIO中代码实现:
实现思路:首先开两个及其以上Selector
,其中一个serverSocketChannel注册OP_ACCEPT,剩下的socketChannel的OP_WRITE和OP_READ事件,每个Selector在不同的线程中轮训遍历准备好的事件。Netty中代码体现:
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workGroup);
其中关于处理连接请求的线程数的问题?如果服务端只开了一个端口的话,处理连接请求的线程用一个就可以了,所以上面的Nio代码实现思路中默认只开一个端口。只用一个线程轮训一个Selector就可以了,所以在Netty中NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
可以直接写入1,不写开一个端口其实也只是用到了一个线程。