请收藏好:Doug Lea写的理解 Java NIO 与 Reactor 模式最权威的资料

15 阅读15分钟

1. 网络服务

Web 服务、分布式对象等,大多数网络服务都有相同的基本结构:

  1. 读取请求(Read request)
  2. 解码请求(Decode request)
  3. 处理服务(Process service)
  4. 编码响应(Encode reply)
  5. 发送响应(Send reply)

但每个步骤的性质和开销各不相同,例如:XML 解析、文件传输、Web 页面生成、计算服务等。


2. 经典服务设计

客户端 ──┐
客户端 ──┼──→ 服务器
客户端 ──┘
              ├── handler (read → decode → compute → encode → send)
              ├── handler (read → decode → compute → encode → send)
              └── handler (read → decode → compute → encode → send)

每个 handler 都可以在各自的线程中启动。

经典的 ServerSocket 循环

class Server implements Runnable {
    public void run() {
        try {
            ServerSocket ss = new ServerSocket(PORT);
            while (!Thread.interrupted())
                new Thread(new Handler(ss.accept())).start();
            // 或者使用单线程,或线程池
        } catch (IOException ex) { /* ... */ }
    }

    static class Handler implements Runnable {
        final Socket socket;
        Handler(Socket s) { socket = s; }
        public void run() {
            try {
                byte[] input = new byte[MAX_INPUT];
                socket.getInputStream().read(input);
                byte[] output = process(input);
                socket.getOutputStream().write(output);
            } catch (IOException ex) { /* ... */ }
        }
        private byte[] process(byte[] cmd) { /* ... */ }
    }
}

注意:为简洁起见,代码示例中省略了大部分异常处理。


3. 可扩展性目标

  • 随着负载增加(更多客户端)能够优雅降级
  • 随着资源增加(CPU、内存、磁盘、带宽)能够持续提升性能
  • 同时还要满足可用性和性能目标:
    • 低延迟
    • 满足峰值需求
    • 可调节的服务质量
  • 分治法通常是实现任何可扩展性目标的最佳方法

4. 分治法

  • 将处理过程拆分为小任务,每个任务执行一个非阻塞操作
  • 当任务被启用时执行它,这里,IO 事件通常作为触发器
handler: read → decode → compute → encode → send
  • java.nio 支持的基本机制:
    • 非阻塞的读和写
    • 分派与感知到的 IO 事件关联的任务
  • 变化无穷,形成了一系列事件驱动设计

5. 事件驱动设计

通常比其他方案更高效

  • 占用更少的资源 – 通常不需要为每个客户端创建一个线程
  • 更少的开销 – 更少的上下文切换,通常更少的锁竞争

但分派速度可能较慢,而且通常更难编程

  • 必须将处理拆分为简单的非阻塞操作
    • 类似于 GUI 的事件驱动操作
    • 无法消除所有阻塞:GC、页面故障(page fault)等
  • 必须持续追踪服务的逻辑状态
  • 必须手动将操作绑定到事件

6. 背景知识:AWT 中的事件

AWT 事件队列 ──→ AWT 线程 ──→ 事件 ──→ Button ──→ ActionListener
                                                     ↓
                                             actionPerformed(...) {
                                                 doSomething();
                                             }

事件驱动 IO 使用类似的思想,但采用不同的设计…


7. Reactor 模式

  • Reactor 通过分派合适的 handler 来响应 IO 事件
    • 类似于 AWT 线程
  • Handler 执行非阻塞操作
    • 类似于 AWT 的 ActionListener
  • 通过将 handler 绑定到事件来管理
    • 类似于 AWT 的 addActionListener

参见:Schmidt 等人著 Pattern-Oriented Software Architecture, Volume 2 (POSA2) ;Richard Stevens 的网络编程著作;Matt Welsh 的 SEDA 框架等。


7.1 基础 Reactor 设计

客户端 ──┐
客户端 ──┼──→ Reactor ──→ acceptor ──→ dispatch
客户端 ──┘                    ├── read → decode → compute → encode → send
                              ├── read → decode → compute → encode → send
                              └── read → decode → compute → encode → send

单线程版本


7.2 java.nio 支持

  • Channels(通道)  – 支持非阻塞读写的文件、套接字等连接
  • Buffers(缓冲区)  – 可以被 Channel 直接读写的类数组对象
  • Selectors(选择器)  – 告知哪些 Channel 上有 IO 事件就绪
  • SelectionKeys(选择键)  – 维护 IO 事件的状态和绑定关系

7.3 Reactor 实现 1:初始化

class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocket;

    Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(port));
        serverSocket.configureBlocking(false);
        SelectionKey sk =
            serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        sk.attach(new Acceptor());
    }

    /*
     也可以使用显式的 SPI provider:
     SelectorProvider p = SelectorProvider.provider();
     selector = p.openSelector();
     serverSocket = p.openServerSocketChannel();
    */

7.4 Reactor 实现 2:分派循环

// Reactor 类续
public void run() { // 通常在一个新线程中运行
    try {
        while (!Thread.interrupted()) {
            selector.select();
            Set selected = selector.selectedKeys();
            Iterator it = selected.iterator();
            while (it.hasNext())
                dispatch((SelectionKey)(it.next()));
            selected.clear();
        }
    } catch (IOException ex) { /* ... */ }
}

void dispatch(SelectionKey k) {
    Runnable r = (Runnable)(k.attachment());
    if (r != null)
        r.run();
}

7.5 Reactor 实现 3:Acceptor

// Reactor 类续
class Acceptor implements Runnable { // 内部类
    public void run() {
        try {
            SocketChannel c = serverSocket.accept();
            if (c != null)
                new Handler(selector, c);
        } catch (IOException ex) { /* ... */ }
    }
}
}

7.6 Reactor 实现 4:Handler 初始化

final class Handler implements Runnable {
    final SocketChannel socket;
    final SelectionKey sk;
    ByteBuffer input = ByteBuffer.allocate(MAXIN);
    ByteBuffer output = ByteBuffer.allocate(MAXOUT);
    static final int READING = 0, SENDING = 1;
    int state = READING;

    Handler(Selector sel, SocketChannel c) throws IOException {
        socket = c;
        c.configureBlocking(false);
        // 可选:尝试立即进行第一次读取
        sk = socket.register(sel, 0);
        sk.attach(this);
        sk.interestOps(SelectionKey.OP_READ);
        sel.wakeup();
    }

    boolean inputIsComplete()  { /* ... */ }
    boolean outputIsComplete() { /* ... */ }
    void process()             { /* ... */ }

7.7 Reactor 实现 5:请求处理

// Handler 类续
public void run() {
    try {
        if      (state == READING) read();
        else if (state == SENDING) send();
    } catch (IOException ex) { /* ... */ }
}

void read() throws IOException {
    socket.read(input);
    if (inputIsComplete()) {
        process();
        state = SENDING;
        // 通常此时也会进行第一次写操作
        sk.interestOps(SelectionKey.OP_WRITE);
    }
}

void send() throws IOException {
    socket.write(output);
    if (outputIsComplete()) sk.cancel();
}
}

7.8 基于状态的 Handler

GoF 状态对象模式的简单应用 – 重新绑定合适的 handler 作为附件(attachment):

class Handler { // ...
    public void run() { // 初始状态为 reader
        socket.read(input);
        if (inputIsComplete()) {
            process();
            sk.attach(new Sender());
            sk.interest(SelectionKey.OP_WRITE);
            sk.selector().wakeup();
        }
    }

    class Sender implements Runnable {
        public void run() { // ...
            socket.write(output);
            if (outputIsComplete()) sk.cancel();
        }
    }
}

8. 多线程设计

策略性地添加线程以提高可扩展性,主要适用于多处理器环境。

8.1 工作线程(Worker Threads)

  • Reactor 应该快速触发 handler
    • handler 的处理会拖慢 Reactor
  • 将非 IO 处理卸载到其他线程

8.2 多 Reactor 线程

  • Reactor 线程可能在做 IO 时达到饱和
  • 将负载分发到其他 reactor
  • 进行负载均衡以匹配 CPU 和 IO 速率

8.3 工作线程详解

  • 将非 IO 处理卸载以加速 Reactor 线程
    • 类似于 POSA2 的 Proactor 设计
  • 比将计算密集型处理改造为事件驱动形式更简单
    • 仍然应该是纯粹的非阻塞计算
    • 需要足够的处理量才能抵消开销
  • 但更难实现处理与 IO 的重叠
    • 最好是能够先将所有输入读入缓冲区
  • 使用线程池以便于调优和控制
    • 通常需要的线程数远少于客户端数

客户端 ──┐
客户端 ──┼──→ Reactor ──→ acceptor
客户端 ──┘        ↓
           read → decode ──→ [线程池] ──→ compute → encode → send
                            (queued tasks)
                              worker threads

8.4 使用线程池的 Handler

class Handler implements Runnable {
    // 使用 util.concurrent 线程池
    static PooledExecutor pool = new PooledExecutor(...);
    static final int PROCESSING = 3;
    // ...

    synchronized void read() { // ...
        socket.read(input);
        if (inputIsComplete()) {
            state = PROCESSING;
            pool.execute(new Processer());
        }
    }

    synchronized void processAndHandOff() {
        process();
        state = SENDING; // 或者重新绑定 attachment
        sk.interest(SelectionKey.OP_WRITE);
    }

    class Processer implements Runnable {
        public void run() { processAndHandOff(); }
    }
}

8.5 任务协调

  • 交接(Handoffs)
    • 每个任务启用、触发或调用下一个任务
    • 通常最快,但可能比较脆弱
  • 回调到每个 handler 的分派器
    • 设置状态、attachment 等
    • GoF 中介者(Mediator)模式的变体
  • 队列
    • 例如,在各阶段之间传递缓冲区
  • Futures
    • 当每个任务产生一个结果时

    • 在 join 或 wait/notify 之上分层协调


8.6 使用 PooledExecutor

一个可调优的工作线程池

  • 主要方法:execute(Runnable r)
  • 可控制:
    • 任务队列的类型(任意 Channel)

    • 最大线程数

    • 最小线程数

    • “预热”线程 vs 按需创建线程

    • 空闲线程的存活间隔(超时后线程销毁,必要时再创建新的)

    • 饱和策略(阻塞、丢弃、生产者运行等)


8.7 多 Reactor 线程

使用 Reactor 池

  • 用于匹配 CPU 和 IO 速率
  • 静态或动态构建
  • 每个 Reactor 拥有自己的 Selector、Thread、分派循环
  • 主 acceptor 将连接分发到其他 reactor
Selector[] selectors; // 同时创建线程
int next = 0;

class Acceptor { // ...
    public synchronized void run() { // ...
        Socket connection = serverSocket.accept();
        if (connection != null)
            new Handler(selectors[next], connection);
        if (++next == selectors.length) next = 0;
    }
}
客户端 ──┐
客户端 ──┼──→ mainReactor ──→ acceptor ──→ subReactor
客户端 ──┘                                      ↓
                                          read → decode ──→ [线程池] ──→ compute → encode → send
                                                            worker threads

9. 使用其他 java.nio 特性

  • 每个 Reactor 多个 Selector
    • 将不同的 handler 绑定到不同的 IO 事件
    • 可能需要仔细的同步来协调
  • 文件传输
    • 自动化的文件到网络或网络到文件的复制
  • 内存映射文件
    • 通过缓冲区访问文件
  • 直接缓冲区(Direct Buffer)
    • 有时可以实现零拷贝传输

    • 但有创建和终结的开销

    • 最适合长连接的应用


10. 基于连接的扩展

不同于单次服务请求,基于连接的模式是:

  • 客户端连接
  • 客户端发送一系列消息/请求
  • 客户端断开连接

示例:数据库和事务监控器、多人游戏、聊天等。

可以扩展基本的网络服务模式:

  • 处理大量相对长生命周期的客户端
  • 追踪客户端和会话状态(包括断线)
  • 将服务分布到多台主机

11. java.nio API 概览

11.1 Buffer

abstract class Buffer {
    int     capacity();
    int     position();
    Buffer  position(int newPosition);
    int     limit();
    Buffer  limit(int newLimit);
    Buffer  mark();
    Buffer  reset();
    Buffer  clear();
    Buffer  flip();
    Buffer  rewind();
    int     remaining();
    boolean hasRemaining();
    boolean isReadOnly();
}

Buffer 内部结构:mark ≤ position ≤ limit ≤ capacity

11.2 ByteBuffer(第1部分)

abstract class ByteBuffer extends Buffer {
    static ByteBuffer allocateDirect(int capacity);
    static ByteBuffer allocate(int capacity);
    static ByteBuffer wrap(byte[] src, int offset, int len);
    static ByteBuffer wrap(byte[] src);
    boolean      isDirect();
    ByteOrder    order();
    ByteBuffer   order(ByteOrder bo);
    ByteBuffer   slice();
    ByteBuffer   duplicate();
    ByteBuffer   compact();
    ByteBuffer   asReadOnlyBuffer();
    byte         get();
    byte         get(int index);
    ByteBuffer   get(byte[] dst, int offset, int length);
    ByteBuffer   get(byte[] dst);
    ByteBuffer   put(byte b);
    ByteBuffer   put(int index, byte b);
    ByteBuffer   put(byte[] src, int offset, int length);
    ByteBuffer   put(ByteBuffer src);
    ByteBuffer   put(byte[] src);
    char         getChar();
    char         getChar(int index);
    ByteBuffer   putChar(char value);
    ByteBuffer   putChar(int index, char value);
    CharBuffer   asCharBuffer();

11.3 ByteBuffer(第2部分)

short        getShort();
short        getShort(int index);
ByteBuffer   putShort(short value);
ByteBuffer   putShort(int index, short value);
ShortBuffer  asShortBuffer();
int          getInt();
int          getInt(int index);
ByteBuffer   putInt(int value);
ByteBuffer   putInt(int index, int value);
IntBuffer    asIntBuffer();
long         getLong();
long         getLong(int index);
ByteBuffer   putLong(long value);
ByteBuffer   putLong(int index, long value);
LongBuffer   asLongBuffer();
float        getFloat();
float        getFloat(int index);
ByteBuffer   putFloat(float value);
ByteBuffer   putFloat(int index, float value);
FloatBuffer  asFloatBuffer();
double       getDouble();
double       getDouble(int index);
ByteBuffer   putDouble(double value);
ByteBuffer   putDouble(int index, double value);
DoubleBuffer asDoubleBuffer();
}

11.4 Channel

interface Channel {
    boolean isOpen();
    void    close() throws IOException;
}

interface ReadableByteChannel extends Channel {
    int read(ByteBuffer dst) throws IOException;
}

interface WritableByteChannel extends Channel {
    int write(ByteBuffer src) throws IOException;
}

interface ScatteringByteChannel extends ReadableByteChannel {
    int read(ByteBuffer[] dsts, int offset, int length) throws IOException;
    int read(ByteBuffer[] dsts) throws IOException;
}

interface GatheringByteChannel extends WritableByteChannel {
    int write(ByteBuffer[] srcs, int offset, int length) throws IOException;
    int write(ByteBuffer[] srcs) throws IOException;
}

11.5 SelectableChannel

abstract class SelectableChannel implements Channel {
    int          validOps();
    boolean      isRegistered();
    SelectionKey keyFor(Selector sel);
    SelectionKey register(Selector sel, int ops)
                    throws ClosedChannelException;
    void         configureBlocking(boolean block)
                    throws IOException;
    boolean      isBlocking();
    Object       blockingLock();
}

11.6 SocketChannel

abstract class SocketChannel implements ByteChannel ... {
    static SocketChannel open() throws IOException;
    Socket  socket();
    int     validOps();
    boolean isConnected();
    boolean isConnectionPending();
    boolean isInputOpen();
    boolean isOutputOpen();
    boolean connect(SocketAddress remote) throws IOException;
    boolean finishConnect() throws IOException;
    void    shutdownInput() throws IOException;
    void    shutdownOutput() throws IOException;
    int     read(ByteBuffer dst) throws IOException;
    int     read(ByteBuffer[] dsts, int offset, int length) throws IOException;
    int     read(ByteBuffer[] dsts) throws IOException;
    int     write(ByteBuffer src) throws IOException;
    int     write(ByteBuffer[] srcs, int offset, int length) throws IOException;
    int     write(ByteBuffer[] srcs) throws IOException;
}

11.7 ServerSocketChannel

abstract class ServerSocketChannel extends ... {
    static ServerSocketChannel open() throws IOException;
    int           validOps();
    ServerSocket  socket();
    SocketChannel accept() throws IOException;
}

11.8 FileChannel

abstract class FileChannel implements ... {
    int  read(ByteBuffer dst);
    int  read(ByteBuffer dst, long position);
    int  read(ByteBuffer[] dsts, int offset, int length);
    int  read(ByteBuffer[] dsts);
    int  write(ByteBuffer src);
    int  write(ByteBuffer src, long position);
    int  write(ByteBuffer[] srcs, int offset, int length);
    int  write(ByteBuffer[] srcs);
    long position();
    void position(long newPosition);
    long size();
    void truncate(long size);
    void force(boolean flushMetaDataToo);
    int  transferTo(long position, int count,
                    WritableByteChannel dst);
    int  transferFrom(ReadableByteChannel src,
                      long position, int count);
    FileLock lock(long position, long size, boolean shared);
    FileLock lock();
    FileLock tryLock(long pos, long size, boolean shared);
    FileLock tryLock();
    static final int MAP_RO, MAP_RW, MAP_COW;
    MappedByteBuffer map(int mode, long position, int size);
}

注意:所有方法都会抛出 IOException。

11.9 Selector

abstract class Selector {
    static Selector open() throws IOException;
    Set  keys();
    Set  selectedKeys();
    int  selectNow() throws IOException;
    int  select(long timeout) throws IOException;
    int  select() throws IOException;
    void wakeup();
    void close() throws IOException;
}

11.10 SelectionKey

abstract class SelectionKey {
    static final int OP_READ, OP_WRITE,
                     OP_CONNECT, OP_ACCEPT;
    SelectableChannel channel();
    Selector          selector();
    boolean           isValid();
    void              cancel();
    int               interestOps();
    void              interestOps(int ops);
    int               readyOps();
    boolean           isReadable();
    boolean           isWritable();
    boolean           isConnectable();
    boolean           isAcceptable();
    Object            attach(Object ob);
    Object            attachment();
}

第二部分:英文原文

Outline

  • Scalable network services
  • Event-driven processing
  • Reactor pattern
    • Basic version
    • Multithreaded versions
    • Other variants
  • Walkthrough of java.nio nonblocking IO APIs

1. Network Services

Web services, Distributed Objects, etc. Most have same basic structure:

  1. Read request
  2. Decode request
  3. Process service
  4. Encode reply
  5. Send reply

But differ in nature and cost of each step: XML parsing, File transfer, Web page generation, computational services, …


2. Classic Service Designs

client ──┐
client ──┼──→ Server
client ──┘
              ├── handler (read → decode → compute → encode → send)
              ├── handler (read → decode → compute → encode → send)
              └── handler (read → decode → compute → encode → send)

Each handler may be started in its own thread.

Classic ServerSocket Loop

class Server implements Runnable {
    public void run() {
        try {
            ServerSocket ss = new ServerSocket(PORT);
            while (!Thread.interrupted())
                new Thread(new Handler(ss.accept())).start();
            // or, single-threaded, or a thread pool
        } catch (IOException ex) { /* ... */ }
    }

    static class Handler implements Runnable {
        final Socket socket;
        Handler(Socket s) { socket = s; }
        public void run() {
            try {
                byte[] input = new byte[MAX_INPUT];
                socket.getInputStream().read(input);
                byte[] output = process(input);
                socket.getOutputStream().write(output);
            } catch (IOException ex) { /* ... */ }
        }
        private byte[] process(byte[] cmd) { /* ... */ }
    }
}

Note: most exception handling elided from code examples.


3. Scalability Goals

  • Graceful degradation under increasing load (more clients)
  • Continuous improvement with increasing resources (CPU, memory, disk, bandwidth)
  • Also meet availability and performance goals:
    • Short latencies
    • Meeting peak demand
    • Tunable quality of service
  • Divide-and-conquer is usually the best approach for achieving any scalability goal

4. Divide and Conquer

  • Divide processing into small tasks
    • Each task performs an action without blocking
  • Execute each task when it is enabled
    • Here, an IO event usually serves as trigger

handler: read → decode → compute → encode → send
  • Basic mechanisms supported in java.nio:
    • Non-blocking reads and writes
    • Dispatch tasks associated with sensed IO events
  • Endless variation possible
    • A family of event-driven designs


5. Event-driven Designs

Usually more efficient than alternatives:

  • Fewer resources – Don’t usually need a thread per client
  • Less overhead – Less context switching, often less locking

But dispatching can be slower. Usually harder to program:

  • Must break up into simple non-blocking actions
    • Similar to GUI event-driven actions
    • Cannot eliminate all blocking: GC, page faults, etc
  • Must keep track of logical state of service
  • Must manually bind actions to events

6. Background: Events in AWT

AWT Event Queue ──→ AWT Thread ──→ Event ──→ Button ──→ ActionListener
                                                          ↓
                                                  actionPerformed(...) {
                                                      doSomething();
                                                  }

Event-driven IO uses similar ideas but in different designs…


7. Reactor Pattern

  • Reactor responds to IO events by dispatching the appropriate handler
    • Similar to AWT thread
  • Handlers perform non-blocking actions
    • Similar to AWT ActionListeners
  • Manage by binding handlers to events
    • Similar to AWT addActionListener

See Schmidt et al, Pattern-Oriented Software Architecture, Volume 2 (POSA2) . Also Richard Stevens’s networking books, Matt Welsh’s SEDA framework, etc.


7.1 Basic Reactor Design

client ──┐
client ──┼──→ Reactor ──→ acceptor ──→ dispatch
client ──┘                    ├── read → decode → compute → encode → send
                              ├── read → decode → compute → encode → send
                              └── read → decode → compute → encode → send

Single threaded version.


7.2 java.nio Support

  • Channels – Connections to files, sockets etc that support non-blocking reads
  • Buffers – Array-like objects that can be directly read or written by Channels
  • Selectors – Tell which of a set of Channels have IO events
  • SelectionKeys – Maintain IO event status and bindings

7.3 Reactor 1: Setup

class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocket;

    Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(port));
        serverSocket.configureBlocking(false);
        SelectionKey sk =
            serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        sk.attach(new Acceptor());
    }

    /*
     Alternatively, use explicit SPI provider:
     SelectorProvider p = SelectorProvider.provider();
     selector = p.openSelector();
     serverSocket = p.openServerSocketChannel();
    */

7.4 Reactor 2: Dispatch Loop

// class Reactor continued
public void run() { // normally in a new Thread
    try {
        while (!Thread.interrupted()) {
            selector.select();
            Set selected = selector.selectedKeys();
            Iterator it = selected.iterator();
            while (it.hasNext())
                dispatch((SelectionKey)(it.next()));
            selected.clear();
        }
    } catch (IOException ex) { /* ... */ }
}

void dispatch(SelectionKey k) {
    Runnable r = (Runnable)(k.attachment());
    if (r != null)
        r.run();
}

7.5 Reactor 3: Acceptor

// class Reactor continued
class Acceptor implements Runnable { // inner
    public void run() {
        try {
            SocketChannel c = serverSocket.accept();
            if (c != null)
                new Handler(selector, c);
        } catch (IOException ex) { /* ... */ }
    }
}
}

7.6 Reactor 4: Handler setup

final class Handler implements Runnable {
    final SocketChannel socket;
    final SelectionKey sk;
    ByteBuffer input = ByteBuffer.allocate(MAXIN);
    ByteBuffer output = ByteBuffer.allocate(MAXOUT);
    static final int READING = 0, SENDING = 1;
    int state = READING;

    Handler(Selector sel, SocketChannel c) throws IOException {
        socket = c;
        c.configureBlocking(false);
        // Optionally try first read now
        sk = socket.register(sel, 0);
        sk.attach(this);
        sk.interestOps(SelectionKey.OP_READ);
        sel.wakeup();
    }

    boolean inputIsComplete()  { /* ... */ }
    boolean outputIsComplete() { /* ... */ }
    void process()             { /* ... */ }

7.7 Reactor 5: Request handling

// class Handler continued
public void run() {
    try {
        if      (state == READING) read();
        else if (state == SENDING) send();
    } catch (IOException ex) { /* ... */ }
}

void read() throws IOException {
    socket.read(input);
    if (inputIsComplete()) {
        process();
        state = SENDING;
        // Normally also do first write now
        sk.interestOps(SelectionKey.OP_WRITE);
    }
}

void send() throws IOException {
    socket.write(output);
    if (outputIsComplete()) sk.cancel();
}
}

7.8 Per-State Handlers

A simple use of GoF State-Object pattern. Rebind appropriate handler as attachment:

class Handler { // ...
    public void run() { // initial state is reader
        socket.read(input);
        if (inputIsComplete()) {
            process();
            sk.attach(new Sender());
            sk.interest(SelectionKey.OP_WRITE);
            sk.selector().wakeup();
        }
    }

    class Sender implements Runnable {
        public void run() { // ...
            socket.write(output);
            if (outputIsComplete()) sk.cancel();
        }
    }
}

8. Multithreaded Designs

Strategically add threads for scalability. Mainly applicable to multiprocessors.

8.1 Worker Threads

  • Reactors should quickly trigger handlers
    • Handler processing slows down Reactor
  • Offload non-IO processing to other threads

8.2 Multiple Reactor Threads

  • Reactor threads can saturate doing IO
  • Distribute load to other reactors
  • Load-balance to match CPU and IO rates

8.3 Worker Threads (Details)

  • Offload non-IO processing to speed up Reactor thread
    • Similar to POSA2 Proactor designs
  • Simpler than reworking compute-bound processing into event-driven form
    • Should still be pure nonblocking computation
    • Enough processing to outweigh overhead
  • But harder to overlap processing with IO
    • Best when can first read all input into a buffer
  • Use thread pool so can tune and control
    • Normally need many fewer threads than clients

client ──┐
client ──┼──→ Reactor ──→ acceptor
client ──┘        ↓
           read → decode ──→ [Thread Pool] ──→ compute → encode → send
                              (queued tasks)
                              worker threads

8.4 Handler with Thread Pool

class Handler implements Runnable {
    // uses util.concurrent thread pool
    static PooledExecutor pool = new PooledExecutor(...);
    static final int PROCESSING = 3;
    // ...

    synchronized void read() { // ...
        socket.read(input);
        if (inputIsComplete()) {
            state = PROCESSING;
            pool.execute(new Processer());
        }
    }

    synchronized void processAndHandOff() {
        process();
        state = SENDING; // or rebind attachment
        sk.interest(SelectionKey.OP_WRITE);
    }

    class Processer implements Runnable {
        public void run() { processAndHandOff(); }
    }
}

8.5 Coordinating Tasks

  • Handoffs
    • Each task enables, triggers, or calls next one
    • Usually fastest but can be brittle
  • Callbacks to per-handler dispatcher
    • Sets state, attachment, etc
    • A variant of GoF Mediator pattern
  • Queues
    • For example, passing buffers across stages
  • Futures
    • When each task produces a result
    • Coordination layered on top of join or wait/notify

8.6 Using PooledExecutor

A tunable worker thread pool. Main method: execute(Runnable r)

Controls for:

  • The kind of task queue (any Channel)
  • Maximum number of threads
  • Minimum number of threads
  • “Warm” versus on-demand threads
  • Keep-alive interval until idle threads die (to be later replaced by new ones if necessary)
  • Saturation policy: block, drop, producer-runs, etc

8.7 Multiple Reactor Threads

Using Reactor Pools:

  • Use to match CPU and IO rates
  • Static or dynamic construction
  • Each with own Selector, Thread, dispatch loop
  • Main acceptor distributes to other reactors
Selector[] selectors; // also create threads
int next = 0;

class Acceptor { // ...
    public synchronized void run() { // ...
        Socket connection = serverSocket.accept();
        if (connection != null)
            new Handler(selectors[next], connection);
        if (++next == selectors.length) next = 0;
    }
}
client ──┐
client ──┼──→ mainReactor ──→ acceptor ──→ subReactor
client ──┘                                      ↓
                                          read → decode ──→ [Thread Pool] ──→ compute → encode → send
                                                            worker threads

9. Using other java.nio features

  • Multiple Selectors per Reactor
    • To bind different handlers to different IO events
    • May need careful synchronization to coordinate
  • File transfer
    • Automated file-to-net or net-to-file copying
  • Memory-mapped files
    • Access files via buffers
  • Direct buffers
    • Can sometimes achieve zero-copy transfer
    • But have setup and finalization overhead
    • Best for applications with long-lived connections

10. Connection-Based Extensions

Instead of a single service request:

  • Client connects
  • Client sends a series of messages/requests
  • Client disconnects

Examples: Databases and Transaction monitors, Multi-participant games, chat, etc.

Can extend basic network service patterns:

  • Handle many relatively long-lived clients
  • Track client and session state (including drops)
  • Distribute services across multiple hosts

11. API Walkthrough

11.1 Buffer

abstract class Buffer {
    int     capacity();
    int     position();
    Buffer  position(int newPosition);
    int     limit();
    Buffer  limit(int newLimit);
    Buffer  mark();
    Buffer  reset();
    Buffer  clear();
    Buffer  flip();
    Buffer  rewind();
    int     remaining();
    boolean hasRemaining();
    boolean isReadOnly();
}

11.2 ByteBuffer (1)

abstract class ByteBuffer extends Buffer {
    static ByteBuffer allocateDirect(int capacity);
    static ByteBuffer allocate(int capacity);
    static ByteBuffer wrap(byte[] src, int offset, int len);
    static ByteBuffer wrap(byte[] src);
    boolean      isDirect();
    ByteOrder    order();
    ByteBuffer   order(ByteOrder bo);
    ByteBuffer   slice();
    ByteBuffer   duplicate();
    ByteBuffer   compact();
    ByteBuffer   asReadOnlyBuffer();
    byte         get();
    byte         get(int index);
    ByteBuffer   get(byte[] dst, int offset, int length);
    ByteBuffer   get(byte[] dst);
    ByteBuffer   put(byte b);
    ByteBuffer   put(int index, byte b);
    ByteBuffer   put(byte[] src, int offset, int length);
    ByteBuffer   put(ByteBuffer src);
    ByteBuffer   put(byte[] src);
    char         getChar();
    char         getChar(int index);
    ByteBuffer   putChar(char value);
    ByteBuffer   putChar(int index, char value);
    CharBuffer   asCharBuffer();

11.3 ByteBuffer (2)

short        getShort();
short        getShort(int index);
ByteBuffer   putShort(short value);
ByteBuffer   putShort(int index, short value);
ShortBuffer  asShortBuffer();
int          getInt();
int          getInt(int index);
ByteBuffer   putInt(int value);
ByteBuffer   putInt(int index, int value);
IntBuffer    asIntBuffer();
long         getLong();
long         getLong(int index);
ByteBuffer   putLong(long value);
ByteBuffer   putLong(int index, long value);
LongBuffer   asLongBuffer();
float        getFloat();
float        getFloat(int index);
ByteBuffer   putFloat(float value);
ByteBuffer   putFloat(int index, float value);
FloatBuffer  asFloatBuffer();
double       getDouble();
double       getDouble(int index);
ByteBuffer   putDouble(double value);
ByteBuffer   putDouble(int index, double value);
DoubleBuffer asDoubleBuffer();
}

11.4 Channel

interface Channel {
    boolean isOpen();
    void    close() throws IOException;
}

interface ReadableByteChannel extends Channel {
    int read(ByteBuffer dst) throws IOException;
}

interface WritableByteChannel extends Channel {
    int write(ByteBuffer src) throws IOException;
}

interface ScatteringByteChannel extends ReadableByteChannel {
    int read(ByteBuffer[] dsts, int offset, int length) throws IOException;
    int read(ByteBuffer[] dsts) throws IOException;
}

interface GatheringByteChannel extends WritableByteChannel {
    int write(ByteBuffer[] srcs, int offset, int length) throws IOException;
    int write(ByteBuffer[] srcs) throws IOException;
}

11.5 SelectableChannel

abstract class SelectableChannel implements Channel {
    int          validOps();
    boolean      isRegistered();
    SelectionKey keyFor(Selector sel);
    SelectionKey register(Selector sel, int ops)
                    throws ClosedChannelException;
    void         configureBlocking(boolean block)
                    throws IOException;
    boolean      isBlocking();
    Object       blockingLock();
}

11.6 SocketChannel

abstract class SocketChannel implements ByteChannel ... {
    static SocketChannel open() throws IOException;
    Socket  socket();
    int     validOps();
    boolean isConnected();
    boolean isConnectionPending();
    boolean isInputOpen();
    boolean isOutputOpen();
    boolean connect(SocketAddress remote) throws IOException;
    boolean finishConnect() throws IOException;
    void    shutdownInput() throws IOException;
    void    shutdownOutput() throws IOException;
    int     read(ByteBuffer dst) throws IOException;
    int     read(ByteBuffer[] dsts, int offset, int length) throws IOException;
    int     read(ByteBuffer[] dsts) throws IOException;
    int     write(ByteBuffer src) throws IOException;
    int     write(ByteBuffer[] srcs, int offset, int length) throws IOException;
    int     write(ByteBuffer[] srcs) throws IOException;
}

11.7 ServerSocketChannel

abstract class ServerSocketChannel extends ... {
    static ServerSocketChannel open() throws IOException;
    int           validOps();
    ServerSocket  socket();
    SocketChannel accept() throws IOException;
}

11.8 FileChannel

abstract class FileChannel implements ... {
    int  read(ByteBuffer dst);
    int  read(ByteBuffer dst, long position);
    int  read(ByteBuffer[] dsts, int offset, int length);
    int  read(ByteBuffer[] dsts);
    int  write(ByteBuffer src);
    int  write(ByteBuffer src, long position);
    int  write(ByteBuffer[] srcs, int offset, int length);
    int  write(ByteBuffer[] srcs);
    long position();
    void position(long newPosition);
    long size();
    void truncate(long size);
    void force(boolean flushMetaDataToo);
    int  transferTo(long position, int count,
                    WritableByteChannel dst);
    int  transferFrom(ReadableByteChannel src,
                      long position, int count);
    FileLock lock(long position, long size, boolean shared);
    FileLock lock();
    FileLock tryLock(long pos, long size, boolean shared);
    FileLock tryLock();
    static final int MAP_RO, MAP_RW, MAP_COW;
    MappedByteBuffer map(int mode, long position, int size);
}

NOTE: ALL methods throw IOException.

11.9 Selector

abstract class Selector {
    static Selector open() throws IOException;
    Set  keys();
    Set  selectedKeys();
    int  selectNow() throws IOException;
    int  select(long timeout) throws IOException;
    int  select() throws IOException;
    void wakeup();
    void close() throws IOException;
}

11.10 SelectionKey

abstract class SelectionKey {
    static final int OP_READ, OP_WRITE,
                     OP_CONNECT, OP_ACCEPT;
    SelectableChannel channel();
    Selector          selector();
    boolean           isValid();
    void              cancel();
    int               interestOps();
    void              interestOps(int ops);
    int               readyOps();
    boolean           isReadable();
    boolean           isWritable();
    boolean           isConnectable();
    boolean           isAcceptable();
    Object            attach(Object ob);
    Object            attachment();
}