長(zhǎng)連接和長(zhǎng)任務(wù)

2024-03-07 18:37 更新

長(zhǎng)連接和長(zhǎng)任務(wù)# 長(zhǎng)連接

技術(shù)特點(diǎn)

默認(rèn)基于Http LongPolling技術(shù)實(shí)現(xiàn),未來(lái)可能擴(kuò)展出WebSokcet的實(shí)現(xiàn)。實(shí)現(xiàn)了單一Page中的長(zhǎng)連接自動(dòng)合并功能,即在單一Page中無(wú)論開發(fā)者利用Dorado建立多少個(gè)長(zhǎng)連接,Dorado都只占用個(gè)一個(gè)物理長(zhǎng)連接,并在此物理連接的基礎(chǔ)上構(gòu)建n個(gè)虛擬連接。此功能未來(lái)可能會(huì)進(jìn)一步擴(kuò)展,以實(shí)現(xiàn)跨Page的連接自動(dòng)合并。消息收發(fā)的自動(dòng)合并,當(dāng)客戶端或服務(wù)端在短時(shí)間內(nèi)頻繁的利用長(zhǎng)連接發(fā)送消息時(shí),Dorado會(huì)自動(dòng)對(duì)將一批消息合并為一次物理Request或Response,從而避免瀏覽器過(guò)度頻繁的與服務(wù)器建立Http連接。

建立連接

客戶端

在客戶端建立一個(gè)長(zhǎng)連接的方法是直接調(diào)用dorado.Socket.connect()方法,該方法有兩個(gè)參數(shù)——options和callback。callback顧名思義就是長(zhǎng)連接建立成功后的回調(diào)方法,而options則是長(zhǎng)連接的選項(xiàng),options參數(shù)可以包含如下的幾個(gè)重要的子參數(shù)(具體參考API文檔): service —— 字符串。服務(wù)端ExposedService(可暴露遠(yuǎn)程服務(wù))的名稱。這里的ExposedService就是我們?cè)诰帉慉jax操作的服務(wù)端代碼時(shí)所使用的那種服務(wù)。parameter —— 任意對(duì)象,可以為空。即建立連接時(shí)傳遞給服務(wù)端的參數(shù)。onReceive —— 當(dāng)收到服務(wù)端發(fā)來(lái)的消息時(shí)觸發(fā)的事件。onDisconnect —— 當(dāng)連接斷開時(shí)觸發(fā)的事件。dorado.Socket.connect()方法的返回值是socket對(duì)象,我們可以通過(guò)這個(gè)對(duì)象在做后面的處理. 例如...

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

服務(wù)器端

定義長(zhǎng)連接服務(wù)端的代碼的方法幾乎與定義Ajax服務(wù)端代碼的方法完全一樣,唯一的不同是你必須在方法的參數(shù)中保留一個(gè)名為socket的參數(shù),該參數(shù)的類型為com.bstek.dorado.view.socket.Socket。通過(guò)該參數(shù)我們方便與客戶端進(jìn)行消息收發(fā)操作。例如...

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

如果客戶端指定了建立連接時(shí)的參數(shù),那么這里的messageService方法中也可以通過(guò)自動(dòng)適配參數(shù)來(lái)接收parameter中的數(shù)據(jù),方法同Ajax操作的一樣,此處不再累述。

收發(fā)消息

客戶端

客戶端收發(fā)消息的操作比較簡(jiǎn)單,要發(fā)送消息可以直接使用socket對(duì)象的send()方法,該方法包含三個(gè)參數(shù): type?—— 字符串,消息類型??梢允侨我庾址ata?—— 任意對(duì)象,消息內(nèi)容。callback?—— 消息發(fā)送成功的回調(diào)方法,即服務(wù)端確認(rèn)收到消息后才會(huì)觸發(fā)的回調(diào)方法。例如...

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

客戶端接收消息需要使用Socket對(duì)象的onReceive事件。例如...

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

服務(wù)器端

服務(wù)端發(fā)送消息的方法與客戶端非常相似,例如...

socket.send(new Message("消息類型", "消息內(nèi)容"));

服務(wù)端接收消息的方法有兩種——阻塞式和非阻塞式。 阻塞式的消息接收 使用阻塞式消息接收通常是在一個(gè)獨(dú)立的線程中,例如我們可能是這樣來(lái)定義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對(duì)象的監(jiān)聽(tīng)器,它的使用場(chǎng)景相對(duì)自由。例如:

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

長(zhǎng)任務(wù)

長(zhǎng)任務(wù)(LongTask)功能是在構(gòu)建在長(zhǎng)連接基礎(chǔ)之上的。用于完成那些需要在后臺(tái)執(zhí)行較長(zhǎng)時(shí)間的任務(wù),這些任務(wù)的執(zhí)行時(shí)長(zhǎng)可以是幾十秒、幾分鐘、幾小時(shí)、甚至更長(zhǎng),對(duì)于這種很長(zhǎng)的任務(wù)用戶幾乎不可能一直在網(wǎng)頁(yè)端等待他們執(zhí)行結(jié)束,但是他們往往又需要隨時(shí)了解該任務(wù)的執(zhí)行狀況 ,或者對(duì)正在執(zhí)行的任務(wù)進(jìn)行一些調(diào)度管理。長(zhǎng)任務(wù)(LongTask)功能正是為了解決用戶的上述困擾而提供的。

LongTask的Client端

使用LongTask的方法與使用AjaxAction的方法有些類似,在IDE工具欄的Action中可以找到LongTask這樣一個(gè)組件,它有如下幾個(gè)重要的屬性... taskName -?該屬性的含義與AjaxAction中的service屬性是一致的,表示一個(gè)后臺(tái)長(zhǎng)任務(wù)的服務(wù)名稱。appearence - 用于指定系統(tǒng)如何向用戶展示當(dāng)前正在執(zhí)行任務(wù)的信息。其中mainTask表示顯示一個(gè)模態(tài)的提示器;daemonTask表示顯示一個(gè)非模態(tài)的提示器;none表示不現(xiàn)實(shí)任何提示,此時(shí)用戶可以利用LongTask的事件自行確定如何展示任務(wù)的執(zhí)行狀態(tài)。disableOnActive - 表示是否要在檢測(cè)到有正在執(zhí)行的任務(wù)時(shí)自動(dòng)禁用本控件。作為一個(gè)最簡(jiǎn)單的例子,我們可以直接在LongTask的taskName中定義一個(gè)字符串,如 #simpleTask,根據(jù)Dorado中提供的新特性,此種簡(jiǎn)略寫法實(shí)際表示的值是:<小寫字符開頭的View名稱>#simpleTask。假設(shè)我們的View的文件名是TestLongTask.view.xml,那么#simpleTask實(shí)際表示testLongTask#simpleTask。(此規(guī)則同樣適用于AjaxAction.service,DataSet.dataProvider,UpdateAction.dataResolver等屬性)

LongTask的Server端

按照默認(rèn)的開發(fā)討論,我們接下來(lái)可以直接在一個(gè)名為TestLongTask的JavaBean編寫LongTask后端邏輯代碼了。例如...

@Component
public class TestLongTask {

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

從上面的代碼可見(jiàn),定義的LongTask后端的方法與Ajax后端的方法非常相似,唯一的要求是此方法必須返回一個(gè)LongTask對(duì)象。LongTask是java.util.concurrent.Callable<Object>的實(shí)現(xiàn)類,會(huì)由LongTask的調(diào)度器在另一個(gè)線程中執(zhí)行。在最為簡(jiǎn)單的示例中,我們只需要實(shí)現(xiàn)LongTask的call()方法,就已經(jīng)完成了一個(gè)長(zhǎng)任務(wù)。 LongTask類中有這樣幾個(gè)常用的方法... setStateInfo() -?每次設(shè)置的TaskStateInfo對(duì)象包含三部分的信息——state、text和data。其中state是enum類型的狀態(tài)代碼,而text和data是當(dāng)前狀態(tài)附帶的信息,您可以根據(jù)自己的需要給text和data設(shè)置任意的值。目前Dorado支持的任務(wù)狀態(tài)有....waiting - 等待,例如當(dāng)任務(wù)調(diào)度器發(fā)現(xiàn)目前正在執(zhí)行某個(gè)任務(wù)的實(shí)例已經(jīng)達(dá)到了上限,而用戶仍希望再開啟新的長(zhǎng)任務(wù),那么新的長(zhǎng)任務(wù)將會(huì)進(jìn)入等待隊(duì)列,即等待狀態(tài)。running - 正在執(zhí)行。suspending - 正在嘗試掛起。suspended - 掛起。resuming - 正在嘗試恢復(fù)執(zhí)行。terminated - 執(zhí)行結(jié)束。aborting - 正在中止,即取消執(zhí)行。aborted - 已終止,即已取消。error - 出錯(cuò)并以退出。appendLog() - 向客戶端輸出日志信息,通常是TaskLog對(duì)象封裝的一段文本。

LongTask的StateInfo是可以重復(fù)設(shè)置的,比如同樣的是處于running狀態(tài),我們可以先設(shè)置為new TaskStateInfo(TaskState.running, "正在復(fù)制第1個(gè)文件"),然后再設(shè)置為new TaskStateInfo(TaskState.running, "正在復(fù)制第2個(gè)文件")。 那么既然StateInfo是可以重復(fù)設(shè)置的,它和Log有什么區(qū)別呢? 區(qū)別在于StateInfo是一種可以維持的信息,直到我們?cè)O(shè)置下一個(gè)StateInfo之前,當(dāng)前的StateInfo對(duì)于長(zhǎng)任務(wù)而言都是有效的。而Log則瞬間的,更像是一種調(diào)試信息。 舉個(gè)例子,當(dāng)一個(gè)長(zhǎng)任務(wù)正在執(zhí)行時(shí)我們刷新了網(wǎng)頁(yè),那么當(dāng)網(wǎng)頁(yè)刷新完成后Dorado會(huì)自動(dòng)的將該任務(wù)的StateInfo同步到Client端,用戶可以立即看到目前有一個(gè)長(zhǎng)任務(wù)正在執(zhí)行,并且它的狀態(tài)是是“running-正在復(fù)制第2個(gè)文件”。而對(duì)于刷新網(wǎng)頁(yè)期間長(zhǎng)任務(wù)輸出的Log信息,用戶是無(wú)法接收到放任,他只能看到從此刻開始的Log信息。

LongTask的調(diào)度

在聲明一個(gè)LongTask的方法時(shí),我們還可以利用@TaskScheduler來(lái)為長(zhǎng)任務(wù)配置簡(jiǎn)單的調(diào)度信息。@TaskScheduler包含以下幾個(gè)屬性... scope - 任務(wù)的可見(jiàn)范圍,目前包含兩種取值 session(默認(rèn))和application。maxRunning - 最大可運(yùn)行實(shí)例數(shù)。maxWaiting - 最大等待隊(duì)列的長(zhǎng)度。impl - 自定義任務(wù)調(diào)度器的實(shí)現(xiàn)類。(待補(bǔ)充...)對(duì)于某個(gè)長(zhǎng)任務(wù)而言,其在scope指定的范圍內(nèi)僅允許存在一個(gè)正在運(yùn)行的實(shí)例(包括正在執(zhí)行的和正在等待執(zhí)行的)。因此當(dāng)scope為session時(shí),maxRunning和maxWaiting所表示的是該長(zhǎng)任務(wù)在整個(gè)系統(tǒng)中的運(yùn)行數(shù)和等待數(shù),而不是指當(dāng)前Session中的。而當(dāng)scope為application時(shí),maxRunning和maxWaiting將是無(wú)效的,因?yàn)榇藭r(shí)整個(gè)系統(tǒng)中事實(shí)上只允許存在一個(gè)正在運(yùn)行的實(shí)例(包括正在執(zhí)行的和正在等待執(zhí)行的)。

實(shí)時(shí)控制

在LongTask執(zhí)行的過(guò)程中,我們可以通過(guò)客戶端LongTask的suspend()、resume()、abort()等方法來(lái)暫停、恢復(fù)或中止LongTask的執(zhí)行。不過(guò)這三個(gè)方法都不能自動(dòng)的完成所有的工作,通常用戶都需要編寫一定的代碼才能真正的實(shí)現(xiàn)對(duì)LongTask的執(zhí)行控制。 以abort()操作為例,當(dāng)用戶在Client端調(diào)用了abort()方法后,Server端的LongTask對(duì)象會(huì)自動(dòng)進(jìn)入aborting狀態(tài),但是LongTask并不會(huì)自動(dòng)的停下來(lái),它仍會(huì)以正常的方式繼續(xù)執(zhí)行。因?yàn)槲覀儾荒軓?qiáng)行的殺死LongTask目前的執(zhí)行線程,這種做法是不受推薦且存在隱患的。開發(fā)者需要自行判斷aborting狀態(tài),并且決定以何種方式讓LongTask優(yōu)雅的退出執(zhí)行。這聽(tīng)起來(lái)有些麻煩,不過(guò)好在在絕大部分情況下我們并不需要去控制LongTask的執(zhí)行過(guò)程。 下面是一個(gè)支持suspend()、resume()、abort()這三種操作的LongTask的代碼實(shí)例,可以從這里獲得一些啟發(fā)...

@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();
        }
    };
}
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)