用过JavaScript或者jQuery的同学都知道,JavaScript特别是jQuery中存在大量的回调函数,例如Ajax、jQuery的动画等。

$.get(url, function() {
doSomething1(); // (3)
}); // (1)
doSomething2(); // (2)

上面的代码是jQuery的Ajax,由于Ajax是异步的,所以在请求URL的过程中并不会阻塞程序,也就是程序运行到(1)并不用等待Ajax请求的结果,就继续往下执行(2)。而$.get的第二个参数是一个回调函数,当Ajax请求完成后,才会调用这个回调函数执行(3)。

这个例子只是用来理解一下异步的概念,没玩过JS看不懂的同学也没关系。

在传统的IO(BIO/阻塞IO)中,所有IO操作都会阻塞当前线程,直到操作完成,所有步骤都是一步一步进行。例如:

OutputStream output = socket.getOutputstream();
out.write(data); // IO操作完成后返回
out.flush();
System.out.println("消息发送完成");

但是在异步网络编程中,由于IO操作是异步的,也就是一个IO操作不会阻塞去等待操作结果,程序就会继续向下执行。

在MINA、Netty、Twisted中,很多网络IO操作都是异步的,比如向网络的另一端write写数据、客户端连接服务器的connect操作等。

例如Netty的write方法(以及writeAndFlush方法),执行完write语句后并不表示数据已经发送出去,而仅仅是开始发送这个数据,程序并不阻塞等待发送完成,而是继续往下执行。所以如果有某些操作想在write完成后再执行,例如write完成后关闭连接,下面这些写法就有问题了,它可能会在数据write出去之前先关闭连接:

Channel ch = ...;
ch.writeAndFlush(message);
ch.close();

那么问题就来了,挖掘机...既然上面的写法不正确,那么如何在IO操作完成后再做一些其他操作?

当异步IO操作完成后,无论成功或者失败,都会再通知程序。至于如何处理发送完成的通知,在MINA、Netty、Twisted中,都会有类似回调函数的实现方式。

Netty:

在Netty中,write及writeAndFlush方法有个返回值,类型是ChannelFuture。ChannelFuture的addListener方法可以添加ChannelFutureListener监听器。ChannelFutureListener接口的抽象方法operationComplete会在write完成(无论成功或失败)时被调用,我们只需要实现这个方法即可处理一些write完成后的操作,例如write完成后关闭连接。

@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg)
throws UnsupportedEncodingException { // 读操作省略... // 发送数据到客户端
ChannelFuture future = ctx.writeAndFlush("message"); // 返回值类型为ChannelFuture
future.addListener(new ChannelFutureListener() { // write操作完成后调用的回调函数
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) { // 是否成功
System.out.println("write操作成功");
} else {
System.out.println("write操作失败");
}
ctx.close(); // 如果需要在write后关闭连接,close应该写在operationComplete中。注意close方法的返回值也是ChannelFuture
}
}); }

上面代码中,close操作是在operationComplete中进行,这样可以保证不会在write完成之前close连接。注意close关闭连接同样是异步的,其返回值也是ChannelFuture。

MINA:

MINA和Netty类似,session.write返回值是WriteFuture类型。WriteFuture也可以通过addListener方法添加IoFutureListener监听器。IoFutureListener接口的抽象方法operationComplete会在write完成(无论成功或失败)时被调用。如果需要在write完成后进行一些操作,只需实现operationComplete方法。

@Override
public void messageReceived(IoSession session, Object message)
throws Exception { // 读操作省略... // 发送数据到客户端
WriteFuture future = session.write("message");
future.addListener(new IoFutureListener<WriteFuture>() { // write操作完成后调用的回调函数
@Override
public void operationComplete(WriteFuture future) {
if(future.isWritten()) {
System.out.println("write操作成功");
} else {
System.out.println("write操作失败");
}
}
});
}

MINA和Netty不同在于关闭连接的close方法。其中无参数的close()方法已经弃用,而是使用带一个boolean类型参数的close(boolean immediately)方法。immediately为true则直接关闭连接,为false则是等待所有的异步write完成后再关闭连接。

所以下面这种写法是正确的:

session.write("message");
session.close(false); // 虽然write是异步的,但是immediately参数为false会等待write完成后再关闭连接

Twisted:

在Twisted中,twisted.internet.defer.Deferred类似于MINA、Netty中的Future,可以用于添加在异步IO完成后的回调函数。但是Twisted并没有在write方法中返回Deffered,虽然write方法也是异步的。下面就用Twisted实现一个简单的TCP客户端来学习Deffered的用法。

# -*- coding:utf-8 –*- 

from twisted.internet import reactor
from twisted.internet.protocol import Protocol
from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol class ClientProtocol(Protocol):
def sendMessage(self):
self.transport.write("Message") # write也是异步的
self.transport.loseConnection() # loseConnection会等待write完成后再关闭连接 # 连接服务器成功的回调函数
def connectSuccess(p):
print "connectSuccess"
p.sendMessage() # 连接服务器失败的回调函数
def connectFail(failure):
print "connectFail" point = TCP4ClientEndpoint(reactor, "localhost", 8080)
d = connectProtocol(point, ClientProtocol()) # 异步连接到服务器,返回Deffered
d.addCallback(connectSuccess) # 设置连接成功后的回调函数
d.addErrback(connectFail) # 设置连接失败后的回调函数
reactor.run()

在Twisted中,关闭连接的loseConnection方法会等待异步的write完成后再关闭,类似于MINA中的session.close(false)。Twisted中想要直接关闭连接可以使用abortConnection,类似MINA中的session.close(true)。

MINA、Netty、Twisted一起学系列

MINA、Netty、Twisted一起学(一):实现简单的TCP服务器

MINA、Netty、Twisted一起学(二):TCP消息边界问题及按行分割消息

MINA、Netty、Twisted一起学(三):TCP消息固定大小的前缀(Header)

MINA、Netty、Twisted一起学(四):定制自己的协议

MINA、Netty、Twisted一起学(五):整合protobuf

MINA、Netty、Twisted一起学(六):session

MINA、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)

MINA、Netty、Twisted一起学(八):HTTP服务器

MINA、Netty、Twisted一起学(九):异步IO和回调函数

MINA、Netty、Twisted一起学(十):线程模型

MINA、Netty、Twisted一起学(十一):SSL/TLS

MINA、Netty、Twisted一起学(十二):HTTPS

源码

https://github.com/wucao/mina-netty-twisted

Mina、Netty、Twisted一起学(九):异步IO和回调函数的更多相关文章

  1. 异步IO与回调

    最好了解 Java NIO 中 Buffer.Channel 和 Selector 的基本操作,主要是一些接口操作,比较简单. 本文将介绍非阻塞 IO 和异步 IO,也就是大家耳熟能详的 NIO 和 ...

  2. JavaScript学习-5——异步同步、回调函数

    ----------异步同步函数 ----------回调函数 一.异步同步函数 同步:发送一个请求,等待返回,然后再发送下一个请求 异步:发送一个请求,不等待返回,随时可以再发送下一个请求 同步可以 ...

  3. c# 异步编程 使用回调函数例子

    环境VS2010, 在项目属性中输出类型选择控制台应用程序 运行结果 using System;using System.Collections.Generic;using System.Compon ...

  4. Mina、Netty、Twisted一起学(八):HTTP服务器

    HTTP协议应该是目前使用最多的应用层协议了,用浏览器打开一个网站就是使用HTTP协议进行数据传输. HTTP协议也是基于TCP协议,所以也有服务器和客户端.HTTP客户端一般是浏览器,当然还有可能是 ...

  5. Mina、Netty、Twisted一起学(十):线程模型

    要想开发一个高性能的TCP服务器,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,如果再搭配上高效率的代码,才能实现一个高大上的服务器.但是如果不了解它 ...

  6. Mina、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)

    消息传递有很多种方式,请求/响应(Request/Reply)是最常用的.在前面的博文的例子中,很多都是采用请求/响应的方式,当服务器接收到消息后,会立即write回写一条消息到客户端.HTTP协议也 ...

  7. Mina、Netty、Twisted一起学(六):session

    开发过Web应用的同学应该都会使用session.由于HTTP协议本身是无状态的,所以一个客户端多次访问这个web应用的多个页面,服务器无法判断多次访问的客户端是否是同一个客户端.有了session就 ...

  8. Mina、Netty、Twisted一起学(五):整合protobuf

    protobuf是谷歌的Protocol Buffers的简称,用于结构化数据和字节码之间互相转换(序列化.反序列化),一般应用于网络传输,可支持多种编程语言. protobuf如何使用这里不再介绍, ...

  9. Mina、Netty、Twisted一起学(三):TCP消息固定大小的前缀(Header)

    在上一篇博文中,有介绍到用换行符分割消息的方法.但是这种方法有个小问题,如果消息中本身就包含换行符,那将会将这条消息分割成两条,结果就不对了. 本文介绍另外一种消息分割方式,即上一篇博文中讲的第2条: ...

随机推荐

  1. CCNA第二章TCP/IP简介考试要点学习笔记

    1.描述网络是如何工作的     DoD过程/应用层 -- OSI应用.表示和会话层(定义了结点到结点的应用通信协议以及对用户界面规范的控制): DoD主机到主机层 -- OSI传输层(保证了数据包的 ...

  2. [1008]harder_prime

    素数定义:一个大于1的整数,如果它的约数如果只有1和它本身,那么它就是一个素数. 回文数定义:一个整数把它的各位数字倒过来还是它本身,那么它就是回文数,比如说2,99,393. 回文素数定义:一个数如 ...

  3. C++模板&泛型编程

    ---恢复内容开始--- 一.泛型编程 定义:编写与类型无关的逻辑代码,是代码复用的一种手段.模板是泛型编程的基础 模板分为:函数模板和类模板 函数模板:代表了一个函数家族,该函数与类型无关,在使用时 ...

  4. 为什么没调用 didRegisterForRemoteNotificationsWithDeviceToken 和 didFailToRegisterForRemoteNotificationsWithError

    一步一步按照网上 push notification 教程走下来,发现didRegisterForRemoteNotificationsWithDeviceToken 和 didFailToRegis ...

  5. \(\S1 \) Gaussian Measure and Hermite Polynomials

    Define on \(\mathbb{R}^d\) the normalized Gaussian measure\[ d \gamma(x)=\frac{1}{(2\pi)^{\frac{d}{2 ...

  6. 蛙蛙推荐:AngularJS学习笔记

    为了降低前端代码的数量,提高可维护性,可测试性,学习了下AngularJS,正在准备投入项目开发中. AngularJS的概念比较多,如果面向对象方面的书理解的不透的话学习起来有些费劲,它的官方有个快 ...

  7. Yii2中的环境配置

    默认的Debug配置 在入口文件中 defined ( 'YII_DEBUG' ) or define ( 'YII_DEBUG', true ); defined ( 'YII_ENV' ) or ...

  8. IOS内测分发策略

    当苹果应用没有上交appstore审核,出于开始内侧阶段时,我们如何邀请别人测试和使用? plist苹果安装包实现 使用七牛云存储解决ios7.1的app部署问题 推荐两个现在比较流行的app内测辅助 ...

  9. CoinPunk项目介绍

           CoinPunk是一个bitcoin比特币钱夹服务web应用程序,你可以自己构建钱夹服务.开源,免费. 轻量级,高效 响应式设计 轻易创建新账户 详细的交易记录 构建于Node.js与H ...

  10. [.NET领域驱动设计实战系列]专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现

    一.引言 在上一专题中,商家发货和用户确认收货功能引入了消息队列来实现的,引入消息队列的好处可以保证消息的顺序处理,并且具有良好的可扩展性.但是上一专题消息队列是基于内存中队列对象来实现,这样实现有一 ...