reactor模式称之为响应器模式,常用于nio的网络通信框架,其服务架构图如下

不同于传统IO的串行调度方式,NIO把整个服务请求分为五个阶段

read:接收到请求,读取数据

decode:解码数据

compute:业务逻辑处理

encode:返回数据编码

send:发送数据

其中,以read和send阶段IO最为频繁

代码实现

 1  // Reactor線程
2 package server;
3
4 import java.io.IOException;
5 import java.net.InetSocketAddress;
6 import java.nio.channels.SelectionKey;
7 import java.nio.channels.Selector;
8 import java.nio.channels.ServerSocketChannel;
9 import java.util.Iterator;
10 import java.util.Set;
11
12 public class TCPReactor implements Runnable {
13
14 private final ServerSocketChannel ssc;
15 private final Selector selector;
16
17 public TCPReactor(int port) throws IOException {
18 selector = Selector.open();
19 ssc = ServerSocketChannel.open();
20 InetSocketAddress addr = new InetSocketAddress(port);
21 ssc.socket().bind(addr); // 在ServerSocketChannel綁定監聽端口
22 ssc.configureBlocking(false); // 設置ServerSocketChannel為非阻塞
23 SelectionKey sk = ssc.register(selector, SelectionKey.OP_ACCEPT); // ServerSocketChannel向selector註冊一個OP_ACCEPT事件,然後返回該通道的key
24 sk.attach(new Acceptor(selector, ssc)); // 給定key一個附加的Acceptor對象
25 }
26
27 @Override
28 public void run() {
29 while (!Thread.interrupted()) { // 在線程被中斷前持續運行
30 System.out.println("Waiting for new event on port: " + ssc.socket().getLocalPort() + "...");
31 try {
32 if (selector.select() == 0) // 若沒有事件就緒則不往下執行
33 continue;
34 } catch (IOException e) {
35 // TODO Auto-generated catch block
36 e.printStackTrace();
37 }
38 Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 取得所有已就緒事件的key集合
39 Iterator<SelectionKey> it = selectedKeys.iterator();
40 while (it.hasNext()) {
41 dispatch((SelectionKey) (it.next())); // 根據事件的key進行調度
42 it.remove();
43 }
44 }
45 }
46
47 /*
48 * name: dispatch(SelectionKey key)
49 * description: 調度方法,根據事件綁定的對象開新線程
50 */
51 private void dispatch(SelectionKey key) {
52 Runnable r = (Runnable) (key.attachment()); // 根據事件之key綁定的對象開新線程
53 if (r != null)
54 r.run();
55 }
56
57 }
 1 // 接受連線請求線程
2 package server;
3
4 import java.io.IOException;
5 import java.nio.channels.SelectionKey;
6 import java.nio.channels.Selector;
7 import java.nio.channels.ServerSocketChannel;
8 import java.nio.channels.SocketChannel;
9
10 public class Acceptor implements Runnable {
11
12 private final ServerSocketChannel ssc;
13 private final Selector selector;
14
15 public Acceptor(Selector selector, ServerSocketChannel ssc) {
16 this.ssc=ssc;
17 this.selector=selector;
18 }
19
20 @Override
21 public void run() {
22 try {
23 SocketChannel sc= ssc.accept(); // 接受client連線請求
24 System.out.println(sc.socket().getRemoteSocketAddress().toString() + " is connected.");
25
26 if(sc!=null) {
27 sc.configureBlocking(false); // 設置為非阻塞
28 SelectionKey sk = sc.register(selector, SelectionKey.OP_READ); // SocketChannel向selector註冊一個OP_READ事件,然後返回該通道的key
29 selector.wakeup(); // 使一個阻塞住的selector操作立即返回
30 sk.attach(new TCPHandler(sk, sc)); // 給定key一個附加的TCPHandler對象
31 }
32
33 } catch (IOException e) {
34 // TODO Auto-generated catch block
35 e.printStackTrace();
36 }
37 }
38
39
40 }
 1 // Handler線程
2 package server;
3
4 import java.io.IOException;
5 import java.nio.ByteBuffer;
6 import java.nio.channels.SelectionKey;
7 import java.nio.channels.SocketChannel;
8 import java.util.concurrent.LinkedBlockingQueue;
9 import java.util.concurrent.ThreadPoolExecutor;
10 import java.util.concurrent.TimeUnit;
11
12 public class TCPHandler implements Runnable {
13
14 private final SelectionKey sk;
15 private final SocketChannel sc;
16
17 int state;
18
19 public TCPHandler(SelectionKey sk, SocketChannel sc) {
20 this.sk = sk;
21 this.sc = sc;
22 state = 0; // 初始狀態設定為READING
23 }
24
25 @Override
26 public void run() {
27 try {
28 if (state == 0)
29 read(); // 讀取網絡數據
30 else
31 send(); // 發送網絡數據
32
33 } catch (IOException e) {
34 System.out.println("[Warning!] A client has been closed.");
35 closeChannel();
36 }
37 }
38
39 private void closeChannel() {
40 try {
41 sk.cancel();
42 sc.close();
43 } catch (IOException e1) {
44 e1.printStackTrace();
45 }
46 }
47
48 private synchronized void read() throws IOException {
49 // non-blocking下不可用Readers,因為Readers不支援non-blocking
50 byte[] arr = new byte[1024];
51 ByteBuffer buf = ByteBuffer.wrap(arr);
52
53 int numBytes = sc.read(buf); // 讀取字符串
54 if(numBytes == -1)
55 {
56 System.out.println("[Warning!] A client has been closed.");
57 closeChannel();
58 return;
59 }
60 String str = new String(arr); // 將讀取到的byte內容轉為字符串型態
61 if ((str != null) && !str.equals(" ")) {
62 process(str); // 邏輯處理
63 System.out.println(sc.socket().getRemoteSocketAddress().toString()
64 + " > " + str);
65 state = 1; // 改變狀態
66 sk.interestOps(SelectionKey.OP_WRITE); // 通過key改變通道註冊的事件
67 sk.selector().wakeup(); // 使一個阻塞住的selector操作立即返回
68 }
69 }
70
71 private void send() throws IOException {
72 // get message from message queue
73
74 String str = "Your message has sent to "
75 + sc.socket().getLocalSocketAddress().toString() + "\r\n";
76 ByteBuffer buf = ByteBuffer.wrap(str.getBytes()); // wrap自動把buf的position設為0,所以不需要再flip()
77
78 while (buf.hasRemaining()) {
79 sc.write(buf); // 回傳給client回應字符串,發送buf的position位置 到limit位置為止之間的內容
80 }
81
82 state = 0; // 改變狀態
83 sk.interestOps(SelectionKey.OP_READ); // 通過key改變通道註冊的事件
84 sk.selector().wakeup(); // 使一個阻塞住的selector操作立即返回
85 }
86
87 void process(String str) {
88 // do process(decode, logically process, encode)..
89 // ..
90 }
91 }
 1 package server;
2
3 import java.io.IOException;
4
5 public class Main {
6
7
8 public static void main(String[] args) {
9 // TODO Auto-generated method stub
10 try {
11 TCPReactor reactor = new TCPReactor(1333);
12 reactor.run();
13 } catch (IOException e) {
14 // TODO Auto-generated catch block
15 e.printStackTrace();
16 }
17 }
18
19 }

客户端代码

 1 package main.pkg;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.io.PrintWriter;
7 import java.net.Socket;
8 import java.net.UnknownHostException;
9
10 public class Client {
11
12 /**
13 * @param args
14 */
15 public static void main(String[] args) {
16 // TODO Auto-generated method stub
17 String hostname=args[0];
18 int port = Integer.parseInt(args[1]);
19 //String hostname="127.0.0.1";
20 //int port=1333;
21
22 System.out.println("Connecting to "+ hostname +":"+port);
23 try {
24 Socket client = new Socket(hostname, port); // 連接至目的地
25 System.out.println("Connected to "+ hostname);
26
27 PrintWriter out = new PrintWriter(client.getOutputStream());
28 BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
29 BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
30 String input;
31
32 while((input=stdIn.readLine()) != null) { // 讀取輸入
33 out.println(input); // 發送輸入的字符串
34 out.flush(); // 強制將緩衝區內的數據輸出
35 if(input.equals("exit"))
36 {
37 break;
38 }
39 System.out.println("server: "+in.readLine());
40 }
41 client.close();
42 System.out.println("client stop.");
43 } catch (UnknownHostException e) {
44 // TODO Auto-generated catch block
45 System.err.println("Don't know about host: " + hostname);
46 } catch (IOException e) {
47 // TODO Auto-generated catch block
48 System.err.println("Couldn't get I/O for the socket connection");
49 }
50
51 }
52
53 }
    

代码解读:

1.创建TCPReactor 类的实例,启动端口监听

2.Acceptor 类只用于处理接受请求的时候,后续的读写跟其无任何关系

3.TCPReactor.run( )一直在进行,后续selectionkey有变动,会监听到,一直执行dispatch方法

最后提醒一点,从性能来说,单线程的reactor没过多的提升,因为IO和CPU的速度还是严重不匹配

参考文章:

https://blog.csdn.net/yehjordan/article/details/51012833

reactor模式:单线程的reactor模式的更多相关文章

  1. 两种高效的事件处理模式(Proactor和Reactor)

    典型的多线程服务器的线程模型 1. 每个请求创建一个线程,使用阻塞式 I/O 操作 这是最简单的线程模型,1个线程处理1个连接的全部生命周期.该模型的优点在于:这个模型足够简单,它可以实现复杂的业务场 ...

  2. 【转】Reactor与Proactor两种模式区别

    转自:http://www.cnblogs.com/cbscan/articles/2107494.html 两种IO多路复用方案:Reactor and Proactor 一般情况下,I/O 复用机 ...

  3. ACE_linux:Reactor与Proactor两种模式的区别

    一.概念: Reactor与Proactor两种模式的区别.这里我们只关注read操作,因为write操作也是差不多的.下面是Reactor的做法: 某个事件处理器宣称它对某个socket上的读事件很 ...

  4. java 高性能Server —— Reactor模型单线程版

    NIO模型 NIO模型示例如下: Acceptor注册Selector,监听accept事件 当客户端连接后,触发accept事件 服务器构建对应的Channel,并在其上注册Selector,监听读 ...

  5. 选择目录,选择文件夹的COM组件问题。在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常。

    异常: 在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式.请确保您的 Main 函数带有 STAThreadAttribute 标记. 只有将调试器附加到该进程才会引发此异常. ...

  6. 在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式

    在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式 转载自:http://blog.163.com/smhily_min/blog/static/75206226201092011 ...

  7. Reactor模型-单线程版

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

  8. c# Clipboard.SetDataObject(bmp1) 在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常

    c# Clipboard.SetDataObject(bmp1)  在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式.请确保您的 Main 函数带有 STAThreadAttri ...

  9. 异常错误:在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式

    最近做一个蛋疼的东西就是C#调用windows API 来操作一个摄像头,自动处理一些东西.要用到剪切板复制 粘贴功能,即 Clipboard.SetDataObject(filedic, true) ...

随机推荐

  1. moviepy音视频开发:使用volumex调节音量大小及其花式用法

    ☞ ░ 前往老猿Python博文目录 ░ 一.概述 音频数据的音量体现为声音的振幅,振幅越大则声音越大,具体到音频帧数据上,体现为声音的绝对值越大则振幅越大音量越大,调节音量大小的本质是调整帧数据的绝 ...

  2. 老猿学5G:融合计费基于流计费的触发器Triggers

    ☞ ░ 前往老猿Python博文目录 ░ 一.概述 每个触发条件都是一个可计费事件.SMF中的功能体CTF在用户上网时达到一定条件就会向CHF上报流量,而CTF什么时候触发流量上报是由CTF中的触发器 ...

  3. 第5.4节 Python函数中的变量及作用域

    一.函数中的变量使用规则 函数执行时,使用的全局空间是调用方的全局空间,参数及函数使用的局部变量存储在函数单独的局部名字空间内: 函数的形参在函数中修改了值时,并不影响调用方本身的数据,但如果形参是一 ...

  4. 归并排序(c++,递归)

    放上c++代码模板(但是该版本中,还可以再进一步优化成原地算法,即不开辟新的空间:本代码中空间复杂度为n,不是1) 1 #include <iostream> 2 #include< ...

  5. 微信小程序中使用text-indent实现首行缩进

    问题由来:在小程序中使用text标签包裹了一段话,要做一个首行缩进的效果,但是不起效果 . 解决方法:使用view标签代替text,使用text-indent:2em即可解决.

  6. 【题解】The Great Divide [Uva10256]

    [题解]The Great Divide [Uva10256] 传送门:\(\text{The Great Divide [Uva10256]}\) [题目描述] 输入多组数据,每组数据给定 \(n\ ...

  7. nginx优化-转载

    (1)nginx运行工作进程个数,一般设置cpu的核心或者核心数x2 如果不了解cpu的核数,可以top命令之后按1看出来,也可以查看/proc/cpuinfo文件 grep ^processor / ...

  8. 使用pip安装pymysql出错;Could not find a version that satisfies the requirement cryptography (from pymysql) (from versions: ) No matching distribution found for cryptography (from pymysql)

    今天使用pip安装pymysql时出现如下错误: Could not find a version that satisfies the requirement cryptography (from ...

  9. ORACLE 10g、11g批量刷新用户密码

    一.ORACLE 10g批量刷新用户密码 select 'alter user ' || username ||' identified by values '''||password ||''';' ...

  10. Redis达到最大占用内存后的淘汰策略

    1. 查询Redis最大占用内存 # 查询最大占用内存 config get maxmemory # 为0时在64操作系统中不限制内存,在32位操作系统中最大为3GB 2. Redis设置最大占用内存 ...