1

(在github上完整的測試程序:https://github.com/olingerc/socketio-copy-large-file燒瓶socketio錯過的事件複製文件在後臺線程

我一起用瓶與瓶-SocketIO插件。我的客戶可以要求服務器通過websocket複製文件,但在文件複製時,我希望客戶端能夠與服務器通信以要求其執行其他操作。我的解決方案是在後臺線程中運行復制過程(shutil)。這是函數:

def copy_large_file(): 
    source = "/home/christophe/Desktop/largefile" 
    destination = "/home/christophe/Desktop/largefile2" 
    try: 
     os.remove(destination) 
    except: 
     pass 
    print("Before copy") 
    socketio.emit('my_response', 
        {'data': 'Thread says: before'}, namespace='/test') 
    shutil.copy(source, destination) 
    print("After copy") 
    socketio.emit('my_response', 
        {'data': 'Thread says: after'}, namespace='/test') 

我遵守以下行爲: 當使用本地socketio方法啓動功能:

socketio.start_background_task(target=copy_large_file) 

所有傳入事件,而一個大文件被複制被延遲,直到文件完成並啓動下一個文件。我客串shutil不relasing的GIL或類似的東西,所以我線程測試:

thread = threading.Thread(target=copy_large_file) 
thread.start() 

相同的行爲。也許多處理?

thread = multiprocessing.Process(target=copy_large_file) 
thread.start() 

啊!這是有效的,並且通過copy_large_file函數內的socketio發射的信號被正確接收。 但是: 如果用戶開始複製一個非常大的文件,關閉瀏覽器並在2分鐘後回來,套接字不再連接到相同的socketio「會話?」並因此不再接收從後臺進程發出的消息。

我猜主要問題是:如何在不阻塞flask-socketio的情況下在後臺複製大文件,但仍能夠在後臺進程中向客戶端發送信號。

測試應用程序可以被用於重現行爲:

在瀏覽器中:

  • 去爲localhost:5000
  • 點擊複製文件
  • 點擊Ping來發送消息,而該文件被複制
  • 也監視着從後臺線程其他信號
+0

如何分配一個房間ID給客戶端,然後發送消息到房間。當客戶回來時,加入前一個房間。 –

回答

1

你正在問兩個不同的問題。

首先,讓我們討論文件的實際複製。

它看起來像你在爲你的服務器使用eventlet。雖然這個框架提供了網絡I/O功能的異步替換,但是在非阻塞的情況下,特別是在Linux上(有關問題here的一些信息),磁盤I/O的處理要複雜得多。正如你已經注意到的那樣,即使使用標準庫猴子修補,文件I/O也會導致阻塞。順便說一下,這跟gevent是一樣的。

對文件執行非阻塞I/O的典型解決方案是使用線程池。使用eventlet,eventlet.tpool.execute函數可以做到這一點。所以基本上,不要直接撥打copy_large_file(),您可以撥打tpool.execute(copy_large_file)。這將使您的應用程序中的其他綠色線程在另一個系統線程中發生時運行。順便說一句,使用另一個進程的解決方案也是有效的,但這可能是過度的,取決於需要多少次以及需要多長時間執行一次這些副本。

第二個問題與「記住」啓動長文件副本的客戶端有關,即使瀏覽器已關閉並重新打開。

這實際上是您的應用程序需要通過存儲恢復客戶端所需的狀態來處理的事情。據推測,您的客戶可以通過令牌或其他身份識別方式來識別您的應用程序。當服務器啓動其中一個文件副本時,它可以爲操作分配一個id,並將該id存儲在與請求它的客戶端關聯的數據庫中。如果客戶端消失並返回,您可以查看是否有任何正在進行的文件副本,並且以這種方式將客戶端同步到關閉瀏覽器之前的方式。

希望這會有所幫助!

+0

很好的答案。注意:您只能將實際的阻止呼叫放入tpool。 'eventlet.tpool.execute(os.remove,path)'和'shutil.copy'相同。 – temoto

+0

@temoto是的,這也是一個選項。 – Miguel

+0

@Miguel。嗯,我嘗試了tpool解決方案,但獲得了相同的結果。整個應用程序被阻止。該文檔甚至規定:函數將在池中的隨機線程中運行,而調用的協程將在其完成時阻塞。 – christophe