Sinilink XY-WF5V 灌 Home Dynamic v2 IoT 控制器
最近因為一些原因拿到了兩塊 Sinilink 的 XY-WF5V WiFi 繼電器模組。由於它的內置 firmware WiFi 有鎖,加上它的 App 看上去就覺得很爛,所以我決寫把它的 firmware 刷掉重新寫一個 HDSv2 的 firmware 進去,讓我的雲端 IoT 控制器能夠控制到它。 首先,怎樣在非開發板上灌程式? 這個時候你就需要 USB 編程器了(大陸叫下載器,通常我叫它 ISP),你可以用不同的方式來灌程式進去,可是我這裡用的是最便宜的 ESP01 用編程器,樣子大約長這樣 然後我們就可以直接把線從那個 2 x 8 2.54 母頭裡拉出來,直接焊接在 ESP8266 上面,線路如下 接線圖 完成之後大約長這樣 逆向工程 之後就是把板子的線路重新畫出來。由於這是一塊兩層板,所以沒甚麼大問題。之後再把找到的 GPIO 跟據需要重新寫進 HDSv2 On/Off 的 example sketch 裡面, 可是,我們怎知道應該使用哪一塊開發板的配置呢? 這個我們可以選擇使用 Wemos D1 R1 作為基礎,重新把 ESP8266 上對應的針腳在 Wemos D1 上找到對應的 Digital Pin 號碼即可。完成之後 Arduino sketch 裡面的開關 pin 被改成如下: int signalOutputPin = D14; //The pin to activate the mosfet of relay int buttonPin = D6; int refPin = D8; 如果找不到 Pull up / down 電阻怎辦? 另外,如果你在板子上找到沒有 pull up / pull down 的按鈕,很有可能它是使用軟體方式做的 internal pull up / down 電阻。在更改 HDSv2 的 example 時只要把 INPUT pin 的設定由 INPUT 改成 INPUT_PULLUP 即可,例子如下: pinMode(buttonPin, INPUT_PULLUP); 相關可以使用程式 pull up / down 的針腳可以參考下表(請對應 Generic 8266 的 GPIO 號碼,不要使用最左欄的 pin 號碼) 完成圖 完成之後在 ArozOS 內重新掃描一下 IoT 裝置,應該就會有新的 Sinilink WiFi Relay 在列表中出現了。
用了三年時間寫網頁桌面作業系統的故事
這是一個想找地方儲存他的音樂、影片跟圖片的窮學生與他的 Raspberry Pi 的故事。 如果你想直接下載來玩,這裡是傳送門:https://github.com/tobychui/arozos 開始時候的樣子 由 2016 年開始,我就一直在寫這套自用的系統。這系統主要是用來存放音樂、影片檔案等等的媒體文件,這樣我就可以很方便的在課堂跟課堂之間的休息時間聽到自己的音樂或是看一套短劇。這是這套系統於 2016 年的時候的樣子 這個版本是使用 PHP5 開發,後來升級到了 PHP 7 + Apache 伺服器。雖然同一台主機(當時我還是在用我的主機來當伺服器)上面有安裝到資料庫,但是它本身是不需要連接到 MySQL 資料庫的。這系統基本上是使用膠水語言黏起來的,運作原理就是後台的一個 VB.net 程式定時把硬碟上的音樂檔案掃描,並產生一個音樂列表讓 php 去 render 一個網頁界面。當有需要的時候再從硬碟裡搬出來到網頁伺服器的資料夾再進行串流。 你現在依然能夠在我的 Github 找到最原始版本的 ArOZ Online 系統(後來才改名為 ArozOS 作業系統),但是拜托 不!要!用!它!https://github.com/tobychui/ArOZ-Omega-Online 由 PHP 到 Go:開發難度與效能的平衡 這個非常簡單的 PHP 版本使用一兩年之後,因為我開始在上面放越來越多的檔案,單一頁面已經無法應付所有檔案的列表,所以開始加入檔案管理器、模組化的播放器到最後的網頁桌面 不過兩年後,PHP 的系統還是無法應付很多大型檔案(特別是現在的檔案都越長越大,由以前幾 MB 的 mp3 到現在隨時到 50 - 60MB 的 flac 檔案),加上桌面系統做得越來越像本地端的桌面,而需要大量而且高反應速度的資料回傳,因此 PHP 已經沒辦法再成為這系統的主力語言,而需要轉移到另一種語言繼續開發。有興趣了解更多關於這部分的技術問題可以看這篇: http://imuslab.com/wordpress/2020/03/29/%E7%B7%A8%E7%A8%8B%E8%AA%9E%E8%A8%80%E7%9A%84%E6%A5%B5%E9%99%90%EF%BC%9Aphp/ 最後經過一點研究之後,我決定轉移到 Go 語言,並把 ArOZ Online Beta (AOB)正名為 ArozOS 。 3 年後的 ArozOS 系統 經過 3 年的開發,它終於成為了一個類本地作業系統的網頁系統。首先就讓我們由桌面開始介紹吧! 網頁桌面 ArozOS 的桌面基本上就是 Ubuntu 20.04 混 Windows 的風格。你可以看到類似 Ubuntu GNOME 的通知欄 也能夠在右上角看到電源開關(可以透過啟動參數關閉這功能),還有一些方便的捷徑 就像 Windows 一樣,如果你要建立一個新檔案也只需要右鍵即可。你也可以透過右鍵選單來更改桌面背景 開始功能表也是如此,看著有沒有一點熟悉的感覺? 在結束桌面的介紹之前最後一定要說的:桌面版的重新命名界面! 看到沒有彈出視窗的感覺是不是非常有桌面的感覺呢?是不是有那種回到家對著主機螢幕的那種安心感(不對 檔案管理員 在使用原本的系統一段時間之後,我開始需要上載其他東西到我的系統裡。而最通用的界面去查看不同類型的檔案當然是?沒錯,就是檔案管理員。所以我就自己寫了一個進去我的系統啦 我的檔案管理員有兩種模式,為別是「列表」模式跟「縮圖」模式 在縮圖模式下,檔案管理只會嘗試從檔案中提取預覽圖片。這功能適用於音樂、影片,照片甚至 3D 模型檔案 檔案管理員最初並沒有分類和排序的功能。可是由於上載到系統的檔案數目日益增加,所以去到一個地步我還是需要把排序功能加進去才有辦法找到我的檔案( 之後還加入了支援 regex 的檔案尋找功能 噢,如果你是喜歡 dark theme 的使用者,檔案管理員也提供 dark theme 模式,不過這最初只是方便我在晚上找電影來看而已 檔案管理功能 檔案管理員提供很多不同的檔案功能。你可以在右鍵選單中找到所有的功能。 就如作業系統一樣,你也可以選擇以某一個 WebApp 開啟指定的檔案 為了讓自己在收拾和清理資料夾的時候比較方便,所以系統也加入了支援 drag & drop (拖放)的功能。要移動檔案的時候只要很簡單的把檔案由一個視窗拖放到另一個視窗即可。 由於 Golang 本身提供 async 模式以處理要求(request),因此這樣的功能比起 PHP 更容易開發。這個即時的進度顯示是使用 websocket 技術,讓後端一邊複制檔案一邊回傳進度。 檔案分享 間中我也會有需要透過 ArozOS 分享檔案的功能。因此我就順便把這個功能加進去了。 (這裡只是一個例子,乖孩子不要把 localhost 的網址分享給別人喔) 別人就可以透過 QR Code 或是分享的連接來下載到檔案 回到原點:音樂與影片播放 當然這些功能現在還存在吧!經過三年的開發,這些功能已經比之前的好用很多了。使用 PWA 技術,現在的音樂播放器大約長這樣 這個界面甚至可以在 Android 手機上顯示獨立的播放界面(如果你是工程師,這個叫 MediaSession API) 影片播放模組亦是如此。使用 HTML5 技術,它能夠串流影片和電影,不再需要好像以前的版本那樣先下載才能看了。 系統設定 基本設定 由於這套系統已經長這麼大,這麼複雜了,要管理這套系統亦會需要一個很複雜的設定界面。我嘗試把設定界面做得簡單易用,所以就設計了一個分類,讓不同的設定放在不同的子分類裡,然後下面再有不同功能設定的細分類。 首先,你可以在系統設定看到系統的基本資料與處理器、記憶體使用量。 WebApp (網頁應用程式)及 Subservice (子服務) 當然,作為一個網頁桌面系統,當然也需要一個安裝應用的界面吧?所以我就弄了一套應用系統出來了。以下是一堆我寫出來自用的應用程式 WebApp 都是一些簡單,只需要檔案存取的應用程式。這些 WebApp 使用 JavaScript 編寫,並使用 Otto VM 作沙盒隔離。而 Subservice 則是一些比較複雜,需要直接與 OS 連接的程式。 你可以輕易的透過設定界面安裝和移除 WebApp 可是 Subservice 只能設定啟用和禁用。畢竟有一些 subservice 是跟系統運作穩定性相關的,理論上應該是由裝置的…
在 Go 伺服器上加入 Gzip Middleware 中繼器
話說最近因為有使用者提出想把 ArozOS 放到 Cloud VM 上面跑,所以想盡量壓縮 bandwidth 使用量,經過一大堆討論之後的結論就是在伺服器主要的檔案傳送之前先加個 gzip 壓縮器。 現時網上對於要應用 gzip middleware (我把它譯做「中繼器」,畢竟叫他做「中介軟體」好像怪怪的,跟中國大陸的叫法叫「中間件」又好像不太能表達意思) gzip middleware 原理 這個原理很簡單,就是在資料發出去之前先經過一層 gzip 以減少傳送的資料量,系統由原本的 要求 --> Mux / Router --> Handler --> 回傳 變成 要求 --> Mux / Router --> Handler --> gzip 壓縮 --> 回傳 因為要解釋起來很麻煩所以我就直接把 Go module 放這裡了: https://gist.github.com/tobychui/e31ca5be46e266cf52fc247dd38c9181 以下是一些使用例子: if *enable_gzip { http.HandleFunc("/media/", gzipmiddleware.CompressFunc(serverMedia)) }else{ http.HandleFunc("/media/", serverMedia) } fs := http.FileServer(http.Dir("./web")) if *enable_gzip { //Gzip enabled. Always serve with gzip if header exists http.Handle("/", gzipmiddleware.Compress(fs)) } else { //Normal file server without gzip http.Handle("/", mroutner(fs)) } 效果 以 ArozOS 的 desktop.system 界面來看,節省的流量大約如圖所示 上:有開啟 gzip 中繼器 下:沒有開啟 gzip 中繼器 嘛,基本上就這麼簡單而已。
ArozOS 1.110 穩定版發佈賀圖
就是因為與 Alanyeung 說到要不要在 ArozOS 1.0 首個 Stable release 的時候興祝一下,於是說著說著就說到了關於「不如畫一張賀圖出來」這樣的話題。 嘛,畫賀圖倒是沒問題啦,問題是誰畫? 結果因為只有 500 港幣預算問題,香港和台灣的繪師都敲不動,跑去了外國看看有沒有人願意接。想不到以同樣的金額還意外的多人畫,而且有的質素也十分不錯。 來自某 Freelance 網站截圖 結果我們就把繪圖要求外判出去,然後最近成品終於回來了。 這比我想像中的還要好啊啊啊(咳咳 就是這樣,我們就用這張立繪來做 1.110 Release 賀圖了! 設計中的 ArozOS 1.110 Release 賀圖
在 Raspberry Pi 上設定 MHS 3.5寸屏幕並啟用 Chromium Kiosk 模式
如果你想快速的做一個 Prototype,通常開發者都會直用 Web Browser 作為 GUI 的首選。但是當去到需要部署在硬體上面的時候,到底要怎樣把整個 Chrome 搬到去 ARM 開發板上面呢? 以下這個一個教學將會記錄我 DIY Rpi DAC 時架設 Chromium 的經歷 選擇屏幕 這個應該不用多說,當然就是最便宜的那個吧!就是這樣,我想也沒想就買了這個 MHS 3.5寸屏幕。 收到後接上 Rpi 4,並安裝好 Raspberry Pi OS LITE (沒有桌面版),之後就是重要的部分了 安裝 xserver 跟 Chromium https://die-antwort.eu/techblog/2017-12-setup-raspberry-pi-for-kiosk-mode/ 跟著這個教學,首先我們需要更新 apt-get sudo apt-get update sudo apt-get upgrade 之後安裝 Xserver 等顯示需要用到的程序庫 sudo apt-get install --no-install-recommends xserver-xorg x11-xserver-utils xinit openbox -y 最後就是安裝 Chromium sudo apt-get install --no-install-recommends chromium-browser -y 設定 Openbox 並讓它啟動 Chromiuum 。編輯 /etc/xdg/openbox/autostart sudo nano /etc/xdg/openbox/autostart 並在裡面填入以下的東西 # Disable any form of screen saver / screen blanking / power management xset s off xset s noblank xset -dpms # Allow quitting the X server with CTRL-ATL-Backspace setxkbmap -option terminate:ctrl_alt_bksp # Start Chromium in kiosk mode sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/chromium/'Local State' sed -i 's/"exited_cleanly":false/"exited_cleanly":true/; s/"exit_type":"[^"]\+"/"exit_type":"Normal"/' ~/.config/chromium/Default/Preferences chromium-browser --disable-infobars --window-size=320,480 --app-shell-host-window-size='320x480' --noerrdialogs --kiosk -app='http://YOUR_URL_HERE/' 這裡因為我的屏幕是 320 x 480的,所以所有 window size 的設定都是 320 x 480。請根據你的屏幕大小作更改。 安裝屏幕驅動 https://github.com/waveshare/LCD-show 安裝 git sudo apt-get install git -y clone 並安裝驅動 git clone https://github.com/waveshare/LCD-show cd ./LCD-show #把下面這行改成你屏幕的規格 sudo ./LCD35-show 如果你需要旋轉顯示角度,用這個指令 #無旋轉 cd LCD-show/ ./LCD35-show 0 #90度 cd LCD-show/ ./LCD35-show 90 #180度 cd LCD-show/ ./LCD35-show 180 #270度 cd LCD-show/ ./LCD35-show 270 測試 xserver…
Go 語言在 Linux IoT 開發板上處理大檔案上傳的解決方法
近年來不少本來用於物聯網開發的主板已經差不多具備 10 年前電腦主機的 IO 速度和處理水平了。在網上不難看到具備 1000Mbps 網絡接口並同時使用多核心的處理器的單片電腦(SBC),價格上也不太貴(約 100 - 120HKD 一塊),可玩性還是很高的。 其中我使用過的兩種開發板:ZeroPi 跟 OrangePi Zero Plus 然後,我想用它來組網絡儲存系統 對,理論上你是可以用這種 SBC 來做網絡儲存器(NAS),我們一個一個來看: ✔️ CPU: H3 / H5 處理器 ( 1.3Ghz ,一般作為文件伺服器完全沒問題) ✔️ 1000Mbps Ethernet / 網絡接口 ✔️ USB2.0 接口(480Mbps,對於家中只有 100Mbps 上下載的我來說完足夠) ✔️ 40 x 42 cm,可以直接黏在硬碟盒後面 ✔️ 運行時只需要 5V 0.2 - 0.3A,超級省電 可是問題就是它只有 512MB 的 RAM 啊!!! 現在這年代連最便宜的 Synology DS118 都要比它多 512MB RAM 啊! 可是,要這麼多 RAM 到底用來幹麼? Golang 網頁伺服器的文件上載的運作原理 一般來說使用 Golang 處理文件上載的邏輯都長這樣 r.ParseMultipartForm(10 << 20) file, handler, err := r.FormFile("myFile") if err != nil { fmt.Println("Error Retrieving the File") fmt.Println(err) return } defer file.Close() //然後用 io.Copy 把暫存檔案複制到目的地,這裡我就不仔細寫了 在一般電腦上運行這段代碼是完全 OK 的,在上載檔案時 Golang 會把 multipart form data 中的檔案緩存到 RAM,然後在 io.Copy 之後,defer 的 file.Close() 會把 Reader 關掉,之後交由 GC 處理刪除。 這情況只能適用於上載的檔案比系統能用的 RAM 還要細的情況,對於像 ZeroPi / Orange Pi 這種 SBC 要上載大型檔案,就不能用這傳統的方法,而需要特別處理了。不然就會出現以下情況 就是所謂的「爆 RAM」 那讓我們先來了解一下 parseMultipartForm 的原理吧,簡單來說是 你的 browser 把檔案塞到了一個 HTTP FORM 裡面browser 把 FORM 的資料連同你要的檔案在同一個(或多個) Request 裡面送到伺服器因為檔案太大,沒有辦法使用同一個 HTTP REQUEST 處理,所以到達的時候檔案可能被切成幾個 「區塊」,而這個就是 Multipart FormGolang 把這些「 區塊 」寫到 RAM 裡面(在 Debian 的情況下就是寫到 /tmp 裡面)然後如果檔案大小比RAM 大,/tmp 就會被塞爆,出現「no space left on device」錯誤如果 Golang 繼續把東西塞進去,它就會被作業系統殺掉(Killed) 要解決這個問題,我們可以使用兩個方法,首先是不要使用 Golang 內置的 Request library,自己重新寫一個。但是那樣實在太麻煩了,所以我決定直接在前端作更改 WebSocket 是個好東西 說到 WebSocket 很多人會想起類似 Chatroom 或是 Web 的 online game 之類,但是…
在 Debian Linux 下安裝 ZeroTier 網絡
由於寫教學太花時間了我就直接把需要的指令放到下面 # Upgrade and update the apt sudo apt-get upgrade sudo apt-get update # Install Git (if you didn't install it yet) sudo apt-get install git #Install Make and build dependencies sudo apt-get install make sudo apt-get install build-essential # Clone the source code of the zeroTier client cd ~/ mkdir build cd ./build git clone https://github.com/zerotier/ZeroTierOne # Optional: Install screen and run it inside screen so you can disconnect your ssh terminal sudo apt-get install screen -y screen # Start building the ZeroTier Client cd ./ZeroTierOne make # Optional: If you have screen installed (Hold Ctrl A + D to detach the terminal) 然後你就可以去吃個飯再回來了。這 Make 在 Intel 處理器的電腦上也行有夠久的,如果是 Rpi 之類的 SBC 就更不用想它會快了。 在它完成建置之後,你應該會看到類似這樣的檔案結構,如果 OK 的話就可以繼續: # Join your network using Network ID sudo ./zerotier-cli join <networkID> # Test if the compilation is working and setup the network sudo zerotier-one -d 前往 ZeroTier 的網站,幫新出現的裝置改名和設成 Auth 等一下之後他會顯示 DHCP 派發給這台裝置的 IP 地址,而回去 Linux 上面你使用 ifconfig 應該能看到相同的地址 如果是正確的話可以繼續: # Create a start script for the zerotier daemon sudo nano ~/zerotier.sh 把下面的資料放進去然後 Save + Exit (把 <user_name> 改成你現在在用的使用者名稱) #!/bin/sh sudo /home/<user_name>/build/ZeroTierOne/zerotier-one # Add zeroTier…
在 Raspberry Pi 安裝 Mercury USB WiFi AC650M 驅動
安裝步驟如下 準備所需的環境 下載 Raspberry Pi OS執行 apt-get update把 RTL8821CU 驅動源碼下載到本地端 mkdir -p ~/build cd ~/build git clone https://github.com/brektrou/rtl8821CU.git 4. 安裝 DKMS sudo apt-get install dkms 5. 更新 apt sudo apt update -y sudo apt upgrade -y 6. 安裝 bc 並 重新啟動 Raspberry Pi sudo apt-get install bc -y sudo reboot 7. 重啟後 cd 進去驅動目錄並更改 Makefile cd ~/build/rtl8821CU sudo nano Makefile 把下面此兩行 CONFIG_PLATFORM_I386_PC = y CONFIG_PLATFORM_ARM_RPI = n 更改成 CONFIG_PLATFORM_I386_PC = n CONFIG_PLATFORM_ARM_RPI = y 8. 處理 ARM 建置 flag 的問題 sudo cp /lib/modules/$(uname -r)/build/arch/arm/Makefile /lib/modules/$(uname -r)/build/arch/arm/Makefile.$(date +%Y%m%d%H%M) sudo sed -i 's/-msoft-float//' /lib/modules/$(uname -r)/build/arch/arm/Makefile sudo ln -s /lib/modules/$(uname -r)/build/arch/arm /lib/modules/$(uname -r)/build/arch/armv7l 9. 使用 DKMS 建置 sudo ./dkms-install.sh 10. 把 WiFi USB 插到 Rpi 上 11. 把 USB 裝置由 Mass Storage Mode 轉換成 WiFi Adpater Mode在轉換之前,你先要使用 lsusb 取得裝置的 ID,例如 0bda:1a2b ,然後使用 usb_modeswitch 指令進行轉換。如果還沒有安裝,可透過 sudo apt-get install usb_modeswitch 安裝 sudo usb_modeswitch -KW -v 0bda -p 1a2b systemctl start bluetooth.service 要讓它下一次開機都會自動轉換,編輯 usb_modeswitch sudo nano /lib/udev/rules.d/40-usb_modeswitch.rules 在以下這一行之前: LABEL="modeswitch_rules_end" 插入以下設置 # Realtek 8211CU Wifi AC USB ATTR{idVendor}=="0bda", ATTR{idProduct}=="1a2b", RUN+="/usr/sbin/usb_modeswitch -K -v 0bda -p 1a2b" 再次重新啟動後使用 sudo ifconfig 應該就能看到新的 WiFi 界面了(wlan1)
只需花 5分鐘就能阻擋 阻擋 AdBlock 網站的阻擋元件
話說我最近在 Facebook 上看到這一篇很有趣的文章,講述如何檢查出 Adblock 然後對該使用者彈出視窗來禁止他繼續閱讀: https://blog.reh.tw/archives/2273 大概會彈出這樣的視窗 先不論這方法是不是真的會讓讀者留久一點還是氣得直接按右上角的叉叉,對我這種習慣了網頁開發的野生技術程序員來說這簡直就是像彈出一張「戰帖」一樣。 好,我就來應戰吧! 對於要解決類似這種的 Adblock Blocker 其實很簡單,你只需要把阻擋你的東西移除掉,把 body 的 css 改一點點(通常是移除 overflow: hidden)就能夠繼續看的了。所以首先,我們要把找到擋著我們的東西。 我這裡使用的是 Firefox,你只要對擋著你的東西按右鍵 --> Inspect Element 之後到程式碼裡面找到代表這個部分的 div 的 id 或 class name 然後在 GreezeMonkey (Firefox 插件)開一個新的 script,引用 jQuery 跟 include 你需要阻擋 阻擋 Adblock 的網站,然後把全部相關的 id 跟 class 都 remove 掉就可以了。以下是一個非常簡單的例子: // ==UserScript== // @name REH TW Block AD Blocker // @version 1 // @grant none // @include https://blog.reh.tw/* // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js // ==/UserScript== console.log("Removing Adblocker"); $("body").removeClass("modal-open"); $(document).find(".modal-backdrop fade show").remove(); $("#adBlockMsg").remove(); 然後我們重新載入網頁: 耶!阻擋 Adblock 的東西被我們阻擋了!
同步上載與 RAM 不足的問題
在 Raspberry Pi 上面執行類似 arozos 之類的大型系統不免會出現一個問題,就是當要處理大量 IO 的時候總會卡住(畢竟最快的儲存器( RAM、記憶體) 只有那 2 - 4GB,雖然說現在已經出到 8GB 了,但是那個價格我倒不如買台二手的 ThinkCentre M73?),所以要如何在有限 RAM 的情況下讓檔案上下載速度最佳化是其中一個很有趣的研究方向。 上載的方式 Single-thread Upload (單執行緒上載) 我們就先由正常的上載情況說起吧,我在這裡說的就是一般的 HTML5 Form 的檔案上載。每次一個檔案,很簡單吧?可是這速度實在太慢了,例如 Google Drive 之類的很久之前就已經用了多執行緒上載了,所以要一個一個檔案上載的話,速度是無法接受的。 Multi-thread Upload (多執行緒上載) 就是如此,我在這文章裡說的主要是多執行緒上載模式。簡單來說 Multi-thread Upload 就是由 Browser 對伺服器的上載接點打開多個 request,並同時向接點上載資料的方式。 那 Multi-thread Upload 又有甚麼問題? Multi-thread Upload 雖然能夠縮短使用者上載檔案的時間,能夠盡用 bandwidth,但是伺服器也要處理短時間內同時上載的檔案,導致系統的開發有點考功夫,有時候甚至要考慮 Race Condition 問題。 在以前純 PHP 5 + Apache 2的年代,伺服器每次只能夠同時處理一個要求,因此很少會出現資源不足的這個問題。但是到現在,很多人都轉用更先進的語言來開發網頁伺服器了,自然也需要處理到資源分配的問題。以下我就以 Go 作為「思考語言」來分析一下吧。 先 Buffer (緩存)到 RAM 不就行了嗎? Buffering 是一個常用來處理 IO 的方法,由於下載的時候就是由作業系統跟硬碟的緩存機制處理,所以我們只需要處理上載就好。我們就先假設系統採用以下的一個邏輯處理使用者上載的檔案 //建立一個 25MB 的 Buffer r.ParseMultipartForm(25 << 20) //從 POST 中取得 file file, handler, _ := r.FormFile("file") //建立儲存的檔案 dest, _ := os.Create(handler.Filename) //從 File 把內容複制到 dest if _, err := io.Copy(dest, file); err != nil { panic(err) } //關閉打開了的 file descriptor (通稱 fd,即檔案描述符) file.Close() dest.Close() 你那看到,在檔案上載到伺服器的一刻,伺服器已經分配了 25MB 的記憶體給這個 request 了。而這個 request 會把這空間卡住,直到 file.Close() 之前都不會把記憶體還給作業系統,因此每上載一個檔案 = 吃掉一個不少於檔案大小的記憶空間。而在 file.Close() 之前還有一個 blocking 的 io.Copy() 把上載的資料複制到最終的儲存位置(例如硬碟),所以最後就是要等硬碟寫完才能把記憶體釋放出來。 簡單來說就是這樣 所以,這東西很慢! 為了解決慢的問題,我們來做多執行緒的檔案搬運吧!Go-routine 是一個很神奇的東西,不需要幾行我們就可以寫出支援 Multi-threading 的程式,只要我們把 io.Copy 的部分改成: //建立儲存的檔案 dest, _ := os.Create(handler.Filename) //把複制的部分放到 Go routine,讓客戶端繼續上載下一個檔案 go func(dest *os.File, file *os.File) //從 File 把內容複制到 dest if _, err := io.Copy(dest, file); err != nil { panic(err) } //關閉打開了的 file descriptor (通稱 fd,即檔案描述符) file.Close() dest.Close() }(dest, file) 新的方法看起來像這樣,比起上面的方法快多了 對,這樣檔案上載的速度快多了,而且還能同時發起幾個上載的要求,但是這還在檔案比較小,RAM 裝得下的情況下都還好,但是檔案大的時候該怎麼辦? 例如說,在只有 2GB…
目前第 2 頁,共有 4 頁