mDNS 在 Android Studio 的應用
目錄
1. mDNS
2. Google、Apple
2-1 Android
3. ESP32 Web Server
4. Android Studio
4-1 內網 IP
⦿ mDNS
⦿ Google、Apple
⦿ Android
⦿ ESP32 Web Server
⦿ Android Studio
⦿ 內網 IP
mDNS
打開瀏覽器,連接到伺服器的時候,我們 Key 在網址列的通常不是 IP 位址,是吧?
如果要連上 Google,我們就會鍵入 google.com
,假如你用的不是 Chrome,我們可以 ping 看看 google.com
這個網址,如下:
如果我們用 142.251.42.238
這個 IP 去連,一樣會出現 google.com。
這是因為 DNS
伺服器已經幫我們解析了網址或 IP,想像這個伺服器有一張 Table,裡面都是網址與該網址對應的 IP,如此一來,我們就不用記得各家網站的 IP 位址,是吧?這就是 DNS(域名系統)。
通常 DNS 是外網操作適用的情況,在內網是否也可以這樣操作?當然可以,如果內網有一個伺服器專門處理 IP 與對應的名稱,就可以按表操課,以內網來說為了 DNS 專門弄一個伺服器並不是太方便的一件事,也相當不切實際,我們通常會是看想要做什麼,能夠用什麼方法實現,那麼,如果沒有 DNS 伺服器,還有沒有簡單的解決辦法呢?有,就是 mDNS。
我們熟悉的網路連接是基於 TCP/IP
的,他是一個套組,跨了數據鏈路層、傳輸層、網路層、應用層等等,因為你不會只用傳輸層的 protocol 就做到我們要的網路應用。
而 mDNS 是基於 UDP
的內網廣播,告訴內網的其他設備自己的 hostname,以及有什麼服務,下面我們來看看 mDNS 的應用。
繼續閱讀|回目錄
Google、Apple
在 IoT 領域中,Google 有 Home
,Apple 有 HomeKit
,我們可以透過 Home 與 HomeKit 來操控設備,其實背後實現的原理各不相同,但操控都是主要目的。
當你的手機跟家裡可連網的電視機都連上同一個內網,如果電視機有支援 Airplay
的話,你就可以把手機畫面投上這台電視機,如果要進行這種程度的連接,通常還是得知道輸出設備的內網 IP 位址,但這又不是連到一個網站那麼麻煩,所以 Apple 設計出 Bonjour
了,Bonjour 跟之前文章中,在 MQTT 裡,透過心跳響應(PING
、PONG
)來知道 Client 與 Server 是否保持連接概念相似,你的手機為 Client,目標為電視機,Client 透過 Bonjour 這項技術去找到電視機,就是用前段所說的 UDP 去廣播,即是應用了 mDNS。
若是將廣播當作一對所有,即是對內網所有設備發訊息,那麼,mDNS 就不能稱為廣播了,不過我們還是先當它是廣播吧,Multicast DNS,是一種 zeroconf(零配置),與域名系統實現同樣操作,但應用了不同原理,在內網進行的域名解析,Port 是 5353,而地址是 224.0.0.251,當啟用 mDNS 服務的設備會發訊息到 224.0.0.251:5353
,告訴它自己的 IP 是多少,hostname 叫做什麼,於是知道 hostname 的設備就會反過來去跟 224.0.0.251:5353
要目標設備的 IP,以此作為連接基底,去找到目標設備提供的服務,例如 Airplay,就可以開始投射螢幕到電視機上,這就是 DNS-SD(Service Discovvery)。
而使用 MAC 就有內建 Bonjour,如果是 Linux,你就可以用 Avahi,下面來看看 MAC 怎麼操作,打開 Terminal,鍵入如下:
~ % dns-sd -B _http._tcp
得到如下:
Timestamp A/R Flags if Domain Service Type Instance Name
13:33:32.233 Add 3 10 local. _http._tcp. PumpRD
13:33:32.233 Add 3 13 local. _http._tcp. Deskjet 2540 series
13:33:32.233 Add 3 13 local. _http._tcp. Deskjet 3510 series
13:33:32.233 Add 2 13 local. _http._tcp. MyServiceName
這些是透過 Bonjour 發現的服務,多數都是印表機
,而 MyServiceName 是此次文章中透過 Android Studio 所寫的 WebServer
,並讓它開啟 mDNS。
接著鍵入如下:
~% dns-sd -L MyServiceName _http._tcp
得到如下:
13:34:00.961 MyServiceName._http._tcp.local. can be reached at Android_d93fd81d8b4c43aca1937844d35ce73e.local.:8081 (interface 13)
這意思是可以透過發現的服務,進一步去看它能夠做什麼,在這裡就是告訴我可以透過後面那一串加上 8081 Port,連上 WebServer。
Android
說完 Apple 的 Airplay,再來說說 Google 這邊的事,Android 陣營相應的投射螢幕,最顯而易見的例子就是手機的 Youtube 或 Netflix 影片投射到 Chromecast 上,如下:
如果內網發現過可投射的設備,就不需要再進行一次配對了,Google 這邊使用的是 DIAL(Discovery And Launch),DIAL 是基於 UPnP 的協議,UPnP 做的事是讓內網接受 DHCP 的設備(不需固定 IP)的服務可以馬上啟用,再挖深一點,發現服務是 UPnP 中的 SSDP(Simple Service Discovery Protocol)協議,而 SSDP 也是基於 UDP 的。
不過 DIAL 是以前的事了,Chromecast 現在用的也是 mDNS,如果再往後退一步的話,我們熟知的應用程式,Google Home 與 Apple HomeKit,在應用層的協議分別是 Weave 跟 HAP(HomeKit Accessory Protocol),如果要說 Home
跟 HomeKit
(現在是 Google Home 跟 Apple Home 了,因為 Homekit 改名了)最大的差異就是一個是透過 API(外網),一個是內網發現操作。
不過這段主要是帶出 Android 陣營其實是較晚才給出 mDNS 的解決方案的,即便到現在…..可能都還不能算完善。
繼續閱讀|回目錄
ESP32 Web Server
關於 mDNS 的實現,我其實用 ESP32 架了一個簡單的網站,在前篇文章中,ESP32 是 STA 模式,必須取得內網 IP 位址,方能連到 STA 模式下架起的網頁,以控制 LED 燈光的明滅。
然而,在物聯網中,ESP32 做為 AP 模式還有一個可實現的作用,透過 WebServer 來設定模組的 SSID 跟 Password,先看到下方:
Android Studio
打開 build.gradle(Module :app)
後加入:
implementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation 'org.jmdns:jmdns:3.5.5'
接著 Sync Gradle。
在 AndroidManifest.xml 中加入適當權限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
接著,為 WebServer 跟 mDNS 各寫一個檔案,如下:
MyService 是為了建立 Web Server,AnotherService 是為了啟用 mDNS 服務。
import fi.iki.elonen.NanoHTTPD
class MyWebServer(port: Int) : NanoHTTPD(port) {
override fun serve(session: IHTTPSession): Response {
return newFixedLengthResponse("Hello from Android Test Server!")
}
}
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
object AnotherService {
fun registerService(context: Context,
serviceName: String,
serviceType: String,
port: Int) {
val nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
val serviceInfo = NsdServiceInfo().apply {
this.serviceName = serviceName
this.serviceType = serviceType
setPort(port)
}
nsdManager.registerService(
serviceInfo,
NsdManager.PROTOCOL_DNS_SD,
object : NsdManager.RegistrationListener {
override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) {
// Service registered
}
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
// Registration failed
}
override fun onServiceUnregistered(serviceInfo: NsdServiceInfo) {
// Service unregistered
}
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
// Unregistration failed
}
}
)
}
}
第一個 class 提供了一個簡單 Web Page 的回應,第二個 Object 裡的 function,必須先註冊這個服務,提供服務名稱、服務類別以及 Port,裡面則是用 NsdManager 來實現。
NSD(新增網路服務探索),終於在 2021 年 11 月加入 Android 的服務中,如此便可以用來解析 hostname.local
這樣的內網網址。
透過 nsdmanager 來註冊服務,將 ServiceInfo 放入,以及採用 DNS_SD,後面則是狀態處理和控制。
在 Activity 中:
private lateinit var server: MyWebServer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
server = MyWebServer(8081)
try {
server.start()
} catch (error: IOException) {
error.printStackTrace()
}
AnotherService.registerService(this, "MyServiceName", "_http._tcp.", 8081)
}
override fun onDestroy() {
super.onDestroy()
server.stop()
}
就可以簡單使用,將 Web Server 設定為 8081 Port,接著繼續把 8081 Port mapping 到 Web Server。
其中 MyServiceName 就是 mDNS 服務的名稱,_http._tcp.
就是 Service Type,這表示 MyServiceName 這個 Service 是基於傳輸層 TCP,應用層 HTTP 來實現,一點也沒錯!因為它是 Web Server。
內網 IP
首先連上 Wi-Fi,查詢你的裝置取得的內網 IP,鍵入瀏覽器。
不過這下麻煩了 NSD
並沒有提供設定 hostname 的方法,我們要怎麼確定 mDNS 服務有沒有啟用呢?
沒關係,剛才有教大家 dns-sd -B _http._tcp
這個指令,不過這邊我使用 Xcode 加入 Bonjour 的專案,透過 Debug Area 來檢查,會找到如下:
Resolved hostname: Android_d93fd81d8b4c43aca1937844d35ce73e.local.
Resolved service: <NSNetService 0x600003d01f00> local. _http._tcp. MyServiceName 8081
IPv4 address: 192.168.23.55
IPv6 address: fe80::f8f9:eaff:fe46:5da4
將這個 hostname 加上 8081 Port 試試看,得到如下:
沒問題,mDNS 正常啟用,只不過 Android 設備的 hostname 是一直在變動的,這反而失去了 mDNS 的意義,Google 呀 Google,真是猜不透你呢!
順帶一提,在 Arduino 函式庫中的 mDNS 服務,目前開出來是沒有 IPv6 而只有 IPv4 的。
好了!
這次就分享到這,感謝您的閱讀。
繼續閱讀|回目錄
Reference: