1. 概述
- 本文介紹了一個(gè)簡單的嵌入式項(xiàng)目的的開發(fā)過程;
- 從需求到實(shí)踐,本文對整個(gè)過程做了全面的介紹,本文所介紹的設(shè)備容易獲得且價(jià)格低廉;
- 本文涉及了 *linux* 下 C 語言下的網(wǎng)絡(luò)編程、網(wǎng)絡(luò)廣播、*Magic Packet*、內(nèi)網(wǎng)穿透、反向代理等概念;
- 本文所涉及的一些技術(shù)概念讀者可以自行參考其它的文章;
- 本文可能并不適合初學(xué)者
2. 需求
- 家里放了一臺(tái)服務(wù)器,差不多我所有的東西都在服務(wù)器上,不管在家里還是其它地方,都需要連接這臺(tái)服務(wù)器才能做事情;
- 這臺(tái)服務(wù)器白天開著,晚上就關(guān)了(省點(diǎn)電:));每天起床以后要想著按一下服務(wù)器的電源開關(guān),每天睡覺前要記得把服務(wù)器關(guān)了;
- 晚上忘記關(guān)服務(wù)器,通常不會(huì)有什么問題;但有時(shí)早上沒有打開服務(wù)器,可能就要有麻煩了;
- 尷尬的時(shí)候就是早上沒有打開服務(wù)器,然后外出,然后剛好需要登錄服務(wù)器,這才想起來服務(wù)器沒開;
- 所以吶,我需要有個(gè)機(jī)制,可以 **遠(yuǎn)程打開我的服務(wù)器**,這樣我就不會(huì)再出現(xiàn)尷尬了;
3. 簡要解決方案
- 我們把要完成的這個(gè)需求當(dāng)作一個(gè)項(xiàng)目來做,可以把這個(gè)項(xiàng)目叫做服務(wù)器遠(yuǎn)程開機(jī)
- 首先要確保服務(wù)器的主板支持網(wǎng)絡(luò)喚醒,否則不太好辦,不過現(xiàn)在的絕大多數(shù)主板都是支持的(PCI 2.2 以后的主板一般都支持,而 PCI 2.2 的標(biāo)準(zhǔn)是1998年提出的),有些主板可能需要 BIOS 設(shè)置,請自行搜索解決方案;
- 第二是在家里的局域網(wǎng)上要有一個(gè)小設(shè)備是 24 小時(shí)運(yùn)行的,通過這臺(tái)設(shè)備在局域網(wǎng)上廣播 Magic Packet 來喚醒服務(wù)器,這臺(tái)設(shè)備應(yīng)該是一臺(tái)低功耗設(shè)備,越小越簡單越好;我們姑且把這個(gè)設(shè)備叫做 wakener;
- 第三是要在這個(gè) wakener 上編寫一個(gè)簡單的程序,這個(gè)程序可以在局域網(wǎng)上廣播 Magic Packet,以喚醒服務(wù)器,這個(gè)程序我們稱為 wakeOnLan;
- 第四是要有一個(gè)機(jī)制可以從互聯(lián)網(wǎng)上訪問到這臺(tái) wakener,只有這樣才能從互聯(lián)網(wǎng)上操控 wakener 上的軟件,其實(shí)這是一個(gè)內(nèi)網(wǎng)穿透的問題,要做到這一點(diǎn)或許需要一臺(tái)連接到互聯(lián)網(wǎng)上的輕量級(jí)服務(wù)器(VPS就可以);
- 這樣我不管在哪里,通過終端(筆記本、臺(tái)式機(jī)、手機(jī)、平板等)登錄家里24小時(shí)開機(jī)的 wakener,運(yùn)行 wakeOnLan,就可以打開我的服務(wù)器;
- 這臺(tái)24小時(shí)運(yùn)行的設(shè)備(wakener)有可能為你完成更多的事情,比如 NAS、遠(yuǎn)程開空調(diào)等等,但是重要的是完成眼前這個(gè)第一步。
4. 技術(shù)要點(diǎn)
上面的解決方案顯然非常粗糙,但是這個(gè)項(xiàng)目本身確實(shí)也比較簡單,沒有必要做非常詳盡的設(shè)計(jì)方案,所以我們下面僅列出一些可能的要點(diǎn)及解決方法
本項(xiàng)目的基本網(wǎng)絡(luò)架構(gòu)
下面是一個(gè)簡單的示意圖,表達(dá)了這個(gè)項(xiàng)目中各個(gè)設(shè)備是如何連接和互相影響的,其中 Local Server 是我們要遠(yuǎn)程開機(jī)的服務(wù)器,Server 是一臺(tái)連接在互聯(lián)網(wǎng)上的 VPS,用于內(nèi)網(wǎng)穿透
圖1:wake on lan架構(gòu)圖
Magic Packet
遠(yuǎn)程喚醒其實(shí)是網(wǎng)卡的一個(gè)功能,在 PCI 2.2 之后,信號(hào)線上多了一個(gè) PME 信號(hào),主機(jī)關(guān)閉電源后進(jìn)入休眠狀態(tài),可以繼續(xù)為網(wǎng)卡供電,網(wǎng)卡在收到一個(gè)叫做 Magic Packet 的數(shù)據(jù)包后,檢測出該數(shù)據(jù)包是發(fā)給自己的,則會(huì)在 PCI 上發(fā)出 PME 信號(hào),從而控制電腦啟動(dòng),這個(gè)功能被稱為 網(wǎng)絡(luò)喚醒
網(wǎng)絡(luò)喚醒其實(shí)并不復(fù)雜,在局域網(wǎng)中,從一臺(tái)電腦以廣播的形式發(fā)送 Magic Packet,那么在 Magic Packet 中指定的 mac 地址對應(yīng)的電腦就會(huì)被喚醒;
Magic Packet 就是一個(gè)指定格式的數(shù)據(jù)包,其格式為:6 個(gè) 0xff,然后 16 組需要被網(wǎng)絡(luò)喚醒的電腦的 MAC 地址,比如需要被喚醒的電腦的 MAC 為:00:e0:2b:69:00:03,則 Magic Packet 為(16進(jìn)制表述):
ff ff ff ff ff ff
00 e0 2b 69 00 03 00 e0 2b 69 00 03
00 e0 2b 69 00 03 00 e0 2b 69 00 03
00 e0 2b 69 00 03 00 e0 2b 69 00 03
00 e0 2b 69 00 03 00 e0 2b 69 00 03
00 e0 2b 69 00 03 00 e0 2b 69 00 03
00 e0 2b 69 00 03 00 e0 2b 69 00 03
00 e0 2b 69 00 03 00 e0 2b 69 00 03
00 e0 2b 69 00 03 00 e0 2b 69 00 03
通常 Magic Packet 的廣播使用 UDP 發(fā)送,端口號(hào)可以任意,通常使用 7 或者 9
關(guān)于 Magic Packet 的更多的信息請參考
- [wikipidia-網(wǎng)絡(luò)喚醒](https://zh.wikipedia.org/zh-cn/%E7%B6%B2%E8%B7%AF%E5%96%9A%E9%86%92)
- [wikipedia-Wake-On-Lan](https://en.wikipedia.org/wiki/Wake-on-LAN)
從圖1中可以看出,Wakener 通過路由器向局域網(wǎng)內(nèi)廣播 Magic Packet,從而喚醒 Local Server;
要注意的是,Magic Packet 只對有線網(wǎng)卡有效,所以,服務(wù)器要使用網(wǎng)線連接到路由器上;
內(nèi)網(wǎng)穿透
在這個(gè)項(xiàng)目里,內(nèi)網(wǎng)穿透指的是:使用一定的技術(shù)手段讓我們可以從互聯(lián)網(wǎng)上直接訪問到家里的一臺(tái)設(shè)備上,這臺(tái)設(shè)備通過普通家用寬帶連接互聯(lián)網(wǎng),家用寬帶可能沒有公網(wǎng) IP;
如果家里的寬帶有公網(wǎng) IP,穿透內(nèi)網(wǎng)并不是一個(gè)很困難的事,大致需要三個(gè)步驟:
- 在家里路由器上設(shè)置一個(gè) NAT 讓外網(wǎng)的訪問直接轉(zhuǎn)發(fā)到局域網(wǎng)內(nèi)某個(gè)指定設(shè)備的指定端口上;
- 局域網(wǎng)中的一個(gè)設(shè)備(或者路由器本身)向某個(gè)有權(quán)限的公網(wǎng)服務(wù)器發(fā)送心跳包,使這個(gè)服務(wù)器可以知道家里寬帶的公網(wǎng) IP;
- 互聯(lián)網(wǎng)上的終端設(shè)備通過服務(wù)器獲知家里寬帶的公網(wǎng) IP,直接訪問即可;
如果家里的寬帶沒有公網(wǎng) IP(大多數(shù)寬帶應(yīng)該是這樣的),穿透內(nèi)網(wǎng)就要麻煩一些,通常需要使用反向代理來實(shí)現(xiàn),這需要一個(gè)公網(wǎng)服務(wù)器;
- 公網(wǎng)服務(wù)器上安裝有支持反向代理的服務(wù)器端軟件,比如 *sshd*;
- 局域網(wǎng)的設(shè)備上裝有反向代理的客戶端軟件,比如 *ssh*,通過向公網(wǎng)服務(wù)器發(fā)送反向代理的指令可以建立一個(gè)反向代理隧道(*tunnel*),*tunnel* 建立起來以后,訪問公網(wǎng)服務(wù)器的某個(gè)指定端口將被映射到訪問局域網(wǎng)中某個(gè) IP 地址下的某個(gè)端口,從而實(shí)現(xiàn)內(nèi)網(wǎng)穿透;
目前有很多的內(nèi)網(wǎng)穿透的工具,其原理其實(shí)都是反向代理,但通常比直接用 ssh 要好用得多,至少 ssh 在意外斷開后不會(huì)自動(dòng)重連,這些工具都會(huì)解決這些問題;
本文所使用的嵌入式 Linux 系統(tǒng)將是 openwrt,理論上可以使用向日葵的內(nèi)網(wǎng)穿透插件,愿意折騰的讀者可以嘗試折騰一下向日葵插件;
本文在內(nèi)網(wǎng)穿透上將使用一個(gè)叫 frp 的開源項(xiàng)目,后面會(huì)給出這個(gè)項(xiàng)目的具體網(wǎng)址
其它要面對的麻煩
為硬件燒錄或編譯一個(gè)定制的 *Linux* 操作系統(tǒng),還好本文的例子并不需要自己編譯一個(gè) *Linux*,燒錄就好了;
交叉編譯環(huán)境,這個(gè)是做嵌入式開發(fā)必須面對的,無法回避。
5. 簡單的嵌入式開發(fā)步驟
- 需求分析
- 概要設(shè)計(jì)和詳細(xì)設(shè)計(jì)
- 硬件開發(fā)及驗(yàn)證
- 編譯與硬件相適應(yīng)的操作系統(tǒng)及所需工具
- 建立在相應(yīng)硬件上進(jìn)行軟件開發(fā)的交叉編譯環(huán)境;
- 嵌入式軟件開發(fā);
- 調(diào)試
下面我們會(huì)依照這個(gè)開發(fā)步驟去實(shí)現(xiàn)我們的方案
6. 具體實(shí)踐
需求分析和設(shè)計(jì)
本項(xiàng)目的開發(fā)的需求分析和設(shè)計(jì)已經(jīng)在前面完成,鑒于該項(xiàng)目比較簡單,就不做更詳細(xì)設(shè)計(jì)了
硬件開發(fā)和驗(yàn)證
- 要找一個(gè)24小時(shí)運(yùn)行的設(shè)備
我有一個(gè)早就不用的 迅雷一代賺錢寶(以下簡稱賺錢寶),不知道為何物的自己去百度一下
賺錢寶這個(gè)玩意 CPU 用的是 Amlogic S805,ARM Cortex-A5 架構(gòu),4核1.5GHz,功耗非常低,記住這個(gè) CPU 的型號(hào),記住這個(gè) CPU 是32位的;
圖2:賺錢寶外觀
圖3:賺錢寶主板
這個(gè)玩意有 *256M* 內(nèi)存,1G 的 *Flash*,*100M* 網(wǎng)口和一個(gè) *USB* 口,足夠用了;
以前有一些廠家用這個(gè) *CPU* 做網(wǎng)絡(luò)機(jī)頂盒,現(xiàn)在應(yīng)該沒人用了;
淘寶上查了一下,賺錢寶一代是買不到了,但三代(玩客云)還有二手賣,大概在45 - 55元,用的CPU和一代一樣,只是內(nèi)存大了,如果你想折騰,也可以買一個(gè)來玩;
- 硬件驗(yàn)證
這個(gè)設(shè)備的硬件顯然不需要驗(yàn)證,迅雷已經(jīng)幫我們驗(yàn)證了;
實(shí)際上,這個(gè)設(shè)備可以有多種選擇,如果你的路由器是 OEM 的,上面通常運(yùn)行的都是 openwrt,那么你可以直接使用它;
或者你手頭有閑置的機(jī)頂盒等,都有可能用得上;但是通常不建議使用平板,一是太耗電,二是把Android/ target=_blank class=infotextkey>安卓刷成 *Linux* 有難度。
操作系統(tǒng)
賺錢寶上原有的 Linux 應(yīng)該是迅雷自己編譯的,很難知道迅雷在這個(gè)系統(tǒng)上做了什么,所以還是不用為好,需要刷個(gè)新的系統(tǒng),CPU 為 S805 的設(shè)備通常都是支持 USB 刷機(jī)的,所以其實(shí)根本不用把它拆開,直接一條 USB 線就可以刷新系統(tǒng)了;
刷個(gè) openwrt 是比較現(xiàn)實(shí)的,有現(xiàn)成的教程,而且 openwrt 資料豐富,便于今后折騰;
刷機(jī)教程:[廢物利用之閑置礦渣迅雷賺錢寶一代刷OpenWrt固件發(fā)揮余熱_智能設(shè)備_什么值得買];
刷機(jī)包及相應(yīng)軟件:(
https://pan.baidu.com/s/1E4Ls05lPHHHhv0Ou8fb7GA);提取碼:ow2l
刷機(jī)包提供了 openwrt 18 和 19 兩個(gè),建議刷 openwrt 19(因?yàn)槲宜⒌氖?nbsp;openwrt 19)
具體刷機(jī)過程自己去享受吧;
刷好的機(jī)器應(yīng)該是可以使用 ssh 登錄的
- 首先要從你的路由器上找到這臺(tái) openwrt 的 IP 地址,比如為:192.168.2.100,然后用 ssh 登錄,新刷的 openwrt 沒有密碼
ssh [email protected]
- 我這里登錄以后的樣子,登錄以后一定要改一下 root 的密碼
圖4:ssh登錄openwrt
- openwrt 還會(huì)有一個(gè) web 界面,直接在瀏覽器上輸入 IP 即可進(jìn)入
圖5:登錄openwrt的web界面
圖6:openwrt的web界面
- 這臺(tái)設(shè)備最好不要使用 DHCP,而是使用固定 IP,這樣便于以后遠(yuǎn)程登錄,有三種方法設(shè)置固定 IP
1. 直接修改配置文件
openwrt 的網(wǎng)絡(luò)配置文件放在 /etc/config.NETwork 文件中,可以直接修改這個(gè)文件,改成下面這個(gè)樣子:
圖7:openwrt 改成固定 IP
2. 使用 openwrt 的 web 界面修改
從瀏覽器登錄 openwrt 的 web 頁面,其密碼與 ssh 的密碼一致,選擇:Network --> Interfaces --> Edit
圖8:通過openwrt的web界面修改固定IP
圖9:通過openwrt的web界面修改固定IP
圖10:通過openwrt的web界面修改固定IP
3. 在路由器上用 *MAC* 地址綁定 IP
實(shí)際上就是在路由器上設(shè)置 DHCP 每次分配 IP 時(shí),給指定 MAC 地址的設(shè)備分配一個(gè)固定的 IP,這樣,你的 openwrt 設(shè)備就不必設(shè)置固定 IP了,每種路由器的設(shè)置方法不一樣,我的路由器的界面像這個(gè)樣子
圖11:通過路由器設(shè)定openwrt的固定IP
建立交叉編譯環(huán)境
要在賺錢寶上寫程序,需要我們在本地電腦完成編程,然后用交叉編譯的工具編譯成在賺錢寶上可以運(yùn)行的程序,傳到賺錢寶上才可以在賺錢寶上運(yùn)行;
所以我們需要有一個(gè)工具鏈,對我們編寫的程序進(jìn)行交叉編譯;
這個(gè)工具鏈不是放在 wakener 上的,因?yàn)?nbsp;wakener 通常性能比較差,而且為了節(jié)省存儲(chǔ)空間,上面通常只放一些運(yùn)行時(shí)(runtime)庫,不具備開發(fā)的能力;所以這個(gè)工具鏈要放在另外一臺(tái)運(yùn)行著 Linux 的機(jī)器上,也可以運(yùn)行在虛擬機(jī)上,我們把這臺(tái)電腦叫做 開發(fā)機(jī),建議在開發(fā)機(jī)上運(yùn)行 ubuntu;
下面是建立這個(gè)編譯環(huán)境的過程
- 首先 *ssh* 登錄你剛刷的 *openwrt*,查看你刷的 *openwrt* 的版本號(hào):
圖12:查看openwrt的版本號(hào)
- 下載適當(dāng)?shù)?sdk
去 [openwrt官網(wǎng)](
https://downloads.openwrt.org) 找到你所需要的版本號(hào)下的 sdk,要選擇 at91/sama5 目錄下的,這個(gè)是 cortex-A5 架構(gòu)(CPU S805 的架構(gòu))的工具鏈
[我的工具鏈的下載地址](
https://downloads.openwrt.org/releases/19.07.7/targets/at91/sama5/openwrt-sdk-19.07.7-at91-sama5_gcc-7.5.0_musl_eabi.Linux-x86_64.tar.xz)
注意這個(gè)工具鏈只能運(yùn)行在 Linux 下,我是運(yùn)行在 Ubuntu 下,而且 openwrt-19.07.7 的 SDK 只有 64 位 X86 版本;
先將這個(gè)工具鏈下載到 ~/Downloads/ 目錄下
wget https://downloads.openwrt.org/releases/19.07.7/targets/at91/sama5/openwrt-sdk-19.07.7-at91-sama5_gcc-7.5.0_musl_eabi.Linux-x86_64.tar.xz -C ~/Downloads/
再將其解壓出來
cd ~/Downloads
tar -xvf openwrt-sdk-19.07.7-at91-sama5_gcc-7.5.0_musl_eabi.Linux-x86_64.tar.xz
在 ~/Downloads/ 目錄下會(huì)建立一個(gè)新目錄
openwrt-sdk-19.07.7-at91-sama5_gcc-7.5.0_musl_eabi.Linux-x86_64,進(jìn)入到這個(gè)目錄,可以看到一個(gè) staging_dir 目錄
cd openwrt-sdk-19.07.7-at91-sama5_gcc-7.5.0_musl_eabi.Linux-x86_64/
ls
這個(gè) staging_dir 目錄下的所有內(nèi)容就是一個(gè)完整的工具鏈,我們可以把這個(gè)工具鏈單獨(dú)拿出來使用;
我把這個(gè)目錄拷貝到了 ~/tooschain/openwrt-a5/ 下,我們之所以放到 ~/toolschain/ 下,是因?yàn)槲覀冞€可能有別的設(shè)備的工具鏈,都放到這個(gè)目錄下便于管理
mkdir -p ~/toolschain/openwrt-a5/
cp -fr ~/Downloads/openwrt-sdk-19.07.7-at91-sama5_gcc-7.5.0_musl_eabi.Linux-x86_64/staging_dir/* ~/toolschain/openwrt-a5/
現(xiàn)在我們已經(jīng)有了一個(gè)完整的工具鏈,但這個(gè)工具鏈基本上是沒辦法用的,我們需要簡單的配置一下,其實(shí)就是設(shè)置一些環(huán)境變量;
在 HOME 目錄下編輯一個(gè)文件 a5.sh,內(nèi)容如下:
#!/bin/bash
#A5 arm-linux-musl-eabi工具鏈,用于一代賺錢寶openwrt
export PATH=/home/whowin/toolschain/openwrt-a5/toolchain-arm_cortex-a5+vfpv4_gcc-7.5.0_musl_eabi/bin:$PATH
export STAGING_DIR=/home/whowin/toolschain/openwrt-a5
其中 /home/whowin 為我的 HOME 目錄,你要更改為你的 HOME 目錄;
其實(shí)這個(gè)文件就是修改了環(huán)境變量 PATH,然后增加了一個(gè)環(huán)境變量 STAGING_DIR,這些設(shè)置都是為了能夠正常使用這個(gè)工具鏈;
將這個(gè)文件設(shè)置為可執(zhí)行,然后把這個(gè)文件拷貝到 /bin/ 下:
cd ~
vi a5.sh
(編輯內(nèi)容并存盤)
chmod 755 a5.sh
sudo cp a5.sh /bin/
放到 /bin/ 目錄下只是為了用起來方便,并沒有特別的含義;
下面我們可以試一下這個(gè)工具鏈
source /bin/a5.sh
arm-openwrt-linux-gcc -v
下面是在我的機(jī)器上的輸出:
圖13:測試工具鏈
源程序
有了工具鏈就可以編程了,編程的過程要在開發(fā)機(jī)上完成,不是在 openwrt 下,但是用這個(gè)源程序編譯出來的可執(zhí)行文件是要在 openwrt 下運(yùn)行的;
下面是這個(gè)項(xiàng)目中需要在 openwrt 下運(yùn)行的程序 wakeOnLan 的源程序
/******************************************************************************
File Name: wakeOnLan.c
Description: 向局域網(wǎng)中的計(jì)算機(jī)發(fā)出遠(yuǎn)程喚醒的指令
該程序?qū)⑦\(yùn)行在迅雷一代賺錢寶上(已刷openwrt),用于喚醒主機(jī)
Compile: source /bin/a5.sh
arm-openwrt-linux-gcc -Wall wakeOnLan.c -o wakeOnLan
Usage: wakeOnLan [boradcast IP] [MAC]
Example: sudo ./wakeOnLan 192.168.2.255 00:e0:2b:68:00:03
Date: 2022-07-26
*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define BIND_PORT 7 // 端口號(hào)
#define MSG_LEN 102 // magic packet的長度
#define USAGE "wakeOnLan [broadcast IP] [MAC]n"
"Example: sudo ./wakeOnLan 192.168.1.255 00:e0:2b:69:00:03n"
/*****************************************************
* Function: int is_IP(char *IP)
* Description: 檢查IP是否為一個(gè)合法的IPv4
* Return: 1 合法的IPv4
* 0 非法的IPv4
*****************************************************/
int is_IP(char *IP) {
int a, b, c, d;
char e;
if (4 == sscanf(IP, "%d.%d.%d.%d%c", &a, &b, &c, &d, &e)) {
if (a >= 0 && a < 256 &&
b >= 0 && b < 256 &&
c >= 0 && c < 256 &&
d >= 0 && d < 256) {
return 1;
}
}
return 0;
}
/*************************************************************************
* Function: int is_MAC(char *mac_str, char *mac)
* Description: 檢查MAC是否為合法的MAC格式,如果合法,將其轉(zhuǎn)換成6組數(shù)字放到mac中
*
* Return: 1 合法的MAC,char *mac中為轉(zhuǎn)換后的mac地址
* 0 非法的MAC
*************************************************************************/
int is_MAC(char *mac_str, char *mac) {
int temp[6];
char e;
int i;
if (6 == sscanf(mac_str, "%x:%x:%x:%x:%x:%x%c", &temp[0], &temp[1], &temp[2], &temp[3], &temp[4], &temp[5], &e)) {
for (i = 0; i < 6; ++i) {
if (temp[i] < 0 || temp[i] > 255) break;
}
if (i == 6) {
for (i = 0; i < 6; ++i) {
mac[i] = temp[i];
}
return 1;
}
}
return 0;
}
// ===================主程序================================================
int main(int argc, char *argv[]) {
struct sockaddr_in sin;
char *broadcast_ip;
char wol_msg[MSG_LEN + 2]; // magic packet
char mac[6];
int socket_fd;
int i, j;
int on = 1;
// 檢查參數(shù)數(shù)量
if (argc < 3) {
// 參數(shù)數(shù)量不對
printf("Incorrect input parameters.n");
printf(USAGE);
return -1;
}
// 檢查第一個(gè)參數(shù)是否為一個(gè)IP地址
if (! is_IP(argv[1])) {
printf("%s is an invalid IPv4.n", argv[1]);
printf(USAGE);
return -1;
}
// 檢查第二個(gè)參數(shù)是否為一個(gè)MAC地址
if (! is_MAC(argv[2], mac)) {
printf("%s is an invalid MAC address.n", argv[2]);
printf(USAGE);
return -1;
}
printf("MAC is %02x:%02x:%02x:%02x:%02x:%02xn", mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]);
// 建立socket
socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_fd < 0) {
printf("Can not set up socket. Program exits(%d).n", socket_fd);
return -1;
}
// 為IP地址分配內(nèi)存
broadcast_ip = calloc(strlen(argv[1]) + 1, sizeof(char));
if (! broadcast_ip) {
printf("Can not allocate memory. Program exitsn");
return -1;
}
// 允許在 socket_fd 上發(fā)送廣播消息
setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
memset((void *)&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(broadcast_ip); // 廣播IP
sin.sin_port = htons(BIND_PORT); // 端口號(hào)
// 6個(gè) ff
for (i = 0; i < 6; i++) {
wol_msg[i] = 0xFF;
}
// 16遍 mac 地址
for (j = 0; j < 16; j++) {
for (i = 0; i < 6; i++) {
wol_msg[6 + j * 6 + i] = mac[i];
}
}
// 發(fā)送 magic packet
sendto(socket_fd, &wol_msg, MSG_LEN, 0, (struct sockaddr *)&sin, sizeof(sin));
close(socket_fd);
// 釋放前面分配的內(nèi)存空間
free(broadcast_ip);
printf("Magic Packet has been sended.n");
return 0;
}
這個(gè)程序本身比較簡單,注釋比較完整,也沒什么好說明的。
交叉編譯
交叉編譯當(dāng)然也是在開發(fā)機(jī)上完成;
假定我們把上面這個(gè)源程序放在 ~/wake_on_lan/ 目錄下,下面是交叉編譯的過程:
cd ~/wake_on_lan
source /bin/a5.sh
arm-openwrt-linux-gcc -Wall wakeOnLan.c -o wakeOnLan
交叉編譯的過程難免出錯(cuò),請自行排錯(cuò);
交叉編譯完成的程序是不能在開發(fā)機(jī)上運(yùn)行的,需要拷貝到 openwrt 上才能運(yùn)行,仍然假定 openwrt 的 IP 地址為:192.168.2.100,則可以這樣將已經(jīng)編譯好的程序拷貝到 openwrt 上,操作在開發(fā)機(jī)上完成
cd ~/wake_on_lan
scp wakeOnLan [email protected]:/root/
在 openwrt 上運(yùn)行程序
用 ssh 登錄到 openwrt,登錄后應(yīng)該就在 /root/ 目錄下,因?yàn)?nbsp;/root/ 是 root 用戶的 HOME 目錄,然后運(yùn)行程序
ssh [email protected]
./wakeOnLan 192.168.2.255 00:e0:2b:69:00:03
第一個(gè)參數(shù)是局域網(wǎng)上的廣播 IP,第二個(gè)參數(shù)是要被遠(yuǎn)程喚醒的機(jī)器的 MAC 地址,請根據(jù)你的具體情況進(jìn)行修改
在我的機(jī)器上運(yùn)行效果是這樣的
圖14:在openwrt上運(yùn)行wakeOnLan
軟件調(diào)試
- 這個(gè)程序的調(diào)試主要是確保程序能夠正確地發(fā)出 magic packet,需要在局域網(wǎng)上找另一臺(tái)機(jī)器進(jìn)行數(shù)據(jù)包的監(jiān)聽,這臺(tái)監(jiān)聽的機(jī)器既可以運(yùn)行 windows 也可以運(yùn)行 Linux,最好是使用準(zhǔn)備遠(yuǎn)程喚醒機(jī)器作為監(jiān)聽的機(jī)器,我們以一臺(tái)運(yùn)行 ubuntu 的機(jī)器為例來完成調(diào)試
使用 ubuntu 下的工具 tcpdump 來進(jìn)行數(shù)據(jù)包的監(jiān)聽,tcpdump 必須在 root 權(quán)限下運(yùn)行;
首先在監(jiān)聽機(jī)器上運(yùn)行 tcpdump
sudo tcpdump -vv -x udp port 7
這行命令的意思就是監(jiān)聽 udp 端口 7 的數(shù)據(jù)包,-vv 的意思是顯示詳細(xì)的信息,-x 的意思是按照 16 進(jìn)制顯示,這兩個(gè)參數(shù)也可以寫成 -vvx
在 openwrt 上運(yùn)行 wakeOnLan
./wakeOnLan 192.168.2.255 00:e0:2b:69:00:03
其中的廣播 IP 和 MAC 地址請按照實(shí)際情況填寫
正常情況下,在監(jiān)聽機(jī)器上可以看到程序發(fā)出的 Magic Packet,仔細(xì)看一下這個(gè)數(shù)據(jù)包的格式是否正確
在我的環(huán)境下,看到的輸出如下:
圖15:偵聽Magic Packet
其中黃線標(biāo)識(shí)的部分是 IP 頭,占 20 個(gè)字節(jié);綠線標(biāo)識(shí)的部分是 UDP 頭,占 8 個(gè)字節(jié),剩下的就是 Magic Packet;
如果你正常地偵聽到了一個(gè)完整且正確的 Magic Packet,那么恭喜你,就快要成功了;
如果你使用 windows 偵聽 Magic Packet 數(shù)據(jù)包,通常使用著名的 Wireshark。
局域網(wǎng)內(nèi)的網(wǎng)絡(luò)喚醒測試
- 把需要網(wǎng)絡(luò)喚醒的機(jī)器關(guān)機(jī),如果需要設(shè)置 BIOS,要先設(shè)置好 BIOS 再關(guān)機(jī);
- 使用局域網(wǎng)內(nèi)的另一臺(tái)電腦 ssh 登錄 openwrt,或者使用手機(jī)登錄 openwrt,如果使用手機(jī)登錄,需要在手機(jī)上安裝一個(gè)終端 App,我使用安卓手機(jī),安裝的 app 叫 ConnetcBot,推薦大家試一下;
在 openwrt 運(yùn)行你編寫的程序 wakeOnLan,如果你運(yùn)氣好,你那臺(tái)剛剛關(guān)機(jī)的電腦應(yīng)該被默默的打開了電源
但是,通常都沒有那么好的運(yùn)氣,那么可能的問題如下:
1. 程序里的廣播 IP 是不是正確?
2. Magic Packet 中的 MAC 地址是否正確?
3. Magic Packet 的格式是否正確?
4. 被喚醒的機(jī)器是否與 openwrt 在同一個(gè)網(wǎng)段?
5. 路由器是否限制了 UDP 的端口 7?
6. 如果以上都沒有問題,恐怕只能懷疑你那臺(tái)要被喚醒的機(jī)器不支持網(wǎng)絡(luò)喚醒,或者 BIOS 設(shè)置的不正確
內(nèi)網(wǎng)穿透
做到現(xiàn)在這樣,我們已經(jīng)完成了大部分工作,下面唯一要做的是如何從外網(wǎng)上訪問到這臺(tái) openwrt 的設(shè)備,這就是前面說過的 內(nèi)網(wǎng)穿透;
搞內(nèi)網(wǎng)穿透是需要在互聯(lián)網(wǎng)上有一臺(tái)服務(wù)器(VPS)的,可以是那種很便宜性能很弱的VPS,因?yàn)槲覀儾桓蓜e的事,就是轉(zhuǎn)發(fā)一下數(shù)據(jù)而已;
我自己使用的是一臺(tái)俄羅斯的 VPS,價(jià)格只有 US$13.04/年,512M內(nèi)存,5G的SSD,運(yùn)行 ubuntu 20.04,雖然配置低,但是用起來感覺還是不錯(cuò)的,我很樂意推薦給大家:
- [俄羅斯VPS](https://justhost.ru/?ref=149230)
前面說過我使用的內(nèi)網(wǎng)穿透的工具是一個(gè)開源項(xiàng)目,叫做 frp,項(xiàng)目地址如下:
- [frp內(nèi)網(wǎng)穿透項(xiàng)目](https://github.com/fatedier/frp)
- 該項(xiàng)目有中文文檔,大家可以按照文檔下載適當(dāng)?shù)?nbsp;release,其服務(wù)器軟件 frps 運(yùn)行在服務(wù)器(VPS)上,客戶端 frpc 運(yùn)行在 openwrt 上;
- 通常服務(wù)器端軟件都是 64 位的 X86 架構(gòu),比較容易搞定;
- 要注意的是,要看清楚運(yùn)行 openwrt 的設(shè)備是什么架構(gòu),是 32 位的還是 64 位的,比如本文中的設(shè)備 CPU 為 S805,就是一個(gè) 32 位的 arm 架構(gòu),否則你下載的客戶端軟件可能無法運(yùn)行;
- 這個(gè)軟件的設(shè)置還是要費(fèi)一些功夫,請認(rèn)真閱讀該項(xiàng)目的文檔,并參考其范例;這里我給出我的實(shí)例
服務(wù)器端配置文件:frps.ini,其中的 xxx.xxx.xxx.xxx 請按照實(shí)際情況設(shè)置,frp_log_path 請指向?qū)嶋H存放 frp 日志的目錄
[common]
bind_addr = xxx.xxx.xxx.xxx
bind_port = 57000
bind_udp_port = 57001
kcp_bind_port = 57000
proxy_bind_addr = xxx.xxx.xxx.xxx
vhost_http_port = 58080
vhost_https_port = 58443
log_file = /frp_log_path/frps.log
log_level = info
log_max_days = 3
disable_log_color = false
detailed_errors_to_client = true
authentication_method = token
authenticate_heartbeats = false
authenticate_new_work_conns = false
token = skyline.admin
oidc_client_id =
oidc_client_secret =
oidc_audience =
oidc_token_endpoint_url =
allow_ports = 58000-59000,50000-53000
max_pool_count = 15
max_ports_per_client = 0
tls_only = false
subdomain_host = frps.com
tcp_mux = true
客戶端配置文件:frpc.ini,其中的 xxx.xxx.xxx.xxx 請按照實(shí)際情況設(shè)置;openwrt.aaa.com 是一個(gè) A 記錄指向 VPS 的域名(子域名),也要根據(jù)實(shí)際情況進(jìn)行設(shè)置
[common]
server_addr=xxx.xxx.xxx.xxx
server_port=57000
log_file=/tmp/frpc.log
log_level=info
log_max_days=3
disable_log_color=false
token=skyline.admin
pool_count=5
tcp_mux=true
user=whowin
login_fail_exit=false
protocol=tcp
tls_enable=falset
DNS_server=8.8.8.8
admin_addr=127.0.0.1
admin_port=7400
admin_user=skyline
admin_pwd=admin
[ssh]
type=tcp
local_ip=192.168.2.100
local_port=22
use_encryption=false
use_compression=false
custom_domain=openwrt.aaa.com
remote_port=52998
health_check_type=tcp
health_check_timeout_s=3
health_check_max_failed=10
health_check_interval_s=30
[openwrt_web]
type=http
local_ip=192.168.2.100
local_port=80
use_encryption=false
use_compression=true
custom_domains=openwrt.aaa.com
header_X-From-Where=frp
health_check_type=http
health_check_url=/
health_check_interval_s=90
health_check_max_failed=3
health_check_timeout_s=3
啟動(dòng) VPS 上的 frps,path_to 指向?qū)嶋H路徑
/path_to/frps -c /path_to/frps.ini &
啟動(dòng) openwrt 上的 frpc,path_to 指向?qū)嶋H路徑
/path_to/frpc -c /path_to/frpc.ini &
如遇問題,強(qiáng)烈建議認(rèn)真查看 frps.log 和 frpc.log;
正常情況下,現(xiàn)在你已經(jīng)可以在互聯(lián)網(wǎng)上通過 frp 訪問你家里的 openwrt 了,像這樣:
ssh [email protected] -p 52998
其中:xxx.xxx.xxx.xxx 為 VPS 的 IP 地址,52998 是在 frpc.ini 中設(shè)置的端口號(hào),也可以設(shè)置一個(gè)域名指向 VPS 的 IP,比如設(shè)置 vps.aaa.com 的 A 記錄指向 VPS,則可以這樣登錄 openwrt:
ssh [email protected] -p 52998
同樣,按照上面的設(shè)置,如果要訪問 openwrt 的 web 界面,在瀏覽器上輸入:openwrt.aaa.com:58080 即可,58080 是在 frps.ini 中設(shè)置的一個(gè)端口號(hào);
特別要注意的是,要在 VPS 上設(shè)置好防火墻,放開可能用到的端口,否則 frp 將無法正常工作。
遠(yuǎn)程開機(jī)測試
測試遠(yuǎn)程開機(jī)使用的電腦、平板或者手機(jī),不能連接到家里的 wifi 上,建議使用手機(jī),關(guān)掉 wifi,打開 ConnectBot
設(shè)置連接如下:
圖16:在ConnectBot上設(shè)置連接
通過 *frp* 連接到 *openwrt*
圖17:在ConnectBot上連接openwrt
在 openwrt 上運(yùn)行 wakeOnLan
圖18:在openwrt上運(yùn)行wakeOnLan
正常情況下,MAC 地址指定的機(jī)器應(yīng)該已經(jīng)開機(jī)了;為了運(yùn)行方便,你可以在 openwrt 上寫一個(gè) shell 腳本,免得每次都要輸入 IP 地址和 MAC 地址,像下面這樣,注意,openwrt 下的 shell 是 ash
$ cat wakeOnLan.sh
#!/bin/ash
/home/whowin/wakeOnLan 192.168.2.255 00:e0:2b:68:00:03
$
7. 后記
前面提過,Magic Packet 可以在任意端口發(fā)送,通常使用端口 7 或 9,我們這個(gè)例子使用端口 7 發(fā)送,讀者可以試一下從其他端口發(fā)送,比如端口 1234;
實(shí)際上,openwrt 是有現(xiàn)成的網(wǎng)絡(luò)喚醒模塊的,可以在 openwrt 的 web 界面上搜索 luci-app-wol,安裝這個(gè)軟件的同時(shí),還會(huì)安裝一個(gè) etherwake 的軟件,安裝好以后,可以使用類似 etherwake -b AA:BB:CC:DD:EE:FF 的命令發(fā)送 Magic Packet 喚醒指定的機(jī)器,也可以通過 web 界面喚醒指定的電腦
圖19:在web界面上發(fā)送Magic Packet
至此,我們的這個(gè)項(xiàng)目就算做完了,我們基本上經(jīng)歷了一個(gè)嵌入式開發(fā)的全過程,只是每一個(gè)階段都比較簡單而已,嵌入式開發(fā),貌似復(fù)雜,其實(shí)并沒有想象的那么難;
在嵌入式項(xiàng)目的設(shè)計(jì)階段,我們需要有足夠的知識(shí)儲(chǔ)備以便為我們的需求提出最合理的方案,在這個(gè)項(xiàng)目的設(shè)計(jì)中,正是由于我們儲(chǔ)備了 Magic Packet 和反向代理的知識(shí),才可以為我們的需求提出這樣一個(gè)軟硬件結(jié)合的方案;
我們這個(gè)項(xiàng)目基本沒有硬件開發(fā),但是通常情況下,嵌入式開發(fā)的軟件工程師是需要參與到硬件開發(fā)中去的,包括芯片方案的選擇等都要參與意見,以便設(shè)計(jì)開發(fā)的硬件產(chǎn)品在今后的軟件開發(fā)中可以比較順利,所以嵌入式軟件工程師同樣要具備閱讀芯片的 datasheet 的能力和看懂硬件原理圖的能力,好的嵌入式軟件工程師也可以擁有非常不錯(cuò)的焊接技能;
嵌入式開發(fā)的最關(guān)鍵的地方還是軟件開發(fā),但比普通的軟件開發(fā)所要了解的知識(shí)要多很多,比如在我們這個(gè)項(xiàng)目中,我們必須要知道我們所用的硬件的 CPU 是什么,甚至要知道這個(gè) CPU 的架構(gòu),否則,我們無法為這個(gè)硬件構(gòu)建正確的操作系統(tǒng),也無法在開發(fā)機(jī)上為這個(gè)硬件構(gòu)建正確的交叉編譯環(huán)境;
如果你喜歡做嵌入式開發(fā),希望這篇文章能給予你幫助。
最后歡迎訪問我的博客:https://whowin.gitee.io






