在 Raspberry Pi Zero 2W 上設定 WiFi AP 作為 WiFi 中繼器
因為不知道為甚麼網上沒有 Raspberry Pi Zero 2W 以外接 WiFi USB 作為 WiFi 中繼器的教學,所以我就來自己研究出一個方法囉 材料 Raspberry Pi Zero 2WRaspberry Pi OS 用作 wlan1 的 USB WiFi 模組 安裝所需 Package sudo apt-get update sudo apt-get upgrade sudo apt-get install hostapd sudo apt-get install dnsmasq sudo systemctl stop hostapd sudo systemctl stop dnsmasq 設定網絡界面卡 sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig sudo nano /etc/dnsmasq.conf 寫入以下內容 interface=wlan1 dhcp-range=192.168.0.11,192.168.0.30,255.255.255.0,24h 固定 wlan1 的 IP 地址 sudo nano /etc/network/interfaces 把 source /etc/network/interfaces.d/* 加上 comment: #source /etc/network/interfaces.d/* 寫入以下內容 auto lo iface lo inet loopback auto wlan0 iface wlan0 inet dhcp allow-hotplug wlan1 iface wlan1 inet static address 192.168.0.10 netmask 255.255.255.0 broadcast 192.168.0.255 gateway 192.168.0.254 設定 Hostapd sudo nano /etc/hostapd/hostapd.conf 寫入以下內容(記得更新 WiFi 名稱與密碼) interface=wlan1 hw_mode=g channel=7 wmm_enabled=0 macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP rsn_pairwise=CCMP ssid=[WiFi SSID 名稱] wpa_passphrase=[WIFI 密碼] 之後編輯預設設定 sudo nano /etc/default/hostapd 把原本的 #DAEMON_CONF="" 改成 DAEMON_CONF="/etc/hostapd/hostapd.conf" 設定 wlan1 -> wlan0 的 forwarding sudo nano /etc/sysctl.conf 把 #net.ipv4.ip_forward=1 改成 net.ipv4.ip_forward=1 新增 iptables 規則 如果你是用 Lite 版沒有 iptables 可以先透過以下指令安裝 sudo apt-get install iptables 然後輸入 sudo iptables -t nat -A POSTROUTING -o wlan1 -j MASQUERADE sudo sh -c "iptables-save > /etc/iptables.ipv4.nat" 編輯 rc.local 在啟動時自動應用規則 sudo nano…
使用 Apache2 當 Reverse Proxy 伺服器
因為最近房東在沒經我同意之前在房間的網絡上遊加入了一台 NAT 路由器,所以原本架在我的房間裡的 ArozOS 伺服器就沒辦法在外面連線了。想了想,於是我想到了可以透過 zeroTier 和 Reverse Proxy 來使用我房間裡的伺服器,所以便開始研究怎樣可以弄到一台 Reverse Proxy 的伺服器。 網絡上很多人喜歡用 nginx 來當 RP伺服器,可是基於某些原因我並沒有太喜歡它所以我就選擇用 Apache2 了。首先,安裝 apache2 sudo apt-get update sudo apt-get install apache2 -y 然後就是編輯它的設定檔,加入你需要 proxy 的目標 sudo nano /etc/apache2/sites-available/000-default.conf NameVirtualHost *:80 <VirtualHost *:80> ServerName ixtw.hkwtc.com ProxyPass / http://{zerotier 的區網 ip}:8080/ ProxyPassReverse / http://{zerotier 的區網 ip}:8080/ </VirtualHost> <VirtualHost *:80> DocumentRoot /var/www/html </VirtualHost> 然後 Enable Proxy 插件並重啟 apache 2 sudo a2enmod proxy_http sudo systemctl restart apache2 可是,WebSocket 要求過不去欸? 這是因為要 proxy websocket 會需要額外的 module 去處理,首先啟用 wstunnel 跟 rewrite engine sudo a2enmod proxy_wstunnel sudo a2enmod rewrite sudo systemctl restart apache2 然後加入 RewriteCond 跟 RewriteRule <VirtualHost *:80> ServerName ixtw.hkwtc.org RewriteEngine On RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR] RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC] RewriteRule /(.*) ws://192.168.196.15:8082/$1 [P,QSA,L] ProxyPass / http://192.168.196.15:8082/ ProxyPassReverse / http://192.168.196.15:8082/ </VirtualHost> 最後再重啟一次 apache sudo systemctl restart apache2 這樣就完成了!
ArOZ Portable 主板開發計劃
說到 ArOZ Portable 計劃,這是一台可以放進口袋裡的迷你 ArozOS 伺服器。如果你沒看過的話現在的版本大約長這樣 最穩定版本的設計 可是如果你有機會把它拆開來看,你就會發現這版本其實是沒辦法量產的。因為裡面的線路長這樣 所以結論是:在真的量產之前我要先把它變成可以量產的設計。 ArOZ Portable 主板 通常說到電腦、手提電子裝置等等的東西第一個最基本的東西就是主板了。 這個計劃由 2016 年開始我就不斷在嘗試弄一塊主板來省略那堆亂七八糟的走線,可是每次都因為不同的原因而失敗。最大的原因可能是因為我還沒有焊接 小於 SOP 跟 0805 針腳的技術力吧,也有不少原因是當年的畫板技術還沒足夠讓我真的去自由發揮。 在這裡我就跟大家分享一下這幾年來我設計的 ArOZ Portable 主板(失敗品)吧 初代 ArOZ Portable 主板 第一代的 ArOZ Portable 主板 這一塊主板基本上就是一塊 FE1.1S USB Hub 晶片 + Micro SD 轉 USB 的晶片(用以代替外接的 USB 儲存裝置),但是當然這兩塊晶片的腳位都是 SSOP 的,而到現在我還是沒有合適的工具去焊接 SSOP 腳位,所以這板子當然是失敗了。 第二代主板 第二代的主板主要是把 SD 卡讀卡器移除,跟把充電電路放回進去。你在 U2 的位置能夠看到一個熟悉的 TP4056 layout,理論上是可以提供 5V 1A 的充電性能。但是唯一一個問題是這電路設計的時候沒考慮到 5V 升壓問題(而且還要畫完,在掉去生產前一刻才發現這問題),而大部分 USB 裝置必須要有 5V 供電才能運作,結果這板子也是不能用的。 第三代主板 在發現第二代這種低級錯誤之後第三代就換回去用移充的主板來供電了(右上的那個凹進去的位置),然後順便加粗了供電的走線和買了焊膏和熱風槍。雖然有成功焊出一兩塊,但是最後也是因為 FE1.1S 焊的良率太低而報廢(果然還是需要紅外線焗爐啊) 第四代 想著既然第三代都能有幾個 % 的成功率,那第四代也能碰碰運氣看看定 20塊板子有沒有一塊能成功吧?於是我把充電、升壓、USB Hub 等等全部放到一塊板子上面,結果良率太低全部焊完之後一塊正常運作的板子都沒有(# 第五代 「算了,還是 back to basic 好了。」用這種邏輯而畫的版子。只提供了最基本的電源、USB port 信號 routing 跟 USB Hub 的晶片,結果雖然有一點問題,但是加一點 patch wire 之後總比一大堆線要好。可是這體積,應該還能再小一點? 第六代 因為考慮到能不能把良率超低的 FE1.1S 找 OEM 進行焊接,然後以模組的形式插回到主板上而開發的板子。基本上與第五代的設計一樣,就是把 FE1.1S 的 footprint 換成了 2.54 針腳的插槽 可是後來這版本並沒有進行生產,因為問了才知道這麼少的生產量走一趟 OEM 貼片實在太貴了(比起買 USB hub 模組回來拆還要貴),所以就這樣這東西就放置了 1年多(由 fyp 開始到 grad 我都沒碰過這計劃) 第七代 在香港疫情爆得最厲害的時候因為在家裡沒事幹所以抽了點時間來設計這塊板子。這基本上是基於第七代的設計,但是把多餘的板子部分切走,讓它最小化方便安裝。可是沒進行生產的原因也是跟第七代一樣。另外你也能看到背面原本要放移充主板的位置被一塊 Powermanv5 代替。 主板背面,使用自家開發的 Powermanv5r2 主板代替移充主板,這樣就不用每做一台 ArOZ Portable 就犧牲一塊移充 背面用來供電跟充電的 Powermanv5r2,也是自家開發的其中一種通用模組,已經量產 https://www.tindie.com/products/tobychui/powerman-power-boost-lipo-charger-inoutput-5v-2a/ 第八代 經過這一年來不斷來回生產不同的 PCB 來做 STEM Kit 跟 了解了更多不同類型的晶片供應商,現在終於有技術可以重新回到這個 project 了。在幾個巧合的情況下才讓我有辦法設計到這塊板子: 在 twitter 看到有工程師分享一塊 SOP16 的 USB Hub 晶片:SL2.1A在拆移充時發現了一塊更便宜的充放管理晶片:HT4928我已經開發好 Powerman 跟 Powerloli 兩塊通用鋰電池充放管理板(內置升壓功能)終於理解 When in doubt, ground it out (有懷疑不知道它要連哪裡的時候,就把它接地吧)的道理 後面可以直接拉線到 Raspberry Pi Zero W 的 test pad 供電和 USB 接口 然後就是等 Power Loli v2 的樣品 PCB 回來,測試過沒問題之後就可以正式生產這塊第 八代的板子了。 8 May…
用了三年時間寫網頁桌面作業系統的故事
這是一個想找地方儲存他的音樂、影片跟圖片的窮學生與他的 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 是跟系統運作穩定性相關的,理論上應該是由裝置的…
從零開始的 IoT 系統設計
說到 IoT (物聯網)很多人第一時間就會想起 Google Home ,Apple HomeKit,Amazon Echo 之類的智能喇叭(音箱),但是他們並不是真的跟 IoT 有關,他們比較像是一個輸入裝置或是一個 gateway 去控制其他裝置而已。 另外比較有技術背景的人可能會想起一些 IoT 的技術,好像說 Bluetooth BLE,Zigbee,2.4Ghz RF 跟 IR 之類的傳輸技術,也有另外一些人會想起 IFTTT、MQTT 之類的通訊協議。可是我們這次不是要說這個,而是 怎樣從零開始自己幹一套 IoT 控制系統出來 嘛,雖然說是從零開始但是總不能從沙開始煉硅晶吧?所以這裡的零界定為: 不使用任何專門為 IoT 設計的產品或軟件來制作這套 IoT 系統。 托比 因為之前我在高中畢業到大一之間的暑假我也曾經研究過一套叫 Home Dynamic 的 IoT 系統,所以這套東西就沿用這個名字叫做 Home Dynamic v2 吧! 選擇硬體平台 如果要開發物聯網系統,最基本的話就是需要有「物聯網」中「物」的部分吧?所以我就先由硬體開始選擇。這裡我有幾個要求 便宜,而且容易買到體積要夠小,而且輕便省電(因為要 24/7 的運作) 所以這裡就直接省卻了基於 ARM 處理器的開發板了。剩下的就只有 Arduino 系列用的 ATmega 或是更省電的 ATtiny 或者 ESP 系列的 ESP8266 或 ESP32 選擇連接協議 到了「物聯網」中「聯」的部分,到底要怎樣讓這塊板子連接到其他裝置上?市面上有很多不同的 IoT 連接協議及相關的硬體,常見的包括: Zigbee (通常用在 Mesh 網絡上 )Bluetooth BLE (通常用在需要超省電的裝置上)LoRa (通常用作長距離通訊)2.4Ghz RF (通常用作即時控制,如搖控車)WiFi (對,就是你手機筆電在用的那個 WiFi) 這裡的選擇其他也滿簡單的。首先,LoRa 一定用不上,香港的房子連轉身的空間也沒有,能連接數公里距離的 LoRa 一點用途都沒有。 Zigbee 的確是其中一個可以考慮的選擇,但是因為我也想透過現有的 ArozOS 系統控制它,而 Android、Windows 等作業系統都沒有原生支援 Zigbee,所以在這裡也不作考慮。 最後只剩下 3個選擇:BLE,2.4Ghz RF 跟 WiFi ,不過反正大家都是 2.4Ghz,而且因為我的 IoT 裝置都是用來控制電源、或是用來檢測太陽能板等很容易都抓到電源的地方,那當然選擇最容易可以透過不同手提裝置控制的選擇:WiFi 選擇網絡連接方式 說到「物聯網」的「網(絡)」,既然都跑 WiFi 了當然是用 HTTP 啊!雖然也有其他連接方式例如 MQTT 之類的,但是在選擇這個技術之前要考慮到幾點 這種技術會不會有 SPOF (Single Point of Failure)這種技術會不會需要額外的伺服器 / 硬體這種技術開發起來方不方便(畢竟是寫給自己用,賺不了錢所以太複雜了反而沒意思) 所以就用最簡單的 GET request 就好了啦。而且這方法也不需要 ESP32 內置的藍牙硬體,所以我們可以直接選擇最便宜的 ESP01 來做我們的控制器,一石二鳥。 可是,我們怎樣從網絡中找到 IoT 裝置? 這是一個好問題,而且之前我在開發 Home Dynamic 系統的時候也遇過類似的問題。但是作為剛剛升大學的我,當時我使用了一個最原始的方法:每個 IP ping 一次 但是在這次的開發計劃中(我把它叫做 Home Dynamic v2),我打算使用 mDNS 來取代這個(爛)IP 掃描方法。畢竟比起一次過把整個區網 ping 一次,例不如讓裝置自己 broadcast 自己的信息好了。剛好 ArozOS 也有內置到 mDNS 掃描器,所以就直接拿來做 IoT 掃描器不就行了嗎? 於是,我把 mDNS 的 transponder 塞到 ESP8266 裡面,然後跟據 ArozOS 的 mDNS transponder 結構寫了個這樣的 metadata 到 transponder 的回應信息裡面。以下為 hdsv2 的電源開關控制器的代碼: //Inject zeroconf attr into the MDNS respond (For scanning by ArozOS) void…
在 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 賀圖
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 之類,但是…
同步上載與 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…
由網頁拖放檔案到桌面的神奇方法
你有沒有想過到底怎樣可以把檔案由 DOM ELEMENT 拖放下載到桌面? 由本地的檔案管理員或是桌面拖放檔案到網頁上的話大家應該都很容易在一些網站如 facebook 或 youtube 看到可是說由網頁端拖放檔案到桌面就更少見了。 為甚麼我從來都沒看過有網站用這個方法讓使用者下載檔案? 這當然是有原因的,因為這個功能只有 Google Chrome 支援而已。對於不能跨平台用的 API 沒人用那當然很正常吧?但是作為研究最新技術的 imuslab,這個有趣的功能當然要加進去 ArOZ Online 系統吧?但是,要使用這個檔案由網頁拖放到桌面卻是有這麼堆特別要求: 拖放的元件必須是連接 (<a>)元件的 href 一定要填寫不能留空,但是也可以填 javascript:void(0); draggable 需要設為 true不能使用DOM 屬性 ondragstart ,需要使用 JavaScript 來 加 EventListener 如果你的情況符合上面的條件,你就可以把檔案透過這個 <a> Element 拖放到本地檔案系統了。以下為一個簡單的 Example: $("#element")[0].addEventListener("dragstart",function(evt){ var targetMime = "plain/text"; $.ajax({ url: "media/getMime/?file=" + encodeURIComponent(properties.filepath), success: function(data){ if (data.error !== undefined){ targetMime = "text/directory"; }else{ targetMime = data; } }, async: false }); evt.dataTransfer.setData("DownloadURL",targetMime+":"+ properties.filename +":"+ "http://" + location.hostname + ":" + location.port + "/media/?file=" + encodeURIComponent(properties.filepath)); },true); 到這裡有幾點值得注意 MIME Type 必須符合檔案的內容,不然拖放之後會出現 Internet Shortcut 檔案而不是檔案本身如果要在 event 裡使用 AJAX,那 Request 必須為 Synchronize ,就好像上面的例子中用來要求取得檔案 MIME Type 的 AJAX request 一樣當設定 DownloadURL 參數時時必需使用完整 URL 而不能使用 Relative PathaddEventListener 的 Option 值必須為 true 就是這樣,你就能成功的做出一個可以拖放到桌面的 HTML5 DOM Element 了喔! 19/6/2020 補充 如果你在下載的時候遇到這個問題 失敗 已封鎖的例子 一般是因為以下兩個原因 你寫入的 URL 不正確或檔案不存在上面的例子裡的一個小 Bug (Hard code 了 http 的問題) 對於非 http 的使用者來說,你可以把這一行 evt.dataTransfer.setData("DownloadURL",targetMime+":"+ properties.filename +":"+ "http://" + location.hostname + ":" + location.port + "/media/?file=" + encodeURIComponent(properties.filepath)); 改成 evt.dataTransfer.setData("DownloadURL",targetMime+":"+ properties.filename +":"+ location.protocol + "//" + location.hostname + ":" + location.port + "/media/?file=" + encodeURIComponent(properties.filepath)); (注意多出來的 location.protocol)來自動 Detect 目前是使用 http: 還是 https: 協議
目前第 1 頁,共有 3 頁