zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    算法----(1)冒泡排序
    淘宝爬虫
    爬虫_豆瓣电影top250 (正则表达式)
    爬虫_猫眼电影top100(正则表达式)
    Android 简单调用摄像头
    Android 简单天气预报
    思维模型
    This view is not constrained, it only has designtime positions, so it will jump to (0,0) unless you
    Android studio preview界面无法预览,报错render problem
    Android studio 3.1.2报错,no target device found
  • 原文地址:https://www.cnblogs.com/wucao/p/4021558.html
Copyright © 2011-2022 走看看