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 函数 ...
随机推荐
- jenkins部署的零碎知识
环境要求 1)版本控制子系统(SVN):SVN服务器.项目对应版本库.版本库中钩子程序(提交代码后,触发Jenkins自动打包并部署到应用服务器)(2)持续集成子系统(存在Jenkins的服务器):J ...
- 几种激活Profile的方式
方法一: 选择spring.profiles.active spring.profiles.active=prodspring.profiles.active=dev 方法二: 选择spring.pr ...
- vue,一路走来(2)--路由vue-router
安装 Mint UI cnpm install mint-ui --save 如果你的项目会用到 Mint UI 里较多的组件,最简单的方法就是把它们全部引入.此时需要在入口文件 main.js 中: ...
- 不小心执行 rm -f,该如何恢复?
每当我们在生产环境服务器上执行rm命令时,总是提心吊胆的,因为一不小心执行了误删,然后就要准备跑路了,毕竟人不是机器,更何况机器也有 bug,呵呵. 那么如果真的删除了不该删除的文件,比如数据库.日志 ...
- html5 jquery音乐播放器,play()和pause()不起作用
今天在自己写的页面上加上背景音乐,当我点击图片时可以切换 播放/暂停 用jquery写的,方法总是提示没有pause这个方法! 检查了半天最后发现 你使用的是jquery选择器所以返回的是jquery ...
- Codeforces 958C3 - Encryption (hard) 区间dp+抽屉原理
转自:http://www.cnblogs.com/widsom/p/8863005.html 题目大意: 比起Encryption 中级版,把n的范围扩大到 500000,k,p范围都在100以内, ...
- java上传附件含有%处理或url含有%(URLDecoder: Illegal hex characters in escape (%) pattern - For input string)
在附件名称中含有%的时候,上传附件进行url编码解析的时候会出错,抛出异常: Exception in thread "main" java.lang.IllegalArgumen ...
- ceph优化记录 ceph.conf优化详解
https://cloud.tencent.com/developer/article/1173069 记录一下一些ceph优化的设置,部分内容来自网络,有些请根据自己需求相应修改 1. Kerne ...
- Oracle序列重置
Oracle 中的序列我们一般用来生成流水号,所以需要进行重置(如每天凌晨重置一次),我们虽然可以通过重新编译的方式重置序列,可是这种方法会有弊端,比如导致与该序列相关的存储过程或函数失效等等,需要重 ...
- delphi 加入超链接
delphi 加入超链接//在uses中加入ShellAPI//通过该lpFile参数可以实现链接到主页或ftp站点 ShellExecute(handle,nil,pchar('http://www ...