Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 日本一区二区精品88,国产男女爽爽爽爽爽免费视频,日本国产最新一区二区三区

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          真正的 C++ 殺手不是 Rust

          C++ 已經死了 80%?”本文作者已經使用 C++ 18 年了,他在體驗了數十門編程語言后,他指出,盡管 C++ 在過去幾十年中一直是程序員最常用的編程語言之一,但它存在一些問題,如不安全、效率低、浪費程序員的精力等。因此,文章探討了一些可能會取代 C++ 的語言和技術,包括 Spiral、Numba 和 ForwardCom 等,并分別對它們進行了詳細的介紹。

          原文鏈接:https://wordsandbuttons.online/the_real_cpp_killers.html

          以下為譯文:

          我是 C++ 粉,已經用 C++ 寫了 18 年代碼,而在這 18 年里,我一直在努力擺脫 C++。

          一切始于 2005 年末的一個三維空間模擬引擎。該引擎具備了當時 C++ 所有的特性,三星指針、八層依賴關系,以及無處不在的 C 風格的宏。還有一些匯編代碼片段,Stepanov 風格的迭代器,以及 Alexandrescu 風格的元編碼。總之是應有盡有。那么,為什么呢?

          因為這款引擎前后歷時 8 年的時間,經手了 5 個不同的團隊。每個團隊都把自己喜歡的時髦技術帶到了項目中,用時髦的包裝方式包裹舊代碼,但真正為引擎本身添加的價值卻很少。

          起初,我認真地嘗試理解每一處小細節(jié),但在碰了一鼻子灰之后,我放棄了。我還是老老實實完成任務,改 bug 吧。不能說我的工作效率很高,只能說很勉強,不至于被解雇。但后來我的老板問我:“你想把部分匯編代碼改成 GLSG 嗎?”雖然我并不了解GLSL是什么,但我覺得總不至于還不如 C++ 吧,于是我答應了。結果確實不至于還不如 C++。

          后來,大部分的時間里我仍在用 C++ 寫代碼,但每當有人問我:“你想不想嘗試一些非 C++ 的工作?”我就會說:“當然!”然后我就會去做。我寫過 C89、MASM32、C#、PHP、Delphi、ActionScript、JavaScript、Erlang、Python、Haskell、D、Rust,以及令人聞風喪膽的腳本語言 InstallShield。我甚至還寫過 VisualBasic、bash,以及幾種不能公開談論的專有語言。我甚至編寫過自己的語言,我寫了一個簡單的 Lisp 風格解釋器,幫助游戲設計師自動加載資源,然后去度假了。回來后發(fā)現他們用這個解釋器編寫了整個游戲場景,所以在接下來的一段時間里我們必須支持這個解釋器。

          在過去的 17 年里,我一直在努力擺脫 C++,但每次嘗試過新技術后,總是會回到 C++。盡管如此,我仍然認為使用 C++ 編寫程序是一個壞習慣。這門語言并不安全,效率也達不到人們的期望,而且程序員需要在與軟件制作毫無關系的工作上浪費大量精力。你知道在 MSVC 中 uint16_t(50000) + uint16_t(50000) == -1794967296 嗎?你知道為什么嗎?你的看法與我不謀而合。

          我認為,作為一名長期使用 C++ 的程序員,我有責任勸誡年輕一代程序員不要將 C++ 作為自己的專攻語言,就像有不良嗜好的人有責任勸誡不要重蹈覆轍。

          那么,為什么我無法放棄 C++ 呢?問題出在哪里?問題在于,所有的編程語言,尤其是那些所謂的“C++ 殺手”,真正帶來的優(yōu)勢都未能超越 C++。這些新語言大多會從一定程度上約束程序員。這本身沒什么問題,畢竟當年晶體管密度每 18 個月翻一番,而程序員的數量每 5 年才翻一番,糟糕的程序員寫不出優(yōu)秀的代碼也并不是什么大問題。

          如今,我們生活在 21 世紀。經驗豐富的程序員數量超過了歷史任何時期,而且我們更需要高效的軟件。

          上個世紀,編寫軟件很簡單。你有一個想法,然后將其包裝成 UI,再作為桌面系統軟件產品出售就可以了。運行速度太慢?沒人在乎!18 個月內,臺式機的速度就會翻倍。重要的是進入市場,打開銷路,而且還沒有 bug。當然,如果編譯器能防止程序員犯錯就更好了,因為 bug 不但不會產生收益,而且你還要付錢給程序員改 bug。

          而如今情況大不相同了。你有一個想法,然后將其包裝到 Docke 容器中,并在云中運行。如今想獲取收入,你的軟件就必須為用戶解決問題。即使一款產品只做一件事,但只要做的正確,就能獲得報酬。你不必為了銷售新版本的產品而不斷擴充功能。相反,如果你的代碼發(fā)揮不了真正的作用,買單的就是你自己。云賬單就能真實地反映出你的程序是否真的起作用。

          因此,在新的環(huán)境下,你需要的功能更少,但所有的功能都需要更出色的性能。

          在這個前提下你就會發(fā)現,所有的“C++ 殺手”,甚至是我由衷喜歡和尊敬的 Rust、Julia 和 D,也沒有解決 21 世紀的問題。它們仍然停留在上個世紀。雖然這些語言可以幫助你編寫更多功能,而且 bug 更少,但當你需要從租用的硬件中壓榨出最后一點 FLOPS 時,它們就沒有太大用處了。

          因此,這些語言只不過是比 C++ 更具競爭優(yōu)勢,或者說彼此之間可以競爭。但大多數編程語言,例如 Rust、Julia 和 Cland,甚至共享同一個后端。所有賽車手都坐在同一輛車上,何談誰能贏得比賽呢?

          那么,究竟哪些技術比 C++ 或者傳統的預先編譯器更有優(yōu)勢呢?

          C++的頭號殺手:Spiral

          在討論 Spiral 之前,讓我先來考考你。你覺得以下哪個版本的代碼運行速度更快?版本1:標準的 C++ 正弦函數;版本2:由4個多項式模型組成的正弦函數?

          下一個問題。以下哪個版本的代碼運行速度更快?版本1:使用短路邏輯運算;版本2:將邏輯表達式轉換為算術表達式?

          第三個問題,以下哪個版本的三元組排序更快?版本1:帶有分支的交換排序;版本2:無分支的索引排序?

          如果你果斷地回答了以上所有問題,甚至沒有思考或上網搜索,那么只能說你被自己的直覺騙了。你沒有發(fā)現陷阱嗎?在沒有上下文的情況下,這些問題都沒有確定的答案。

          1. 如果使用 clang 11 和 -O2 -march=native 構建,在英特爾Core i7-9700F 上運行,多項式模型比標準正弦快 3 倍。但如果使用 NVCC 和 --use-fast-math 構建,在GeForce GTX 1050 Ti Mobile 上運行,標準正弦比多項式模型快10 倍。
          2. 在 i7 上,如果將短路邏輯替換為向量化算術,可以將代碼的運行速度提高一倍。但在 ARMv7 上,使用 clang 和-O2,標準邏輯比微優(yōu)化快 25%。
          3. 對于索引排序與交換排序,在英特爾上,索引排序比交換排序快 3 倍;而在 GeForce 上,交換排序比索引排序快 3 倍。

          因此,我們喜愛的微優(yōu)化都有可能將代碼的運行提升3倍,也有可能導致速度下降90%。這完全取決于上下文。如果編譯器能為我們選擇最佳替代方案,那該多好,例如,當我們切換構建目標時,索引排序會神奇地變成交換排序。但可惜編譯器做不到。

          1. 即使我們允許編譯器將正弦函數換成多項式模型,用犧牲精度的代價換取速度,它也不清楚我們的目標精度。在 C++ 中,我們無法表達:“此函數允許有誤差”。我們只有--use-fast-math之類的編譯器標志,而且只在翻譯單元的范圍內。
          2. 在第二個示例中,編譯器不知道我們的值僅限于 0 或 1,而且也不可能提出可以實施的優(yōu)化。雖然我們可以通過布爾類型來暗示,但這又是另一個問題了。
          3. 在第三個示例中,兩段代碼完全不同,編譯器無法將二者視為等效代碼。代碼描寫了太多細節(jié)。如果只有 std::sort,就可以給編譯器更多自由選擇算法的空間。但它不會選擇索引排序或交換排序,因為這兩種算法處理大型數組的效率都很低,而 std::sort 適合通用可迭代容器。

          此處就不得不提到 Spiral 了。該語言是卡內基梅隆大學和蘇黎世聯邦理工學院的聯合項目。簡單來說,信號處理專家厭倦了每出現一種新硬件就需要手動重寫他們喜歡的算法,因此編寫了一個可自動完成這項工作的程序。該程序接受算法的高級描述和硬件架構的詳細描述,并優(yōu)化代碼,直到在指定的硬件上實現最高效的算法。

          與 Fortran 等語言不同,Spiral 真正解決了數學意義上的優(yōu)化問題。它將運行時定義為目標函數,并在受硬件架構限制的可變因素空間內尋找全局最優(yōu)實現。編譯器永遠無法真正實現這種優(yōu)化。

          編譯器不會尋找真正的最優(yōu)解。它只不過是根據程序員所教的啟發(fā)式規(guī)則來優(yōu)化代碼。實質上,編譯器并不是一個尋找最優(yōu)解的機器,更像一個匯編程序員。一個好的編譯器就像一個好的匯編程序員,僅此而已。

          Spiral是一個研究項目,范圍和預算都很有限。但最后展現的結果卻很驚人。在快速傅里葉變換中,他們的解決方案明顯優(yōu)于 MKL 和 FFTW 的實現,他們的代碼速度約快了 2 倍,即使在英特爾上也是如此。

          為了突顯如此宏大的成就,需要說明一下,MKL 是英特爾自己的數學內核庫(Math Kernel Library,簡稱MKL),因此他們非常了解如何充分利用自家的硬件。而WWTF(Fastest Fourier Transform in the West,西部最快傅里葉變換)是一種高度專業(yè)化的庫,由最了解該算法的人編寫。二者都是各自領域的冠軍,而 Spiral 的速度能夠達到二者兩倍,這實在太不可思議了。

          等到 Spiral 使用的優(yōu)化技術最終成熟并商業(yè)化,不僅僅是 C++,包括 Rust、Julia,甚至 Fortran 都將面臨前所未有的競爭壓力。既然能使用高級算法描述語言編寫2倍速的代碼,誰還會使用C++呢?

          C++ 殺手之二:Numba

          相信你很熟悉這門優(yōu)秀的編程語言。幾十年來,大多數程序員來說最熟悉的語言一直是 C。在 TIOBE 指數中,C語言一直名列第一,其他類似 C 的語言占據了前十名。然而,兩年前,一件前所未聞的事情發(fā)生了,C 語言第一名的地位不保。

          取而代之的語言是Python。90年代,沒有人看好Python,因為它不過是眾多腳本語言中的一個。

          有人會說:“Python很慢”,但這種說法很荒謬,就像說手風琴或平底鍋很慢一樣,語言本身沒有快慢之分。就像手風琴的速度取決于演奏者一樣,語言的快慢取決于編譯器的速度。

          可能還會有人說:“Python不是一種編譯語言”,這個說法也不嚴謹。Python 編譯器有很多,其中一個最被看好的編譯器也算是Python腳本。我來解釋一下。

          我曾經有一個項目,是一個3D打印模擬,最初是用Python編寫的,后來“為了性能”改用C++重寫,后來又移植到 GPU 上,當然這些都是在我進入項目之前發(fā)生的事兒。后來,我花了幾個月的時間將構建遷移到 Linux,優(yōu)化了 Tesla M60 的 GPU 代碼,因為這是當時AWS中最便宜的GPU。之后,我又在 C++/CU 代碼中驗證了所有變更,以便與原來的Python代碼相結合。除了設計幾何算法之外,所有的工作都是由我完成的。

          在一切正常運行后,Bremen 的一名兼職學生打電話給我問道:“聽說你很擅長使用多種技術,能幫我在 GPU 上運行一個算法嗎?”“當然可以!”我給他講了CUDA、CMake、Linux 構建、測試以及優(yōu)化等等,大約花了一個小時。他很有禮貌地聽完了我的介紹,最后說:“很有意思,但我想問一個非常具體的問題。我有一個函數,我在函數的定義前面加了@cuda.jit,Python就無法編譯內核了,還提示了一些關于數組的錯誤。你知道這里面有什么問題嗎?”

          我不知道。后來,他花了一天時間自己搞清楚了。原因是,Numba 無法處理原生的Python列表,只接受 NumPy 數組中的數據。他找到了問題所在,并在 GPU 上運行了算法。使用的是Python。他沒有遇到我花費了幾個月心思解決的任何“問題”。想在 Linux 上運行代碼?沒問題,直接在Linux運行即可。想針對目標平臺優(yōu)化代碼?也不是問題。Numba 會替你優(yōu)化在平臺上運行的代碼,因為它不會預先編譯代碼,而是在部署時按需編譯。

          很厲害,對不對?然而,對我來說并不是。我花費了幾個月的時間,使用C++解決 Numba 中不會出現的問題,而那位Bremen的兼職學生完成相同的工作只花費了幾天的時間。如果不是因為那是他第一次使用Numba,可能只需要幾個小時。說到底,Numba是什么?它是一種什么樣的魔法?

          沒有魔法。Python 的裝飾器將每一段代碼都轉換成了抽象語法樹,因此你可以隨意處理。Numba是一個 Python 庫,可使用任何后端、為任何支持的平臺編譯抽象語法樹。如果你想將Python 代碼編譯成以高度并行的方式在 CPU 核心上運行,只需告訴 Numba 編譯即可。如果你希望在GPU上運行代碼,同樣只需提出請求即可。

          Numba是一個Python編譯器,可以淘汰C++。然而,從理論上來說,Numba并沒有超越C++,因為二者使用的是同一個后端。Numba的GPU編程使用了CUDA,CPU編程使用了LLVM。實際上,由于它不需要針對每種新的架構提前重建,因此能夠更好地適應每種新硬件及其潛在的優(yōu)化。

          當然,如果Numba能像Spiral那樣具有顯著的性能優(yōu)勢會更好。但Spiral更像是一個研究項目,最終可能會淘汰C++,但前提是足夠幸運才行。Numba與Python的結合可以立即判C++死刑。如果可以使用Python編程,而且能擁有C++的性能,誰還會寫C++代碼呢?

          C++ 殺手之三:ForwardCom

          下面,我們再玩一個游戲。我給你三段代碼,你猜猜哪一段(也有可能是多段)是用匯編語言編寫的。

          第一段代碼:

          第二段代碼:

          第三段代碼:

          如果你猜到這三個例子都是匯編,那么恭喜你!

          第一個例子是用 MASM32 編寫的。這是一個帶有“if”和“while”的宏匯編器,用于編寫原生Windows 應用程序。注意,不是以前有人這么寫,而是至今仍在采用這種寫法。微軟一直在積極維護Windows 與 Win32 API 的向后兼容性,因此所有以前編寫的 MASM32 程序都可以在現代 PC 上正常運行。

          很諷刺的是,C 語言的發(fā)明是為了降低將 UNIX 從PDP-7 轉換成 PDP-11 的難度。C語言的設計初衷就是成為一種便攜式匯編語言,能夠在 70 年代硬件架構的寒武紀爆發(fā)中生存下來。但在 21 世紀,硬件架構的演變如此緩慢,我在 20 年前用 MASM32 寫的程序如今仍然能完美運行,但我不敢確定去年用 CMake 3.21 構建的 C++ 應用程序今時今日能否用 CMake 3.25 構建。

          第二段代碼是 WebAssembly,這門技術甚至不是一個宏匯編器,沒有“if”和“while”,更像是人類可讀的瀏覽器機器碼。從概念上來說,可以是任何瀏覽器。

          WebAssembly代碼根本不依賴于硬件架構。它提供的機器是抽象的、虛擬的、通用的,隨你怎么稱呼它。如果你能閱讀這段文字,說明你的物理機器上已經有一個能運行WebAssembly的硬件架構了。

          最有趣的是第三段代碼。這是 ForwardCom:一款由著名的 C++ 和匯編優(yōu)化手冊作者 Agner Fog 提出的匯編器。與 Web Assembly 一樣,這不僅僅是一個匯編器,而且旨在實現向后以及向前兼容性的通用指令集。因此得名。ForwardCom 的全稱是an open forward-compatible instruction set architecture(一款開放式向前兼容指令集架構)。換句話說,它不僅是一個匯編器的提議,而且也是一份和平條約提議。

          我們知道最常見的計算機架構系列 x64、ARM 和 RISC-V 都有不同的指令集。但沒有人知道為什么要保持這種狀態(tài)。所有現代處理器,除了最簡單的一些之外,運行的都不是你提供的代碼,而是將你的輸入轉換為微碼。因此,不僅M1芯片提供英特爾的向后兼容層,每個處理器本質上都為自己的早期版本提供了向后兼容層。

          那么,為什么架構設計者未能就類似的向前兼容層達成統一意見呢?無外乎各個公司之間的競爭野心。但如果處理器制造商最終決定建立一個共同的指令集,而不是為每個競爭對手實現一個新的兼容層,ForwardCom就能夠讓匯編重回主流。這種向前兼容層可以治愈每個匯編程序員最大的心理創(chuàng)傷:“如今我為這個特定的架構編寫一次性代碼,不出一年就會被淘汰?”

          有了向前兼容層,這些代碼就永遠不會過時。這就是關鍵所在。

          此外,匯編編程還受到了另一種錯誤觀念的限制,人們普遍認為匯編代碼太難寫,因此不實用。Fog 的提議也解決了這個問題。如果人們認為寫匯編代碼太難,而寫 C 不難,那么我們就把匯編變成C語言。這不是問題。現代匯編語言沒有必要延續(xù)50年代祖宗的模樣。

          上面你看到的三個匯編示例都不像“傳統”的匯編,而且也不應該還是老樣子。

          ForwardCom是一種匯編,可用于編寫永遠不會過時的最佳代碼,并且不需要學習“傳統”的匯編。從現實的角度來看率,ForwardCom是未來的 C。不是 C++。

          C++ 什么時候終消亡?

          我們生活在一個后現代世界。與世長辭的不是技術,而是人。就像拉丁語從未真正消失一樣,COBOL、Algol 68 和 Ada 也一樣,C++ 注定要永遠介于生死參半的狀態(tài)。C++ 永遠不會真正消失,它只會被更新更強大的新技術所取代。

          嚴格來說,不是“將來會被取代”,而是“正在被取代”。我的職業(yè)生涯源自 C++,而如今在使用 Python 寫代碼。我編寫方程式,SymPy 幫我求解,然后將解決方案轉換為 C++。然后,我將這段代碼粘貼到 C++ 庫中,甚至都無需調整格式,因為 clang-tidy 會自動完成。靜態(tài)分析器會檢查命名空間是否混亂,動態(tài)分析器會檢查內存泄漏。CI/CD 負責跨平臺編譯。性能分析器讓我了解代碼實際的運行情況,反匯編器可以解釋為什么。

          如果我用 C++ 之外的技術代替 C++,那么 80% 的工作不會有變化。對于我的大多數工作來說,C++ 根本無關緊要。這是否意味著,對于我來說,C++ 已經死了 80%?

          icpick是一款非常實用的屏幕截圖工具,軟件為用戶提供了多種截取屏幕的方式,如全屏、活動窗口、窗口空間、滾動窗口、矩形區(qū)域、固定區(qū)域、任意形狀等等,用戶們可以根據自己的需求進行選擇使用,滿足了不同用戶的使用需求,同時還提供了眾多的實用工具供用戶使用,比如取色器、放大鏡、標尺、調色板、坐標軸、量角器、白板等等,在截圖之后就可以直接使用這些實用工具制作你需要的素材圖片了,輕松就可以幫你制作出最優(yōu)質的圖片,并且還可以對圖片進行添加各種的效果,因此軟件內置了一個強大的編輯器,用戶可以進行添加文本、陰影、水印等一系列操作,非常的方便實用。總之使用picpick截圖軟件可以輕松幫你抓取到全屏幕或是局部的畫面,可以隨意進行修改,而且操作也很簡單,是一款非常優(yōu)秀的截圖工具。picpick下載安裝-picpick截圖軟件下載 v5.1.9中文免費版 - 多多軟件站

          軟件功能

          1、截獲任何截圖
          截獲屏幕截圖、活動窗口的截圖、桌面滾動窗口的截圖和任何特定區(qū)域的截圖等等。
          2、編輯你的圖片
          注釋并標記您的圖片:您可以使用內置圖片編輯器中的文本、箭頭、形狀和更多功能,并且該編輯器還帶有最新的Ribbon風格菜單。
          3、增強效果
          為你的圖片添加各種效果:陰影、框架、水印、馬賽克、運動模糊和亮度控制等等。
          4、分享到任何地方
          通過網絡、郵件、ftp、Dropbox、Google Drive、SkyDrive、Box、Evernote、Facebook、Twitter和其它更多方式來保存、分享或發(fā)送你的照片。
          5、平面設計附件
          各種平面設計附件包括顏色選擇器、顏色調色板、像素標尺、量角器、瞄準線、放大鏡和白板。
          6、自定義設置
          軟件帶有各種高級的設置,您可以自定義快捷鍵、文件命名、圖片質量和許多其它的功能。

          picpick怎么修改快捷鍵?

          1、點擊左上角的文件,然后在彈出窗口中選擇程序選項;


          2、在打開的窗口左側有一項是快捷鍵,點擊打開快捷鍵窗口;


          3、在快捷鍵窗口中根據自己的喜好,分別設置需要的快捷鍵。設置完成后記得點擊底部的確定按鈕進行保存。

          picpick怎么滾動截圖?

          1、首先打開軟件,然后點擊滾動窗口;


          2、這時候,可以看到窗口的外圍有個綠色的框框,不要亂動鼠標;


          3、用鼠標點擊右側的滾動條,然后不要動鼠標,鍵盤和電腦;


          4、picpick自己截屏;


          5、當滾動條滾動到最下方的時候,截屏結束,picpick自動保存圖片,打開圖片編輯界面;


          6、可以看一下,由于電腦自運行比較慢的問題,有的地方截取效果不好,這需要自己修改。

          軟件特色

          1、友好的用戶界面,提供Windows 7的 Ribbon 樣式;
          2、擁有基本的編輯繪圖、形狀、指示箭頭、線條、文本等功能;
          3、同時還支持模糊,銳化,色調,對比度,亮度,色彩平衡,像素化,旋轉,翻轉,邊框等效果;
          4、支持共享截圖至 FTP 、Web 、E-Mail、Facebook、Twitter 等社交網絡;
          5、屏幕截圖:支持全屏、活動窗口、滾動窗口 、窗口控制、區(qū)域、固定區(qū)域、手繪、重復捕捉;
          6、Ribbon界面圖像編輯器:箭頭、線條等繪圖工具。模糊、銳化、像素化、旋轉、翻轉,框架等特效;
          7、拾色器和調色板:支持RGB、HTML、C++、Delphi等代碼類型,Photoshop風格轉換,保存顏色;
          8、屏幕放大鏡、量角器、屏幕坐標計算功能,為你的演示文稿把屏幕當作白板自由繪畫。

          PicPick截圖之后怎么批量保存圖片?

          1、打開PicPick,并使用PicPick截圖多張圖片。此時圖片處于未保存狀態(tài);


          2、然后我們點擊PicPick左上角的文件;


          3、接下來我們點擊如下圖所示的保存(或者另存為);


          4、然后我們看到并點擊“全部保存”;


          5、在彈出的保存窗口中,點擊三個點的圖標,找到你的圖片要保存的位置;


          6、之后點擊確定,即可完成批量保存。

          載大文件時,斷點續(xù)傳是很有必要的,特別是網速度慢且不穩(wěn)定的情況下,很難保證不出意外,一旦意外中斷,又要從頭下載,會很讓人抓狂。斷點續(xù)傳就能很好解決意外中斷情況,再次下載時不需要從頭下載,從上次中斷處繼續(xù)下載即可,這樣下載幾G或十幾G大小的一個文件都沒問題。本文介紹利用miniframe開源Web框架分別在lazarus、delphi下實現文件HTTP下載斷點續(xù)傳的功能。

          本文Demo還實現了批量下載文件,同步服務器上的文件到客戶端的功能。文件斷點續(xù)傳原理:分塊下載,下載后客戶端逐一合并,同時保存已下載的位置,當意外中斷再次下載時從保存的位置開始下載即可。這其中還要保證,中斷后再次下載時服務器上相應的文件如果更新了,還得重新下載,不然下載到的文件是錯了。說明:以下代碼lazarus或delphi環(huán)境下都能使用。全部源碼及Demo請到miniframe開源web框架下載: https://www.wyeditor.com/miniframe/或https://github.com/dajingshan/miniframe。

          服務器端代碼

          文件下載斷點續(xù)傳服務器端很簡單,只要提供客戶端要求下載的開始位置和指定大小的塊即可。

          以下是服務器獲取文件信息和下載一個文件一塊的代碼:

          <%@//Script頭、過程和函數定義
          program codes;
          %>
           
          <%!//聲明變量
          var
            i,lp: integer;
            FileName, RelativePath, FromPath, ErrStr: string;
            json: TminiJson;
            FS: TFileStream;
            
          function GetOneDirFileInfo(Json: TminiJson; Path: string): string;
          var
            Status: Integer;
            SearchRec: TSearchRec;
            json_sub: TminiJson;
          begin
            Path := PathWithSlash(Path);
            SearchRec := TSearchRec.Create;
            Status := FindFirst(Path + '*.*', faAnyFile, SearchRec);
            try
              while Status = 0 do
              begin 
                if SearchRec.Attr and faDirectory = faDirectory then
                begin
                  if (SearchRec.name <> '.') and (SearchRec.name <> '..') then
                    GetOneDirFileInfo(Json, Path + SearchRec.Name + '\');
                end else
                begin
                  FileName := Path + SearchRec.Name;
                  try
                    if FileExists(FileName) then
                    begin 
                      json_sub := Pub.GetJson;  
                      json_sub.SO; //初始化 或 json.Init;    
                      json_sub.S['filename'] := SearchRec.name;
                      json_sub.S['RelativePath'] := GetDeliBack(FileName, FromPath);
                      json_sub.S['FileTime'] := FileGetFileTimeA(FileName);
                      json_sub.I['size'] := SearchRec.Size;
                      json.A['list'] := json_sub;
                    end;
                  except
                    //print(ExceptionParam)
                  end;//}
                end; 
                Status := FindNext(SearchRec);
              end;
            finally
              FindClose(SearchRec);
              SearchRec.Free;
            end;//*) 
          end;
          %>
          <%
          begin
            FromPath := 'D:\code\delphi\sign\發(fā)行文件'; //下載源目錄
            
            json := Pub.GetJson; //這樣創(chuàng)建json對象不需要自己釋放,系統自動管理
            json.SO; //初始化 或 json.Init;
            
            // 驗證是否登錄代碼
            {if not Request.IsLogin('Logined') then
            begin 
              json.S['retcode'] := '300';
              json.S['retmsg'] := '你還沒有登錄(no logined)!'; 
              print(json.AsJson(true));
              exit; 
            end;//} 
            
            json.S['retcode'] := '200';
            json.S['retmsg'] := '成功!';
            if Request.V('opr') = '1' then
            begin //獲取服務上指定目錄的文件信息
              GetOneDirFileInfo(Json, FromPath);
            end else
            if Request.V('opr') = '2' then
            begin //下載指定文件給定大小的塊 
              FromPath := PathWithSlash(FromPath);   
              RelativePath := Request.V('fn');
              FileName := FromPath + RelativePath;
              Fs := Pub.GetFS(FileName, fmShareDenyWrite, ErrStr);
              if trim(ErrStr) <> '' then 
              begin
                json.S['retcode'] := '300';
                json.S['retmsg'] := ErrStr;
                print(json.AsJson(true));  
                exit;
              end;
              Fs.Position := StrToInt(Request.V('pos'));
              Response.ContentStream := TMemoryStream.Create; //注意不能用 Pub.GetMs,這是因為Pub.GetMs創(chuàng)建的對象在動態(tài)腳本運行完就釋放了
              Response.ContentStream.CopyFrom(Fs, StrToInt(Request.V('size')));
              //返回流數據
              Response.ContentType := 'application/octet-stream';   
            end;
            print(json.AsJson(true));
          end;
          %>

          客戶端代碼

          客戶端收到塊后,進行合并。全部塊下載完成后,還要把新下載的文件的文件修改為與服務器上的文件相同。以下是客戶端實現的主代碼:

          procedure TMainForm.UpgradeBlock_Run(var ThreadRetInfo: TThreadRetInfo);
          const
            BlockSize = 1024*1024; //1M
          var
            HTML, ToPath, RelativePath, FN, Tmp, TmpFileName, FailFiles, SuccFiles, Newfn, TmpToPath: string;
            Json, TmpJson: TminiJson;
            lp, I, Number, HadUpSize, AllSize, AllBlockCount, MySize, MyNumber: Int64;
            Flag: boolean;
            SL, SLDate, SLSize, SLTmp: TStringlist;
            MS: TMemoryStream;
            Fs: TFileStream;
            procedure HintMsg(Msg: string);
            begin
              FMyMsg := Msg; // '正在獲取文件列表。。。';
              ThreadRetInfo.Self.Synchronize(ThreadRetInfo.Self, MyUpdateface); //為什么不直接用匿名,因為laz不支持
            end;
          begin
            ToPath := 'D:\superhtml'; //如果是當前程序更新  ExtractFilePath(ParamStr(0))
           
            ThreadRetInfo.Ok := false;
           
            HintMsg('正在獲取文件列表。。。');
            if not HttpPost('/接口/同步文件到客戶端.html?opr=1',
                '', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML) then exit;
            if Pos('{', ThreadRetInfo.HTML) <> 1 then
            begin
              ThreadRetInfo.ErrStr :='請先檢查腳本源碼是否配置正確!';
              exit;
            end;
            ToPath := Pub.PathWithSlash(ToPath);
           
            Json := TminiJson.Create;
            SL := TStringlist.Create;
            SLDate := TStringlist.Create;
            SLSize := TStringlist.Create;
            SLTmp := TStringlist.Create;
            try
              Json.LoadFromString(ThreadRetInfo.HTML);
              if json.S['retcode'] = '200' then
              begin
                TmpJson := json.A['list'];
                for lp := 0 to TmpJson.length - 1 do
                begin
                  HintMsg(lp.ToString + '/' + TmpJson.length.ToString + '正在檢查文件:' + RelativePath);
                  RelativePath := TmpJson[lp].S['RelativePath'];
                  if trim(RelativePath) = '' then Continue;
                  Flag := FileExists(ToPath + RelativePath);
                  if Flag then
                  begin
                    if (PubFile.FileGetFileTimeA(ToPath + RelativePath) = TmpJson[lp].S['FileTime']) and
                       (PubFile.FileGetFileSize(ToPath + RelativePath) = TmpJson[lp].I['Size']) then
                    else
                      Flag := false;
                  end;
                  if not Flag then //此文件需要更新
                  begin
                    SL.Add(RelativePath);
                    SLDate.Add(TmpJson[lp].S['FileTime']);
                    SLSize.Add(TmpJson[lp].S['Size']);
                  end;
                end;
           
                //開始下載
                FailFiles := '';
                SuccFiles := '';
                HintMsg('需要更新的文件共有' + IntToStr(SL.Count) + '個。。。');
                for lp := 0 to SL.Count - 1 do
                begin
                  RelativePath := SL[lp];
                  if RelativePath[1] = '\' then RelativePath := Copy(RelativePath, 2, MaxInt);
                  FN := ToPath + RelativePath;
           
                  //先計算要分幾個包,以處理進度
                  Number := 0;
                  HadUpSize := 0;
                  AllSize := StrToInt64(SLSize[lp]);
                  AllBlockCount := 0;
                  while true do
                  begin
                    AllBlockCount := AllBlockCount + 1;
                    if AllSize - HadUpSize >= BlockSize then
                       MySize := BlockSize
                    else
                       MySize := AllSize - HadUpSize;
                    HadUpSize := HadUpSize + MySize;
                    if HadUpSize >= AllSize then
                      break;
                  end;
           
                  //開始分塊下載
                  Number := 0;
                  HadUpSize := 0;
                  //AllSize := Fs.Size;
                  //TmpToPath := PubFile.FileGetTemporaryPath;
                  Newfn := '@_' + PubPWD.GetMd5(SLDate[lp] + SLSize[lp]) + ExtractFileName(FN);  //Pub.GetClientUniqueCode;
           
                  if FileExists(ToPath + Newfn) and (FileExists(FN)) then
                  begin
                    SLTmp.LoadFromFile(ToPath + Newfn);
                    MyNumber := StrToInt64(trim(SLTmp.Text));
                    Fs := TFileStream.Create(FN, fmOpenWrite);
                  end else
                  begin
                    MyNumber := 0;
                    Fs := TFileStream.Create(FN, fmCreate);
                  end;
                  try
                    while true do
                    begin
                      HintMsg('正在下載文件[' + Pub.GetDeliBack(RelativePath, '@@') + ']第[' + IntToStr(Number + 1) + '/' + IntToStr(AllBlockCount) + ']個包。。。');
           
                      if AllSize - HadUpSize >= BlockSize then
                         MySize := BlockSize
                      else
                         MySize := AllSize - HadUpSize;
                      Number := Number + 1;
                      if (MyNumber = 0) or (Number >= MyNumber) or (HadUpSize + MySize >= AllSize) then
                      begin
                        for I := 1 to 2 do //意外出錯重試一次
                        begin
                          if not HttpPost('/接口/同步文件到客戶端.html?opr=2fn=' + UrlEncode(RelativePath) +
                            'pos=' + UrlEncode(IntToStr(HadUpSize)) + 'size=' + UrlEncode(IntToStr(MySize)),
                            '', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML, MS) then
                          begin
                            if I = 2 then
                            begin
                              ThreadRetInfo.ErrStr := Json.S['retmsg'];
                              exit;
                            end else
                              Continue;
                          end;
                          if Pos('{', ThreadRetInfo.HTML) < 1 then
                          begin
                            if I = 2 then
                            begin
                              ThreadRetInfo.ErrStr := Json.S['retmsg'];
                              exit;
                            end else
                              Continue;
                          end;
           
                          Json.LoadFromString(ThreadRetInfo.HTML);
                          if json.S['retcode'] <> '200' then
                          begin
                            if I = 2 then
                            begin
                              ThreadRetInfo.ErrStr := Json.S['retmsg'];
                              exit;
                            end else
                              Continue;
                          end;
                          break;
                        end;
           
                        if MS = nil then
                        begin
                          ThreadRetInfo.ErrStr := '沒能下載到文件[' + RelativePath + ']!' + json.S['retmsg'];
                          exit;
                        end else
                        begin
                          Fs.Position := HadUpSize;
                          MS.Position := 0;
                          Fs.CopyFrom(MS, MS.Size);
                          MS.Free;
                          MS := nil;
                          SLTmp.Text := Number.ToString;
                          try
                            SLTmp.SaveToFile(ToPath + Newfn);
                          except
                          end;
                        end;
                      end;
                      HadUpSize := HadUpSize + MySize;
           
                      if HadUpSize >= AllSize then
                      begin //全部下載完成
                        Fs.Free;
                        Fs := nil;
                        Sleep(10);
                        PubFile.FileChangeFileDate(Fn, SLDate[lp]);
                        DeleteFile(ToPath + Newfn);
                        SuccFiles := SuccFiles + #13#10 + RelativePath;
                        break;
                      end;
                    end;
                  finally
                    if Fs <> nil then
                      Fs.Free;
                  end;
                end;
                ThreadRetInfo.HTML := '';
                if trim(SuccFiles) <> '' then
                  ThreadRetInfo.HTML := '本次更新了以下文件:'#13#10 + SuccFiles;
                //if trim(FailFiles) <> '' then
                  //ThreadRetInfo.HTML := trim(ThreadRetInfo.HTML + #13#10'以下文件更新失敗:'#13#10 + FailFiles);
              end;
            finally
              SLTmp.Free;
              SLSize.Free;
              SL.Free;
              Json.Free;
              SLDate.Free;
            end;
            ThreadRetInfo.Ok := true;
          end;

          以下是Demo運行界面:


          主站蜘蛛池模板: 日本无卡码一区二区三区| 波多野结衣AV无码久久一区| 中文字幕一区二区视频| 亚洲乱码一区二区三区国产精品 | 国内精自品线一区91| 国精产品一区一区三区免费视频 | 一区二区乱子伦在线播放| 国产精品综合一区二区| 无码夜色一区二区三区| 人妻无码第一区二区三区| 激情一区二区三区| 国内国外日产一区二区| 人妻少妇精品一区二区三区| 国产肥熟女视频一区二区三区| 天天综合色一区二区三区| 久久精品成人一区二区三区| 无码播放一区二区三区| 91久久精品一区二区| 91久久精品一区二区| 国产成人综合一区精品| 国产精品香蕉在线一区| 久久毛片一区二区| 国产激情一区二区三区小说 | 中文日韩字幕一区在线观看| 亚洲国产精品成人一区| 国产另类ts人妖一区二区三区| 日韩AV片无码一区二区不卡| 色一情一乱一伦一区二区三区日本| 好爽毛片一区二区三区四无码三飞 | 中文字幕一区二区三区有限公司| 日本一区二区三区在线视频 | 国产亚洲无线码一区二区| 三上悠亚一区二区观看| 福利一区二区三区视频在线观看| 色偷偷久久一区二区三区| 精品人妻少妇一区二区三区不卡 | 国产一区视频在线| 亚洲综合一区二区精品导航| 精品人妻系列无码一区二区三区| 亚洲欧美日韩国产精品一区| 国产av一区二区精品久久凹凸|