NIO模型

NIO模型示例如下:

  • Acceptor注册Selector,监听accept事件
  • 当客户端连接后,触发accept事件
  • 服务器构建对应的Channel,并在其上注册Selector,监听读写事件
  • 当发生读写事件后,进行相应的读写处理

NIO优缺点

  • 优点

    • 性能瓶颈高
  • 缺点
    • 模型复杂
    • 编码复杂
    • 需处理半包问题

NIO的优缺点和BIO就完全相反了!性能高,不用一个连接就建一个线程,可以一个线程处理所有的连接!相应的,编码就复杂很多,从上面的代码就可以明显体会到了。还有一个问题,由于是非阻塞的,应用无法知道什么时候消息读完了,就存在了半包问题!

半包问题

简单看一下下面的图就能理解半包问题了!

我们知道TCP/IP在发送消息的时候,可能会拆包(如上图1)!这就导致接收端无法知道什么时候收到的数据是一个完整的数据。例如:发送端分别发送了ABC,DEF,GHI三条信息,发送时被拆成了AB,CDRFG,H,I这四个包进行发送,接受端如何将其进行还原呢?在BIO模型中,当读不到数据后会阻塞,而NIO中不会!所以需要自行进行处理!例如,以换行符作为判断依据,或者定长消息发生,或者自定义协议!

NIO虽然性能高,但是编码复杂,且需要处理半包问题!为了方便的进行NIO开发,就有了Reactor模型!

Reactor模型

Reactor中的组件

  • Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;
  • Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;
  • Acceptor:Handler的一种,绑定了ACCEPT事件,当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。

对应上面的NIO代码来看:

  • Reactor:相当于有分发功能的Selector
  • Acceptor:NIO中建立连接的那个判断分支
  • Handler:消息读写处理等操作类

代码实现

服务端 Reactor.java

package com.test1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator; public class Reactor implements Runnable { private ServerSocketChannel serverSocketChannel = null; private Selector selector = null; public Reactor() {
try {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
SelectionKey selectionKey = serverSocketChannel.register(selector,
SelectionKey.OP_ACCEPT);
selectionKey.attach(new Acceptor());
System.out.println("服务器启动正常!");
} catch (IOException e) {
System.out.println("启动服务器时出现异常!");
e.printStackTrace();
}
} public void run() {
while (true) {
try {
selector.select(); Iterator<SelectionKey> iter = selector.selectedKeys()
.iterator();
while (iter.hasNext()) {
SelectionKey selectionKey = iter.next();
dispatch((Runnable) selectionKey.attachment());
iter.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
} public void dispatch(Runnable runnable) {
if (runnable != null) {
runnable.run();
}
} public static void main(String[] args) {
new Thread(new Reactor()).start();
} class Acceptor implements Runnable {
public void run() {
try {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
System.out.println("接收到来自客户端("
+ socketChannel.socket().getInetAddress()
.getHostAddress() + ")的连接");
new Handler(selector, socketChannel);
} } catch (IOException e) {
e.printStackTrace();
}
}
}
} class Handler implements Runnable { private static final int READ_STATUS = 1; private static final int WRITE_STATUS = 2; private SocketChannel socketChannel; private SelectionKey selectionKey; private int status = READ_STATUS; public Handler(Selector selector, SocketChannel socketChannel) {
this.socketChannel = socketChannel;
try {
socketChannel.configureBlocking(false);
selectionKey = socketChannel.register(selector, 0);
selectionKey.interestOps(SelectionKey.OP_READ);
selectionKey.attach(this);
selector.wakeup();
} catch (IOException e) {
e.printStackTrace();
}
} public void run() {
try {
if (status == READ_STATUS) {
read();
selectionKey.interestOps(SelectionKey.OP_WRITE);
status = WRITE_STATUS;
} else if (status == WRITE_STATUS) {
process();
selectionKey.cancel();
System.out.println("服务器发送消息成功!");
}
} catch (IOException e) {
e.printStackTrace();
}
} public void read() throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
System.out.println("接收到来自客户端("
+ socketChannel.socket().getInetAddress().getHostAddress()
+ ")的消息:" + new String(buffer.array()));
} public void process() throws IOException {
String content = "Hello World!";
ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
socketChannel.write(buffer);
}
}

客户端 Client.java

package com.test1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; public class Client { ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
ByteBuffer readBuffer = ByteBuffer.allocate(1024); public void start() throws IOException {
// 打开socket通道
SocketChannel sc = SocketChannel.open();
// 设置为非阻塞
sc.configureBlocking(false);
// 连接服务器地址和端口
//sc.connect(new InetSocketAddress("localhost", 8001));
sc.connect(new InetSocketAddress("localhost", 8888));
// 打开选择器
Selector selector = Selector.open();
// 注册连接服务器socket的动作
sc.register(selector, SelectionKey.OP_CONNECT); Scanner scanner = new Scanner(System.in);
while (true) {
// 选择一组键,其相应的通道已为 I/O 操作准备就绪。
// 此方法执行处于阻塞模式的选择操作。
selector.select();
// 返回此选择器的已选择键集。
Set<SelectionKey> keys = selector.selectedKeys();
System.out.println("keys=" + keys.size());
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
// 判断此通道上是否正在进行连接操作。
if (key.isConnectable()) {
sc.finishConnect();
sc.register(selector, SelectionKey.OP_WRITE);
System.out.println("server connected...");
break;
} else if (key.isWritable()) { // 写数据
System.out.print("please input message:");
String message = scanner.nextLine();
// ByteBuffer writeBuffer =
// ByteBuffer.wrap(message.getBytes());
writeBuffer.clear();
writeBuffer.put(message.getBytes());
// 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
writeBuffer.flip();
sc.write(writeBuffer); // 注册写操作,每个chanel只能注册一个操作,最后注册的一个生效
// 如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来
// int interestSet = SelectionKey.OP_READ |
// SelectionKey.OP_WRITE;
// 使用interest集合
sc.register(selector, SelectionKey.OP_READ);
sc.register(selector, SelectionKey.OP_WRITE);
sc.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) {// 读取数据
System.out.print("receive message:");
SocketChannel client = (SocketChannel) key.channel();
// 将缓冲区清空以备下次读取
readBuffer.clear();
int num = client.read(readBuffer);
System.out.println(new String(readBuffer.array(), 0, num));
// 注册读操作,下一次读取
sc.register(selector, SelectionKey.OP_WRITE);
}
}
}
} public static void main(String[] args) throws IOException {
new Client().start();
}
}

java 高性能Server —— Reactor模型单线程版的更多相关文章

  1. Reactor模型-单线程版

    Reactor模型是典型的事件驱动模型.在网络编程中,所谓的事件当然就是read.write.bind.connect.close等这些动作了.Reactor模型的实现有很多种,下面介绍最基本的三种: ...

  2. Java网络编程学习A轮_08_NIO的Reactor模型

    参考资料: 了解 Java NIO 的 Reactor 模型,大神 Doug Lea 的 PPT Scalable IO in Java 必看:http://gee.cs.oswego.edu/dl/ ...

  3. Reactor模型-多线程程版

    1.概述 在Reactor单线程版本的设计中,I/O任务乃至业务逻辑都由Reactor线程来完成,这无疑增加了Reactor线程的负担,高负载情况下必然会出现性能瓶颈.此外,对于多处理器的服务器来说, ...

  4. I/O模型之四:Java 浅析I/O模型(BIO、NIO、AIO、Reactor、Proactor)

    目录: <I/O模型之一:Unix的五种I/O模型> <I/O模型之二:Linux IO模式及 select.poll.epoll详解> <I/O模型之三:两种高性能 I ...

  5. Reactor模型

    Reactor模型 原文地址:http://www.ivaneye.com/2016/07/23/iomodel.html 无处不在的C/S架构 在这个充斥着云的时代,我们使用的软件可以说99%都是C ...

  6. 服务器端高性能的IO模型 转自酷勤网

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(BlockingIO):即传统的IO模型. (2)同步非阻塞IO(Non-blockingIO):默认创建的soc ...

  7. (转)高性能I/O模型

    本文转自:http://www.cnblogs.com/fanzhidongyzby/p/4098546.html 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO ...

  8. 高性能IO —— Reactor(反应器)模式

    讲到高性能IO绕不开Reactor模式,它是大多数IO相关组件如Netty.Redis在使用的IO模式, 为什么需要这种模式,它是如何设计来解决高性能并发的呢? 最最原始的网络编程思路就是服务器用一个 ...

  9. Redis 源码简洁剖析 09 - Reactor 模型

    Reactor 模型 事件驱动框架 Redis 如何实现 Reactor 模型 事件的数据结构:aeFileEvent 主循环:aeMain 函数 事件捕获与分发:aeProcessEvents 函数 ...

随机推荐

  1. [C++] 所有该类的对象共享静态类成员变量

    问:智能指针可以对指针的引用数量进行计数,一个智能指针释放时,别的智能指针怎么知道的? 同一类的对象共享同一变量最简单的方法是静态变量: 不像普通的变量,静态成员变量是被所有类对象共享的,不同的对象可 ...

  2. Informatica参考

    近日在做informatica项目时,要求参照informatica中原有的mapping方式实现.那么当知道源表或者目标表或者是映射时,如何快速的找出分散在不同的Subject的mapping或者是 ...

  3. Sass-变量计算

    在 Sass 中除了可以使用数值进行运算之外,还可以使用变量进行计算,其实在前面章节的示例中也或多或少的向大家展示了.在 Sass 中使用变量进行计算,这使得 Sass 的数学运算功能变得更加实用.一 ...

  4. Vue-鼠标按键修饰符

    left .right .middle 这些修饰符会限制处理函数仅响应特定的鼠标按钮. 如下例子 <div id="app"> <input type=" ...

  5. Go 数组(1)

    1.一旦声明,数组里存储的数据类型和数组长度就都不能改变了.如果需要存储更多的元素, 就需要先创建一个更长的数组,再把原来数组里的值复制到新数组里. 例如: ]int 2.使用数组字面量声明数组 // ...

  6. Java二级上机训练

    NCRE上机训练一 import javax.swing.JOptionPane; /** * 并完成两个整数的输入,计算乘积,最后按确定键退出程序. */ public class Java_1 { ...

  7. mysql 生成max+1编号

    #sql info表插入一条数据,number字段根据info表最大number+1插入,若为初始插入,number为100000#在获取本表number最大值时,mysql不允许直接查询本表获取最大 ...

  8. element-ui中使用el-radio单选切换表格

    应用场景:点击单选,切换表格数据 代码: data里的数据:(这里的值是默认选中的   和label值是对应的) change事件操作切换,这里面添加@click事件是不生效的,注意...

  9. SQL SERVER视图对查询效率的提高

    SQL SERVER视图不仅可以实现许多我们需要的功能,而且对于SQL SERVER查询效率的提高也有帮助,下面一起来了解一下. 有两张数据表:A和B,其中A的记录为2万条左右,而B中的数据为200万 ...

  10. 连载 3:利用 matlab计算卷积