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. 如何改变string中的字符值?

    string本身是不可变的,因此要改变string中字符,需要如下操作: str := “hello world” s := []byte(str) s[] = ‘o’ str = string(s) ...

  2. CF 187D BRT Contract

    传送门 给了60分的nq暴力还是很资磁的!!! 基本上想的跟正解差不多了但是刚T2去了就没想细节QAQ 大概就是我们逆序求一下每一个点从0时刻开始走到终点需要用的时间f 我们需要找到它遇到的第一个红灯 ...

  3. instanceof是Java的一个二元操作符(运算符)

    instanceof是Java的一个二元操作符(运算符),也是Java的保留关键字.它的作用是判断其左边对象是否为其右边类的实例,返回的是boolean类型的数据.用它来判断某个对象是否是某个Clas ...

  4. 每天一个linux命令:tail(16)

    tail tail命令用于输入文件中的尾部内容,不指定文件时,作为输入信息进行处理.tail命令默认在屏幕上显示指定文件的末尾10行.命令从指定点开始将文件写到标准输出,使用tail命令的-f选项可以 ...

  5. java 反射,类的加载过程以及Classloader类加载器

    首先自定义一个类Person package reflection; public class Person { private String name; public int age; public ...

  6. docker-swarm笔记

    1.部署环境: centos7 创建三节点的 swarm 集群 swarm-manager 是 manager node : 192.168.1.150 swarm-worker1 和 swarm-w ...

  7. [CSU1806]Toll

    题目:Toll 传送门:http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1806 题目简述:给定n个点m条有向边的有向图,每条边的花费是$b_i ...

  8. css3D动画

    css3D动画 前言 最近玩了玩用css来构建3D效果,写了几个demo,所以博客总结一下. 在阅读这篇博客之前,请先自行了解一下css 3D的属性,例如:transform-style,transf ...

  9. 20175120彭宇辰 《Java程序设计》第十周学习总结

    教材内容总结 十二章 Java多线程机制 一.进程与线程.操作系统与进程 -线程不是进程,是比进程更小的执行单位.但与进程不同的是,线程的中断和恢复可以更加节省系统的开销. -线程可以共享进程中的某些 ...

  10. 【SpringBoot】SpringBoot的基础,全面理解bean的生命周期

    前言 前段时间直接上手使用springboot开发了一个数据平台的后台部分,但是自身对于springboot的原理和过程还不是很清晰,所以反过来学习下springboot的基础. 大家都知道sprin ...