长连接和长任务

2024-03-07 18:37 更新

长连接和长任务# 长连接

技术特点

默认基于Http LongPolling技术实现,未来可能扩展出WebSokcet的实现。实现了单一Page中的长连接自动合并功能,即在单一Page中无论开发者利用Dorado建立多少个长连接,Dorado都只占用个一个物理长连接,并在此物理连接的基础上构建n个虚拟连接。此功能未来可能会进一步扩展,以实现跨Page的连接自动合并。消息收发的自动合并,当客户端或服务端在短时间内频繁的利用长连接发送消息时,Dorado会自动对将一批消息合并为一次物理Request或Response,从而避免浏览器过度频繁的与服务器建立Http连接。

建立连接

客户端

在客户端建立一个长连接的方法是直接调用dorado.Socket.connect()方法,该方法有两个参数——options和callback。callback顾名思义就是长连接建立成功后的回调方法,而options则是长连接的选项,options参数可以包含如下的几个重要的子参数(具体参考API文档): service —— 字符串。服务端ExposedService(可暴露远程服务)的名称。这里的ExposedService就是我们在编写Ajax操作的服务端代码时所使用的那种服务。parameter —— 任意对象,可以为空。即建立连接时传递给服务端的参数。onReceive —— 当收到服务端发来的消息时触发的事件。onDisconnect —— 当连接断开时触发的事件。dorado.Socket.connect()方法的返回值是socket对象,我们可以通过这个对象在做后面的处理. 例如...

var socket = dorado.Socket.connect({ service: "test#messageService" }, function() {
    alert("Socket connected.");
});

服务器端

定义长连接服务端的代码的方法几乎与定义Ajax服务端代码的方法完全一样,唯一的不同是你必须在方法的参数中保留一个名为socket的参数,该参数的类型为com.bstek.dorado.view.socket.Socket。通过该参数我们方便与客户端进行消息收发操作。例如...

@Component
public class Test {
    @Expose
    public void messageService(Socket socket) {
        ... ... 
    }
}

如果客户端指定了建立连接时的参数,那么这里的messageService方法中也可以通过自动适配参数来接收parameter中的数据,方法同Ajax操作的一样,此处不再累述。

收发消息

客户端

客户端收发消息的操作比较简单,要发送消息可以直接使用socket对象的send()方法,该方法包含三个参数: type —— 字符串,消息类型。可以是任意字符串。data —— 任意对象,消息内容。callback —— 消息发送成功的回调方法,即服务端确认收到消息后才会触发的回调方法。例如...

socket.send("chat", "吃了吗?");

客户端接收消息需要使用Socket对象的onReceive事件。例如...

var socket = dorado.Socket.connect({ service: "test#messageService", onReceive: function(arg) {
    alert("收到了来自服务器的消息..." + "\ntype:" + arg.type + "\ndata:" + arg.data);
}});

服务器端

服务端发送消息的方法与客户端非常相似,例如...

socket.send(new Message("消息类型", "消息内容"));

服务端接收消息的方法有两种——阻塞式和非阻塞式。 阻塞式的消息接收 使用阻塞式消息接收通常是在一个独立的线程中,例如我们可能是这样来定义ExposedService的...

@Expose
public void messageService(Socket socket) {
    (new SocketServerThread(socket) {
        @Override
        protected void run(Socket socket)  {
            while (socket.isConnected()) {
                try {
                    Message message = socket.receive(); // 阻塞方法
                    ... ...
                    socket.send(new Message("chat", "呵呵"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
}

非阻塞式的消息接收 非阻塞式的消息接收是利用Socket对象的监听器,它的使用场景相对自由。例如:

@Expose
public void messageService(Socket socket) {
    socket.addReceiveListener(new SocketReceiveListener() {
        public void onReceive(Socket socket, Message message) {
            ... ...
            socket.send(new Message("chat", "呵呵"));
        }
    });
}

长任务

长任务(LongTask)功能是在构建在长连接基础之上的。用于完成那些需要在后台执行较长时间的任务,这些任务的执行时长可以是几十秒、几分钟、几小时、甚至更长,对于这种很长的任务用户几乎不可能一直在网页端等待他们执行结束,但是他们往往又需要随时了解该任务的执行状况 ,或者对正在执行的任务进行一些调度管理。长任务(LongTask)功能正是为了解决用户的上述困扰而提供的。

LongTask的Client端

使用LongTask的方法与使用AjaxAction的方法有些类似,在IDE工具栏的Action中可以找到LongTask这样一个组件,它有如下几个重要的属性... taskName - 该属性的含义与AjaxAction中的service属性是一致的,表示一个后台长任务的服务名称。appearence - 用于指定系统如何向用户展示当前正在执行任务的信息。其中mainTask表示显示一个模态的提示器;daemonTask表示显示一个非模态的提示器;none表示不现实任何提示,此时用户可以利用LongTask的事件自行确定如何展示任务的执行状态。disableOnActive - 表示是否要在检测到有正在执行的任务时自动禁用本控件。作为一个最简单的例子,我们可以直接在LongTask的taskName中定义一个字符串,如 #simpleTask,根据Dorado中提供的新特性,此种简略写法实际表示的值是:<小写字符开头的View名称>#simpleTask。假设我们的View的文件名是TestLongTask.view.xml,那么#simpleTask实际表示testLongTask#simpleTask。(此规则同样适用于AjaxAction.service,DataSet.dataProvider,UpdateAction.dataResolver等属性)

LongTask的Server端

按照默认的开发讨论,我们接下来可以直接在一个名为TestLongTask的JavaBean编写LongTask后端逻辑代码了。例如...

@Component
public class TestLongTask {

 
    @Expose
    public LongTask simpleTask() {
        return new LongTask() {
            public Object call() throws Exception {
                Thread.sleep(5000);
                return null;
            }
        };
    }
}

从上面的代码可见,定义的LongTask后端的方法与Ajax后端的方法非常相似,唯一的要求是此方法必须返回一个LongTask对象。LongTask是java.util.concurrent.Callable<Object>的实现类,会由LongTask的调度器在另一个线程中执行。在最为简单的示例中,我们只需要实现LongTask的call()方法,就已经完成了一个长任务。 LongTask类中有这样几个常用的方法... setStateInfo() - 每次设置的TaskStateInfo对象包含三部分的信息——state、text和data。其中state是enum类型的状态代码,而text和data是当前状态附带的信息,您可以根据自己的需要给text和data设置任意的值。目前Dorado支持的任务状态有....waiting - 等待,例如当任务调度器发现目前正在执行某个任务的实例已经达到了上限,而用户仍希望再开启新的长任务,那么新的长任务将会进入等待队列,即等待状态。running - 正在执行。suspending - 正在尝试挂起。suspended - 挂起。resuming - 正在尝试恢复执行。terminated - 执行结束。aborting - 正在中止,即取消执行。aborted - 已终止,即已取消。error - 出错并以退出。appendLog() - 向客户端输出日志信息,通常是TaskLog对象封装的一段文本。

LongTask的StateInfo是可以重复设置的,比如同样的是处于running状态,我们可以先设置为new TaskStateInfo(TaskState.running, "正在复制第1个文件"),然后再设置为new TaskStateInfo(TaskState.running, "正在复制第2个文件")。 那么既然StateInfo是可以重复设置的,它和Log有什么区别呢? 区别在于StateInfo是一种可以维持的信息,直到我们设置下一个StateInfo之前,当前的StateInfo对于长任务而言都是有效的。而Log则瞬间的,更像是一种调试信息。 举个例子,当一个长任务正在执行时我们刷新了网页,那么当网页刷新完成后Dorado会自动的将该任务的StateInfo同步到Client端,用户可以立即看到目前有一个长任务正在执行,并且它的状态是是“running-正在复制第2个文件”。而对于刷新网页期间长任务输出的Log信息,用户是无法接收到放任,他只能看到从此刻开始的Log信息。

LongTask的调度

在声明一个LongTask的方法时,我们还可以利用@TaskScheduler来为长任务配置简单的调度信息。@TaskScheduler包含以下几个属性... scope - 任务的可见范围,目前包含两种取值 session(默认)和application。maxRunning - 最大可运行实例数。maxWaiting - 最大等待队列的长度。impl - 自定义任务调度器的实现类。(待补充...)对于某个长任务而言,其在scope指定的范围内仅允许存在一个正在运行的实例(包括正在执行的和正在等待执行的)。因此当scope为session时,maxRunning和maxWaiting所表示的是该长任务在整个系统中的运行数和等待数,而不是指当前Session中的。而当scope为application时,maxRunning和maxWaiting将是无效的,因为此时整个系统中事实上只允许存在一个正在运行的实例(包括正在执行的和正在等待执行的)。

实时控制

在LongTask执行的过程中,我们可以通过客户端LongTask的suspend()、resume()、abort()等方法来暂停、恢复或中止LongTask的执行。不过这三个方法都不能自动的完成所有的工作,通常用户都需要编写一定的代码才能真正的实现对LongTask的执行控制。 以abort()操作为例,当用户在Client端调用了abort()方法后,Server端的LongTask对象会自动进入aborting状态,但是LongTask并不会自动的停下来,它仍会以正常的方式继续执行。因为我们不能强行的杀死LongTask目前的执行线程,这种做法是不受推荐且存在隐患的。开发者需要自行判断aborting状态,并且决定以何种方式让LongTask优雅的退出执行。这听起来有些麻烦,不过好在在绝大部分情况下我们并不需要去控制LongTask的执行过程。 下面是一个支持suspend()、resume()、abort()这三种操作的LongTask的代码实例,可以从这里获得一些启发...

@Expose
public LongTask copyFiles() {
    return new LongTask() {
        public Object call() throws Exception {
            for (File file: files) {
                synchronized (this) {
                    if (isSuspendRequired()) {
                        setStateInfo(new TaskStateInfo(TaskState.suspended);
                        wait();
                    }
                    if (isAbortRequired()) {
                        setStateInfo(new TaskStateInfo(TaskState.aborted));
                        break;
                    }
                    FileUtils.copy(file, destDir);
                }
            }
            return null;
        }
        protected void doResume() {
            notify();
            setStateInfo(new TaskStateInfo(TaskState.running));
        }
        protected void doAbort() {
            notify();
        }
    };
}
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号