mDNS 在 Android Studio 的應用

春麗 S.T.E.M.
15 min readDec 22, 2023

--

目錄
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 裡,透過心跳響應(PINGPONG)來知道 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),如果要說 HomeHomeKit (現在是 Google Home 跟 Apple Home 了,因為 Homekit 改名了)最大的差異就是一個是透過 API(外網),一個是內網發現操作。

不過這段主要是帶出 Android 陣營其實是較晚才給出 mDNS 的解決方案的,即便到現在…..可能都還不能算完善。

回目錄

繼續閱讀|回目錄

ESP32 Web Server

關於 mDNS 的實現,我其實用 ESP32 架了一個簡單的網站,在前篇文章中,ESP32 是 STA 模式,必須取得內網 IP 位址,方能連到 STA 模式下架起的網頁,以控制 LED 燈光的明滅。

然而,在物聯網中,ESP32 做為 AP 模式還有一個可實現的作用,透過 WebServer 來設定模組的 SSID 跟 Password,先看到下方:

左方是 Arduino 函式庫中預設的 AP 模式下的內網 IP 位址,右方是 ESP32 開啟 mDNS 服務後,便可以透過 hostname.local 去連上,不過這裡需注意,你的 Wi-Fi 必須先連上 ESP32 提供的 AP,如下:

關於如何透過 Web Server 去設定 ESP32 的 Wi-Fi SSID 跟 Password 有機會再跟大家分享。

好了!下面就直接進到 Android Studio 如何設置 Android 裝置為 Web Server 並開啟 mDNS 服務。

回目錄

繼續閱讀|回目錄

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:

--

--

春麗 S.T.E.M.
春麗 S.T.E.M.

Written by 春麗 S.T.E.M.

Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage, against the dying of the light.

No responses yet