最近搬砖的帝都民航的啥航电XX设备大数据预测那部分好像黄了。
这边进来一个月不到,我刚动手搬砖就停手项目这套操作是真滴尴尬,不过发现这家有自研测运行维护数据的硬件发送tcp(为啥断断续续短连接不用udp?)报文到InfluxDB使用的是netty,一个臭名昭著大名鼎鼎的玩意,干脆来深入瞎学一下。
但是在看源码之前先复习下反应器模式感觉更好,一种设计模式,在redis,netty等网络应用上使用,看看这个 Doug Lea(令人景仰的神奇大爷)写的NIO.pdf,简单翻译然后自己写点笔记。
发现有错误地方请留言,谢谢。
但使用情景不同XML解释、文件传输、网页生成、电脑端服务等
然后大多都是有自己线程的Handler
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){ /* do something */ }
}
static class Handler implements Runnable {
final Socket socket;
Handler(Socket s) { socket = s }
public void run() {
try{
/* do something */
}catch(IOException ex){ /* do something */ }
}
private byte[] process(byte[] cmd){ /* do something */ }
}
}
分而治之的思想一般是最好的解决上述问题办法
例子java.awt.event:java中awt的事件触发,事件驱动的IO类似这种结构但设计不同
连接文件,sockets等来支持NIO读取
一种类数组对象,可以直接由Channels读写
有IO事件的Channels组
Maintain维护IO事件状态和绑定
我的理解是运行反应器同时构建selector和channel并绑定channel到socket port。同时设置NIO然后注册channel到selector将返回的selectionkey绑定到一个acceptor实例待执行
class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocket;
// 构造方法初始化反应器
Reactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
// 绑定channel到port
serverSocket.socket.bind(new InetSocketAddress(port));
// NIO设置
serverSocket.configureBlocking(false);
// 注册channel到selector并生成accept后的selectionkey(绑定accepyor实例)
SelectionKey sk = serverSocket.register(selector,SelectionKey.OP_ACCEPT);
sk.attach(new Acceptor());
}
/*
// 可替代做法:直接使用清楚的SPI生成者类
// Alternatively, use explicit SPI provider:
SelectorProvider p = SelectorProvider.provider();
selector = p.openSelector();
serverSocket = p.openServerSocketChannel();
*/
获取selector中所有的selecitonkeys并迭代遍历调度执行(新线程)相应的acceptor实例
// 接上Reactor类里
public void run() {
// 新线程内容
try {
while (!Thread.interrupted()) {
selector.select();
// 获取selector里已绑定的keys
Set selected = selector.selectedKeys();
// 迭代处理selectedKeys,调度其绑定的selectors
Iterator it = selected.iterator();
// 当前是否有待执行
while (it.hasNext())
// 调度sk的attachment(acceptor或handler)
dispatch((SelectionKey)(it.next()));
selected.clear();
}
} catch (IOException ex) { /* ... */ }
}
void dispatch(SelectionKey k) {
// 新线程运行selectedKey上面相应附加的acceptor
Runnable r = (Runnable)(k.attachment());
if (r != null)
r.run();
}
独立线程,调度执行sk对应的acceptor看看有无事务
//接上面Reactor类里面
class Acceptor implements Runnable {
// inner
public void run() {
try {
// 检查channel里有没有东西
SocketChannel c = serverSocket.accept();
if (c != null)
// 启动执行事务操作该channel里的东西
new Handler(selector, c);
}catch(IOException ex) { /* ... */ }
}
}
}
处理程序的线程类,也就是实际初始化(也是绑定sk)和执行netty程序的地方
final class Handler implements Runnable {
final SocketChannel socket;
final SelectionKey sk;
// 分配IO流大小
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1;
// 默认状态为读取
int state = READING;
// 构造函数处理传入Selector和Channel
Handler(Selector sel, SocketChannel c) throws IOException {
socket = c;
// NIO设置
c.configureBlocking(false);
// 注册事务到channel0(运行)到当前selector并生成selectionkey(绑定handler实例)
sk = socket.register(sel, 0);
// 当前channel中的第一个selectionkey绑定当前handler实例
sk.attach(this);
sk.interestOps(SelectionKey.OP_READ);
sel.wakeup();
}
boolean inputIsComplete() { /* ... */ }
boolean outputIsComplete() { /* ... */ }
void process() { /* ... */ }
一般搬砖要写的应用操作在这里
// 接上面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;
// Normally also do first write now
sk.interestOps(SelectionKey.OP_WRITE);
}
}
void send() throws IOException {
socket.write(output);
if (outputIsComplete())
sk.cancel();
}
}
初始化时绑定port到channel(属于selector)并生成其所属的selectionkey(绑定acceptor实例),在调度器while中一直检查selector然后调度每一个里面的selectionkey,并创建新线程执行初始化绑定的acceptor实例来处理(若真有东西则new handler实例)
上文中acceptor线程中若真有东西则new handler来构造具体事务并绑定selectionkey到一个独特的channel(本代码中是channel0)等待反应器进程中的while去发现sk执行它,另补充handler实例也就是最后目标程序实现功能的那个线程
port内的事务经过accept到达channel属于selector由reactor调度执行selector里所有的selectionkey对应绑定的的acceptor实例后注册成为handler实例绑定的selectionkey等待执行
多线程读写事务
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(); }
}
}
让反应器动态使用独立的selector、线程、调度循环
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;
}
}
这位大爷的API参考
Buffer
ByteBuffer(CharBuffer, LongBuffer, etc not shown.)
Channel
SelectableChannel
SocketChannel
ServerSocketChannel
FileChannel
Selector
SelectionKey
具体设计PDF里面有我就不献丑了