java 高性能Server —— Reactor模型单线程版
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模型单线程版的更多相关文章
- Reactor模型-单线程版
Reactor模型是典型的事件驱动模型.在网络编程中,所谓的事件当然就是read.write.bind.connect.close等这些动作了.Reactor模型的实现有很多种,下面介绍最基本的三种: ...
- Java网络编程学习A轮_08_NIO的Reactor模型
参考资料: 了解 Java NIO 的 Reactor 模型,大神 Doug Lea 的 PPT Scalable IO in Java 必看:http://gee.cs.oswego.edu/dl/ ...
- Reactor模型-多线程程版
1.概述 在Reactor单线程版本的设计中,I/O任务乃至业务逻辑都由Reactor线程来完成,这无疑增加了Reactor线程的负担,高负载情况下必然会出现性能瓶颈.此外,对于多处理器的服务器来说, ...
- 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 ...
- Reactor模型
Reactor模型 原文地址:http://www.ivaneye.com/2016/07/23/iomodel.html 无处不在的C/S架构 在这个充斥着云的时代,我们使用的软件可以说99%都是C ...
- 服务器端高性能的IO模型 转自酷勤网
服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(BlockingIO):即传统的IO模型. (2)同步非阻塞IO(Non-blockingIO):默认创建的soc ...
- (转)高性能I/O模型
本文转自:http://www.cnblogs.com/fanzhidongyzby/p/4098546.html 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO ...
- 高性能IO —— Reactor(反应器)模式
讲到高性能IO绕不开Reactor模式,它是大多数IO相关组件如Netty.Redis在使用的IO模式, 为什么需要这种模式,它是如何设计来解决高性能并发的呢? 最最原始的网络编程思路就是服务器用一个 ...
- Redis 源码简洁剖析 09 - Reactor 模型
Reactor 模型 事件驱动框架 Redis 如何实现 Reactor 模型 事件的数据结构:aeFileEvent 主循环:aeMain 函数 事件捕获与分发:aeProcessEvents 函数 ...
随机推荐
- Android学习电子书
- Envoy的线程模型[翻译]
Envoy threading Model 关于envoy 代码的底层文档相当稀少.为了解决这个问题我计划编写一系列文档来描述各个子系统的工作.由于是第一篇, 请让我知道你希望其他主题覆盖哪些内容. ...
- 系列(1) Note.Js的安装和部署
一.检测电脑上是否安装node 首先确认下你的node是否已经安装并且配置到了环境变量中了,直接按下快捷键[win+R],调出运行框,然后在里面输入cmd,进入windows的命令窗口. 在命令窗口中 ...
- 从零开始之uboot、移植uboot2017.01(一、移植前的准备)
手边的是一个S5PV210的开发板,想尝试移植一个比较新的uboot 下载最新版本uboot2018. ftp://ftp.denx.de/pub/u-boot/ 编译器下载 http://www.v ...
- 苹果正制造一款疯狂的“16K”VR耳机,效果到底有多牛?
苹果一直以来都有传闻称正在研发一副增强现实眼镜,但今天的一份报告显示,他们希望在虚拟现实领域与Google,微软和Facebook竞争. 据CNET报道,苹果公司着眼于2020年发布的将AR和VR技术 ...
- SpringBoot---注册Servlet,Filter,Listener
1.概述 1.1.当使用 内嵌的Servlet容器(Tomcat.Jetty等)时,将Servlet,Filter,Listener 注册到Servlet容器的方法: 1.1.1.直接注册Bean ...
- centos 6.5 查看 IP
ip 和 ifconfig 两个命令都可以,但现在推荐使用 ip ip addr ifconfig
- js-放大镜效果
jd或者淘宝的具体商品有个放大镜的效果.虽然网上类似插件琳琅满目,应用到项目上有诸多不便,自己抽点时间自己写了个类似插件,积累下代码,何乐而不为呢!!let‘go: 打算把此特效封装成个插件,先把最基 ...
- Adobe Flash Player30.0.0.113离线安装包
远程控制工具 www.iis7.net/a/lm/yczmljgj/ Adobe2017年7月26日宣布,计划终结 Flash 浏览器插件,并在2020年停止开发和分发这款插件.Adobe 建议内容 ...
- 【CF1100F】Ivan and Burgers(线性基,分治)
题意:给定n个数,每个数为c[i],有q个询问,每次询问从第l个到第r个数字的最大xor和 n,q<=5e5,c[i]<=1e6,时限3s 思路:直接线段树维护区间线性基是3个log,会T ...