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
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 個不同的團隊。每個團隊都把自己喜歡的時髦技術帶到了項目中,用時髦的包裝方式包裹舊代碼,但真正為引擎本身添加的價值卻很少。
起初,我認真地嘗試理解每一處小細節,但在碰了一鼻子灰之后,我放棄了。我還是老老實實完成任務,改 bug 吧。不能說我的工作效率很高,只能說很勉強,不至于被解雇。但后來我的老板問我:“你想把部分匯編代碼改成 GLSG 嗎?”雖然我并不了解GLSL是什么,但我覺得總不至于還不如 C++ 吧,于是我答應了。結果確實不至于還不如 C++。
后來,大部分的時間里我仍在用 C++ 寫代碼,但每當有人問我:“你想不想嘗試一些非 C++ 的工作?”我就會說:“當然!”然后我就會去做。我寫過 C89、MASM32、C#、PHP、Delphi、ActionScript、JavaScript、Erlang、Python、Haskell、D、Rust,以及令人聞風喪膽的腳本語言 InstallShield。我甚至還寫過 VisualBasic、bash,以及幾種不能公開談論的專有語言。我甚至編寫過自己的語言,我寫了一個簡單的 Lisp 風格解釋器,幫助游戲設計師自動加載資源,然后去度假了。回來后發現他們用這個解釋器編寫了整個游戲場景,所以在接下來的一段時間里我們必須支持這個解釋器。
在過去的 17 年里,我一直在努力擺脫 C++,但每次嘗試過新技術后,總是會回到 C++。盡管如此,我仍然認為使用 C++ 編寫程序是一個壞習慣。這門語言并不安全,效率也達不到人們的期望,而且程序員需要在與軟件制作毫無關系的工作上浪費大量精力。你知道在 MSVC 中 uint16_t(50000) + uint16_t(50000) == -1794967296 嗎?你知道為什么嗎?你的看法與我不謀而合。
我認為,作為一名長期使用 C++ 的程序員,我有責任勸誡年輕一代程序員不要將 C++ 作為自己的專攻語言,就像有不良嗜好的人有責任勸誡不要重蹈覆轍。
那么,為什么我無法放棄 C++ 呢?問題出在哪里?問題在于,所有的編程語言,尤其是那些所謂的“C++ 殺手”,真正帶來的優勢都未能超越 C++。這些新語言大多會從一定程度上約束程序員。這本身沒什么問題,畢竟當年晶體管密度每 18 個月翻一番,而程序員的數量每 5 年才翻一番,糟糕的程序員寫不出優秀的代碼也并不是什么大問題。
如今,我們生活在 21 世紀。經驗豐富的程序員數量超過了歷史任何時期,而且我們更需要高效的軟件。
上個世紀,編寫軟件很簡單。你有一個想法,然后將其包裝成 UI,再作為桌面系統軟件產品出售就可以了。運行速度太慢?沒人在乎!18 個月內,臺式機的速度就會翻倍。重要的是進入市場,打開銷路,而且還沒有 bug。當然,如果編譯器能防止程序員犯錯就更好了,因為 bug 不但不會產生收益,而且你還要付錢給程序員改 bug。
而如今情況大不相同了。你有一個想法,然后將其包裝到 Docke 容器中,并在云中運行。如今想獲取收入,你的軟件就必須為用戶解決問題。即使一款產品只做一件事,但只要做的正確,就能獲得報酬。你不必為了銷售新版本的產品而不斷擴充功能。相反,如果你的代碼發揮不了真正的作用,買單的就是你自己。云賬單就能真實地反映出你的程序是否真的起作用。
因此,在新的環境下,你需要的功能更少,但所有的功能都需要更出色的性能。
在這個前提下你就會發現,所有的“C++ 殺手”,甚至是我由衷喜歡和尊敬的 Rust、Julia 和 D,也沒有解決 21 世紀的問題。它們仍然停留在上個世紀。雖然這些語言可以幫助你編寫更多功能,而且 bug 更少,但當你需要從租用的硬件中壓榨出最后一點 FLOPS 時,它們就沒有太大用處了。
因此,這些語言只不過是比 C++ 更具競爭優勢,或者說彼此之間可以競爭。但大多數編程語言,例如 Rust、Julia 和 Cland,甚至共享同一個后端。所有賽車手都坐在同一輛車上,何談誰能贏得比賽呢?
那么,究竟哪些技術比 C++ 或者傳統的預先編譯器更有優勢呢?
在討論 Spiral 之前,讓我先來考考你。你覺得以下哪個版本的代碼運行速度更快?版本1:標準的 C++ 正弦函數;版本2:由4個多項式模型組成的正弦函數?
下一個問題。以下哪個版本的代碼運行速度更快?版本1:使用短路邏輯運算;版本2:將邏輯表達式轉換為算術表達式?
第三個問題,以下哪個版本的三元組排序更快?版本1:帶有分支的交換排序;版本2:無分支的索引排序?
如果你果斷地回答了以上所有問題,甚至沒有思考或上網搜索,那么只能說你被自己的直覺騙了。你沒有發現陷阱嗎?在沒有上下文的情況下,這些問題都沒有確定的答案。
因此,我們喜愛的微優化都有可能將代碼的運行提升3倍,也有可能導致速度下降90%。這完全取決于上下文。如果編譯器能為我們選擇最佳替代方案,那該多好,例如,當我們切換構建目標時,索引排序會神奇地變成交換排序。但可惜編譯器做不到。
此處就不得不提到 Spiral 了。該語言是卡內基梅隆大學和蘇黎世聯邦理工學院的聯合項目。簡單來說,信號處理專家厭倦了每出現一種新硬件就需要手動重寫他們喜歡的算法,因此編寫了一個可自動完成這項工作的程序。該程序接受算法的高級描述和硬件架構的詳細描述,并優化代碼,直到在指定的硬件上實現最高效的算法。
與 Fortran 等語言不同,Spiral 真正解決了數學意義上的優化問題。它將運行時定義為目標函數,并在受硬件架構限制的可變因素空間內尋找全局最優實現。編譯器永遠無法真正實現這種優化。
編譯器不會尋找真正的最優解。它只不過是根據程序員所教的啟發式規則來優化代碼。實質上,編譯器并不是一個尋找最優解的機器,更像一個匯編程序員。一個好的編譯器就像一個好的匯編程序員,僅此而已。
Spiral是一個研究項目,范圍和預算都很有限。但最后展現的結果卻很驚人。在快速傅里葉變換中,他們的解決方案明顯優于 MKL 和 FFTW 的實現,他們的代碼速度約快了 2 倍,即使在英特爾上也是如此。
為了突顯如此宏大的成就,需要說明一下,MKL 是英特爾自己的數學內核庫(Math Kernel Library,簡稱MKL),因此他們非常了解如何充分利用自家的硬件。而WWTF(Fastest Fourier Transform in the West,西部最快傅里葉變換)是一種高度專業化的庫,由最了解該算法的人編寫。二者都是各自領域的冠軍,而 Spiral 的速度能夠達到二者兩倍,這實在太不可思議了。
等到 Spiral 使用的優化技術最終成熟并商業化,不僅僅是 C++,包括 Rust、Julia,甚至 Fortran 都將面臨前所未有的競爭壓力。既然能使用高級算法描述語言編寫2倍速的代碼,誰還會使用C++呢?
相信你很熟悉這門優秀的編程語言。幾十年來,大多數程序員來說最熟悉的語言一直是 C。在 TIOBE 指數中,C語言一直名列第一,其他類似 C 的語言占據了前十名。然而,兩年前,一件前所未聞的事情發生了,C 語言第一名的地位不保。
取而代之的語言是Python。90年代,沒有人看好Python,因為它不過是眾多腳本語言中的一個。
有人會說:“Python很慢”,但這種說法很荒謬,就像說手風琴或平底鍋很慢一樣,語言本身沒有快慢之分。就像手風琴的速度取決于演奏者一樣,語言的快慢取決于編譯器的速度。
可能還會有人說:“Python不是一種編譯語言”,這個說法也不嚴謹。Python 編譯器有很多,其中一個最被看好的編譯器也算是Python腳本。我來解釋一下。
我曾經有一個項目,是一個3D打印模擬,最初是用Python編寫的,后來“為了性能”改用C++重寫,后來又移植到 GPU 上,當然這些都是在我進入項目之前發生的事兒。后來,我花了幾個月的時間將構建遷移到 Linux,優化了 Tesla M60 的 GPU 代碼,因為這是當時AWS中最便宜的GPU。之后,我又在 C++/CU 代碼中驗證了所有變更,以便與原來的Python代碼相結合。除了設計幾何算法之外,所有的工作都是由我完成的。
在一切正常運行后,Bremen 的一名兼職學生打電話給我問道:“聽說你很擅長使用多種技術,能幫我在 GPU 上運行一個算法嗎?”“當然可以!”我給他講了CUDA、CMake、Linux 構建、測試以及優化等等,大約花了一個小時。他很有禮貌地聽完了我的介紹,最后說:“很有意思,但我想問一個非常具體的問題。我有一個函數,我在函數的定義前面加了@cuda.jit,Python就無法編譯內核了,還提示了一些關于數組的錯誤。你知道這里面有什么問題嗎?”
我不知道。后來,他花了一天時間自己搞清楚了。原因是,Numba 無法處理原生的Python列表,只接受 NumPy 數組中的數據。他找到了問題所在,并在 GPU 上運行了算法。使用的是Python。他沒有遇到我花費了幾個月心思解決的任何“問題”。想在 Linux 上運行代碼?沒問題,直接在Linux運行即可。想針對目標平臺優化代碼?也不是問題。Numba 會替你優化在平臺上運行的代碼,因為它不會預先編譯代碼,而是在部署時按需編譯。
很厲害,對不對?然而,對我來說并不是。我花費了幾個月的時間,使用C++解決 Numba 中不會出現的問題,而那位Bremen的兼職學生完成相同的工作只花費了幾天的時間。如果不是因為那是他第一次使用Numba,可能只需要幾個小時。說到底,Numba是什么?它是一種什么樣的魔法?
沒有魔法。Python 的裝飾器將每一段代碼都轉換成了抽象語法樹,因此你可以隨意處理。Numba是一個 Python 庫,可使用任何后端、為任何支持的平臺編譯抽象語法樹。如果你想將Python 代碼編譯成以高度并行的方式在 CPU 核心上運行,只需告訴 Numba 編譯即可。如果你希望在GPU上運行代碼,同樣只需提出請求即可。
Numba是一個Python編譯器,可以淘汰C++。然而,從理論上來說,Numba并沒有超越C++,因為二者使用的是同一個后端。Numba的GPU編程使用了CUDA,CPU編程使用了LLVM。實際上,由于它不需要針對每種新的架構提前重建,因此能夠更好地適應每種新硬件及其潛在的優化。
當然,如果Numba能像Spiral那樣具有顯著的性能優勢會更好。但Spiral更像是一個研究項目,最終可能會淘汰C++,但前提是足夠幸運才行。Numba與Python的結合可以立即判C++死刑。如果可以使用Python編程,而且能擁有C++的性能,誰還會寫C++代碼呢?
下面,我們再玩一個游戲。我給你三段代碼,你猜猜哪一段(也有可能是多段)是用匯編語言編寫的。
第一段代碼:
第二段代碼:
第三段代碼:
如果你猜到這三個例子都是匯編,那么恭喜你!
第一個例子是用 MASM32 編寫的。這是一個帶有“if”和“while”的宏匯編器,用于編寫原生Windows 應用程序。注意,不是以前有人這么寫,而是至今仍在采用這種寫法。微軟一直在積極維護Windows 與 Win32 API 的向后兼容性,因此所有以前編寫的 MASM32 程序都可以在現代 PC 上正常運行。
很諷刺的是,C 語言的發明是為了降低將 UNIX 從PDP-7 轉換成 PDP-11 的難度。C語言的設計初衷就是成為一種便攜式匯編語言,能夠在 70 年代硬件架構的寒武紀爆發中生存下來。但在 21 世紀,硬件架構的演變如此緩慢,我在 20 年前用 MASM32 寫的程序如今仍然能完美運行,但我不敢確定去年用 CMake 3.21 構建的 C++ 應用程序今時今日能否用 CMake 3.25 構建。
第二段代碼是 WebAssembly,這門技術甚至不是一個宏匯編器,沒有“if”和“while”,更像是人類可讀的瀏覽器機器碼。從概念上來說,可以是任何瀏覽器。
WebAssembly代碼根本不依賴于硬件架構。它提供的機器是抽象的、虛擬的、通用的,隨你怎么稱呼它。如果你能閱讀這段文字,說明你的物理機器上已經有一個能運行WebAssembly的硬件架構了。
最有趣的是第三段代碼。這是 ForwardCom:一款由著名的 C++ 和匯編優化手冊作者 Agner Fog 提出的匯編器。與 Web Assembly 一樣,這不僅僅是一個匯編器,而且旨在實現向后以及向前兼容性的通用指令集。因此得名。ForwardCom 的全稱是an open forward-compatible instruction set architecture(一款開放式向前兼容指令集架構)。換句話說,它不僅是一個匯編器的提議,而且也是一份和平條約提議。
我們知道最常見的計算機架構系列 x64、ARM 和 RISC-V 都有不同的指令集。但沒有人知道為什么要保持這種狀態。所有現代處理器,除了最簡單的一些之外,運行的都不是你提供的代碼,而是將你的輸入轉換為微碼。因此,不僅M1芯片提供英特爾的向后兼容層,每個處理器本質上都為自己的早期版本提供了向后兼容層。
那么,為什么架構設計者未能就類似的向前兼容層達成統一意見呢?無外乎各個公司之間的競爭野心。但如果處理器制造商最終決定建立一個共同的指令集,而不是為每個競爭對手實現一個新的兼容層,ForwardCom就能夠讓匯編重回主流。這種向前兼容層可以治愈每個匯編程序員最大的心理創傷:“如今我為這個特定的架構編寫一次性代碼,不出一年就會被淘汰?”
有了向前兼容層,這些代碼就永遠不會過時。這就是關鍵所在。
此外,匯編編程還受到了另一種錯誤觀念的限制,人們普遍認為匯編代碼太難寫,因此不實用。Fog 的提議也解決了這個問題。如果人們認為寫匯編代碼太難,而寫 C 不難,那么我們就把匯編變成C語言。這不是問題。現代匯編語言沒有必要延續50年代祖宗的模樣。
上面你看到的三個匯編示例都不像“傳統”的匯編,而且也不應該還是老樣子。
ForwardCom是一種匯編,可用于編寫永遠不會過時的最佳代碼,并且不需要學習“傳統”的匯編。從現實的角度來看率,ForwardCom是未來的 C。不是 C++。
我們生活在一個后現代世界。與世長辭的不是技術,而是人。就像拉丁語從未真正消失一樣,COBOL、Algol 68 和 Ada 也一樣,C++ 注定要永遠介于生死參半的狀態。C++ 永遠不會真正消失,它只會被更新更強大的新技術所取代。
嚴格來說,不是“將來會被取代”,而是“正在被取代”。我的職業生涯源自 C++,而如今在使用 Python 寫代碼。我編寫方程式,SymPy 幫我求解,然后將解決方案轉換為 C++。然后,我將這段代碼粘貼到 C++ 庫中,甚至都無需調整格式,因為 clang-tidy 會自動完成。靜態分析器會檢查命名空間是否混亂,動態分析器會檢查內存泄漏。CI/CD 負責跨平臺編譯。性能分析器讓我了解代碼實際的運行情況,反匯編器可以解釋為什么。
如果我用 C++ 之外的技術代替 C++,那么 80% 的工作不會有變化。對于我的大多數工作來說,C++ 根本無關緊要。這是否意味著,對于我來說,C++ 已經死了 80%?
icpick是一款非常實用的屏幕截圖工具,軟件為用戶提供了多種截取屏幕的方式,如全屏、活動窗口、窗口空間、滾動窗口、矩形區域、固定區域、任意形狀等等,用戶們可以根據自己的需求進行選擇使用,滿足了不同用戶的使用需求,同時還提供了眾多的實用工具供用戶使用,比如取色器、放大鏡、標尺、調色板、坐標軸、量角器、白板等等,在截圖之后就可以直接使用這些實用工具制作你需要的素材圖片了,輕松就可以幫你制作出最優質的圖片,并且還可以對圖片進行添加各種的效果,因此軟件內置了一個強大的編輯器,用戶可以進行添加文本、陰影、水印等一系列操作,非常的方便實用。總之使用picpick截圖軟件可以輕松幫你抓取到全屏幕或是局部的畫面,可以隨意進行修改,而且操作也很簡單,是一款非常優秀的截圖工具。picpick下載安裝-picpick截圖軟件下載 v5.1.9中文免費版 - 多多軟件站
1、截獲任何截圖
截獲屏幕截圖、活動窗口的截圖、桌面滾動窗口的截圖和任何特定區域的截圖等等。
2、編輯你的圖片
注釋并標記您的圖片:您可以使用內置圖片編輯器中的文本、箭頭、形狀和更多功能,并且該編輯器還帶有最新的Ribbon風格菜單。
3、增強效果
為你的圖片添加各種效果:陰影、框架、水印、馬賽克、運動模糊和亮度控制等等。
4、分享到任何地方
通過網絡、郵件、ftp、Dropbox、Google Drive、SkyDrive、Box、Evernote、Facebook、Twitter和其它更多方式來保存、分享或發送你的照片。
5、平面設計附件
各種平面設計附件包括顏色選擇器、顏色調色板、像素標尺、量角器、瞄準線、放大鏡和白板。
6、自定義設置
軟件帶有各種高級的設置,您可以自定義快捷鍵、文件命名、圖片質量和許多其它的功能。
1、點擊左上角的文件,然后在彈出窗口中選擇程序選項;
2、在打開的窗口左側有一項是快捷鍵,點擊打開快捷鍵窗口;
3、在快捷鍵窗口中根據自己的喜好,分別設置需要的快捷鍵。設置完成后記得點擊底部的確定按鈕進行保存。
1、首先打開軟件,然后點擊滾動窗口;
2、這時候,可以看到窗口的外圍有個綠色的框框,不要亂動鼠標;
3、用鼠標點擊右側的滾動條,然后不要動鼠標,鍵盤和電腦;
4、picpick自己截屏;
5、當滾動條滾動到最下方的時候,截屏結束,picpick自動保存圖片,打開圖片編輯界面;
6、可以看一下,由于電腦自運行比較慢的問題,有的地方截取效果不好,這需要自己修改。
1、友好的用戶界面,提供Windows 7的 Ribbon 樣式;
2、擁有基本的編輯繪圖、形狀、指示箭頭、線條、文本等功能;
3、同時還支持模糊,銳化,色調,對比度,亮度,色彩平衡,像素化,旋轉,翻轉,邊框等效果;
4、支持共享截圖至 FTP 、Web 、E-Mail、Facebook、Twitter 等社交網絡;
5、屏幕截圖:支持全屏、活動窗口、滾動窗口 、窗口控制、區域、固定區域、手繪、重復捕捉;
6、Ribbon界面圖像編輯器:箭頭、線條等繪圖工具。模糊、銳化、像素化、旋轉、翻轉,框架等特效;
7、拾色器和調色板:支持RGB、HTML、C++、Delphi等代碼類型,Photoshop風格轉換,保存顏色;
8、屏幕放大鏡、量角器、屏幕坐標計算功能,為你的演示文稿把屏幕當作白板自由繪畫。
1、打開PicPick,并使用PicPick截圖多張圖片。此時圖片處于未保存狀態;
2、然后我們點擊PicPick左上角的文件;
3、接下來我們點擊如下圖所示的保存(或者另存為);
4、然后我們看到并點擊“全部保存”;
5、在彈出的保存窗口中,點擊三個點的圖標,找到你的圖片要保存的位置;
6、之后點擊確定,即可完成批量保存。
載大文件時,斷點續傳是很有必要的,特別是網速度慢且不穩定的情況下,很難保證不出意外,一旦意外中斷,又要從頭下載,會很讓人抓狂。斷點續傳就能很好解決意外中斷情況,再次下載時不需要從頭下載,從上次中斷處繼續下載即可,這樣下載幾G或十幾G大小的一個文件都沒問題。本文介紹利用miniframe開源Web框架分別在lazarus、delphi下實現文件HTTP下載斷點續傳的功能。
本文Demo還實現了批量下載文件,同步服務器上的文件到客戶端的功能。文件斷點續傳原理:分塊下載,下載后客戶端逐一合并,同時保存已下載的位置,當意外中斷再次下載時從保存的位置開始下載即可。這其中還要保證,中斷后再次下載時服務器上相應的文件如果更新了,還得重新下載,不然下載到的文件是錯了。說明:以下代碼lazarus或delphi環境下都能使用。全部源碼及Demo請到miniframe開源web框架下載: https://www.wyeditor.com/miniframe/或https://github.com/dajingshan/miniframe。
文件下載斷點續傳服務器端很簡單,只要提供客戶端要求下載的開始位置和指定大小的塊即可。
以下是服務器獲取文件信息和下載一個文件一塊的代碼:
<%@//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\發行文件'; //下載源目錄
json := Pub.GetJson; //這樣創建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創建的對象在動態腳本運行完就釋放了
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運行界面:
*請認真填寫需求信息,我們會在24小時內與您取得聯系。