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


    用过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

  • 相关阅读:
    cstring string 比较之二(学习笔记)
    转 大话设计模式学习笔记(思维导图) 更新中
    转 十三种设计模式的思维导图
    (转)关于栈、堆、静态存储区最大可分配大小的探讨 海量之一
    如何学习网络协议(学习笔记)
    境界篇:linux 驱动开发的境界(学习笔记)
    b.关于vc编程境界的讨论
    关于编程境界的小结
    Java异常(一) Java异常简介及其架构
    多线程简单阐述
  • 原文地址:https://www.cnblogs.com/wucao/p/4021558.html
Copyright © 2020-2023  润新知