1. 网络服务
Web 服务、分布式对象等,大多数网络服务都有相同的基本结构:
- 读取请求(Read request)
- 解码请求(Decode request)
- 处理服务(Process service)
- 编码响应(Encode reply)
- 发送响应(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
- 类似于 AWT 的
参见: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:
- Read request
- Decode request
- Process service
- Encode reply
- 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();
}