java 异步io selector_【JAVA新生】拿协程开始写个异步io应用

前面已经准备好了greenlet对应的Java版本了,一个删减后的kilim(http://segmentfault.com/blog/taowen/1190000000697487)。接下来,就看怎么用协程来实现异步io了。首先,拿一段最最简单的tcp socket accept的代码:

Selector selector = Selector.open();

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9090));

serverSocketChannel.configureBlocking(false);

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("listening...");

selector.select();

scheduler.accept(serverSocketChannel);

System.out.println("hello");

这里使用的是java 6的NIO1的selector模型。直接拿NIO的原始api写代码会死人的。引入协程就是为了把上下连续的业务逻辑放在一个协程里,把与业务关系不大的selector的处理部分放到框架的ioloop里。也就是把一段交织的代码,分成两个关注点不同的组成部分。

改造之后的代码在这里: https://github.com/taowen/daili/tree/1e319f929678213a8d8f63ee5e8b8cf016637317

这是改造之后的效果:

Scheduler scheduler = new Scheduler();

DailiTask task = new DailiTask(scheduler) {

@Override

public void execute() throws Pausable, Exception {

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9090));

serverSocketChannel.configureBlocking(false);

System.out.println("listening...");

scheduler.accept(serverSocketChannel);

System.out.println("hello");

}

};

scheduler.callSoon(task);

scheduler.loop();

其中最关键的一行是 scheduler.accept(serverSocketChannel); 这个调用是阻塞的。但是只阻塞调用它的Task协程。如果有多个Task并行的话,别的Task可以在这个时候被运行。那么scheduler.accept是如何做到把NIO的selector api转换成这样的形式的呢?

public SocketChannel accept(ServerSocketChannel serverSocketChannel) throws IOException, Pausable {

SocketChannel socketChannel = serverSocketChannel.accept();

if (null != socketChannel) {

return socketChannel;

}

SelectionKey selectionKey = serverSocketChannel.keyFor(selector);

if (null == selectionKey) {

selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, new WaitingSelectorIO());

} else {

selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_ACCEPT);

}

WaitingSelectorIO waitingSelectorIO = (WaitingSelectorIO) selectionKey.attachment();

waitingSelectorIO.acceptBlockedAt = System.currentTimeMillis();

waitingSelectorIO.acceptTask = (Task) Task.getCurrentTask();

selectionKey.attach(waitingSelectorIO);

Task.pause(waitingSelectorIO);

return serverSocketChannel.accept();

}

这个函数分成四部分:第一部分是尝试去accept,如果有戏就不用NIO了。第二部分是注册selection key,说明我希望知道什么时候可以accept了,并把task作为附件加上去。第三部分是Task.pause放弃掉执行权。第四部分是task被回调了,说明等待的accept已经ok了,可以去调用了。

但是Task.pause了之后,是谁在把这个暂停的task重新拉起来执行的呢?这个就是scheduler的loop干的活了

public void loop() throws IOException {

while (true) {

executeReadyTasks();

selector.select();

Set selectionKeys = selector.selectedKeys();

for (SelectionKey selectionKey : selectionKeys) {

WaitingSelectorIO waitingSelectorIO = (WaitingSelectorIO) selectionKey.attachment();

if (selectionKey.isAcceptable()) {

Task taskToCall = waitingSelectorIO.acceptTask;

waitingSelectorIO.acceptBlockedAt = 0;

waitingSelectorIO.acceptTask = null;

callSoon(taskToCall);

}

}

}

}

在循环中调用selector.select获得网络事件的通知。如果selection key就绪了,就把附件里的task取出来回调。具体的回调发生在executeReadyTasks内部,其实就是调用一下resume而已。

private void executeReadyTasks() {

Task task;

while((task = readyTasks.poll()) != null) {

executeTask(task);

}

}

private void executeTask(Task task) {

try {

task.resume();

} catch (Exception e) {

LOGGER.error("failed to execute task: " + task, e);

}

}

这样一个只能接收telnet 127.0.0.1 9090打印一行hello的异步io应用就写好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值