原文鏈接: http://fighting300.com...
之前一直考慮在local現(xiàn)場怎么與別的用戶通信,后來陸續(xù)了解了蘋果的Bonjour×勺埃現(xiàn)在簡單寫一篇Bonjour的入門介紹。
Bonjour介紹
bonjour其實來自法語透葛,是你好的意思续镇。而Bonjour服務是蘋果公司發(fā)布的一個基于ZEROCONF工作組(IETF下屬小組)的工作,用于實現(xiàn)零配置網(wǎng)絡聯(lián)網(wǎng)的解決方案。Bonjour是基于IP層協(xié)議的,簡單來說,就是一套解決方案,能夠不需要復雜的配置,即可互相發(fā)現(xiàn)彼此的解決方案陵究∶咭可以用它來輕松探測并連接到相同網(wǎng)絡中的其他設備,并與別的智能硬件進行交互或者其他操作铜邮。典型的Bonjour應用有Remote仪召、AirPrint等寨蹋。
為了實現(xiàn)簡單配置網(wǎng)絡,Bonjour做了以下三點:
尋址(分配IP地址給主機)
一個在網(wǎng)絡中的設備需要有一個自己的IP扔茅。有了IP地址,我們才能基于IP協(xié)議進行通信已旧。對于IPV6標準,IP協(xié)議已經(jīng)包括了自動尋找IP地址的功能。但是目前仍然普遍使用的IPV4不包含本地鏈路尋址功能召娜。而Bonjour會在本地網(wǎng)絡選擇一個隨機的IP地址進行測試,如果已經(jīng)被占用,則繼續(xù)挑選另外一個地址运褪,直到選到可用的IP地址。命名(使用名字而不是IP地址來代表主機)
Bonjour還實現(xiàn)了命名和解析功能玖瘸,保證了我們服務的名字在本地網(wǎng)絡是唯一的,并且把別人對我們名字的查詢指向正確的IP地址和端口秸讹,而不是以IP地址這樣不易讀的方式來作為服務的標志。
而且Bonjour在系統(tǒng)級別上添加了一個mDNSResponder服務來處理請求和發(fā)送回復,從系統(tǒng)級層面上處理,我們就無需在應用內(nèi)自己監(jiān)聽和返回IP地址,只需到系統(tǒng)內(nèi)注冊服務即可雅倒。減少了我們應用的工作量和提高了穩(wěn)定性璃诀。服務搜索(自動在網(wǎng)絡搜索服務)
Bonjour可以只需指定所需服務的類型,即可收到本地網(wǎng)絡上可用的設備列表蔑匣。設備在本地網(wǎng)絡發(fā)出請求,說我需要"XXX"類型的服務,例如:我要打印機服務文虏。所有打印機服務的設備回應自己的名字。
Bonjour API
Cocoa中網(wǎng)絡框架有三層殖演,最底層的是基于 BSD socket庫氧秘,然后是 Cocoa 中基于 C 的 CFNetwork,最上面一層是 Cocoa 中 Bonjour趴久。通常我們無需與 socket 打交道丸相,我們會使用經(jīng) Cocoa 封裝的 CFNetwork 和 Bonjour 來完成大多數(shù)工作。
cocoa 很多組件都有兩種實現(xiàn)彼棍,一種是基于 C 的以 CF 開頭的類(CF=Core Foundation)灭忠,這是比較底層的;另一種是基于 Obj-C 的以 NS 開頭的類(NS=Next Step)座硕,這種類抽象層次更高弛作,易于使用。對于網(wǎng)絡框架也一樣华匾。Bonjour 中 NSNetService 也有對應的 CFNetService映琳,NSInputStream 有對應的 CFInputStream。
通過 Bonjour蜘拉,一個應用程序publish一個網(wǎng)絡服務 service萨西,然后網(wǎng)絡中的其他程序就能自動發(fā)現(xiàn)這個 service,從而可以向這個 service 查詢其 ip 和 port旭旭,然后通過獲得的 ip 和 port 建立 socket 鏈接進行通信谎脯。通常我們是通過 NSNetService 和 NSNetServiceBrowser 來使用 Bonjour 的,前者用于建立與發(fā)布 service持寄,后者用于監(jiān)聽查詢網(wǎng)絡上的 service源梭。
建立連接
簡單來說娱俺,建立Bonjour連接一般需要三個步驟,即服務端發(fā)布服務废麻、客戶端瀏覽服務矢否、客戶端/服務端交互。
客戶端發(fā)布服務
首先通過NetService
對象初始化服務脑溢,指定服務的域僵朗、類型、名稱和端口屑彻,在同一網(wǎng)絡中验庙,服務類型名必須唯一,這樣才能精準定位服務社牲。
Bonjour操作也需要異步進行粪薛,以免長時間阻礙主線程,所以我們將發(fā)布任務交給當前run loop去調(diào)度搏恤。
func setupService(){
let service = NetService.init(domain: "local.", type: "_dragon._tcp", name: "dragon", port: 2333)
service.schedule(in: RunLoop.current, forMode: .commonModes)
service.delegate = self
let dictData = "http://fighting300.github.io".data(using: String.Encoding.utf8)
let data = NetService.data(fromTXTRecord: ["node":dictData!])
service.setTXTRecord(data)
service.publish()
self.service = service
}
另外需要實現(xiàn)NetService協(xié)議NetServiceDelegate
的代理方法跟蹤服務發(fā)布信息违寿。
func netServiceWillPublish(_ sender: NetService) {
print("----netServiceWillPublish")
}
func netService(_ sender: NetService, didNotPublish errorDict: [String : NSNumber]) {
print("----netService didNotPublish")
}
客戶端瀏覽服務
服務發(fā)布成功后,會在代理方法中接受到發(fā)布的消息熟空,這時候要在客戶端通過NetServiceBrowser
對象來瀏覽本地的服務藤巢,并展示本地網(wǎng)絡中可用的服務。
可以通過searchForServices
方法指定需要查找的服務類型和查找的域息罗,然后運行在mainRunLoop中掂咒。
func netServiceDidPublish(_ sender: NetService) {
print("----netService didPublish")
let browser = NetServiceBrowser()
browser.delegate = self
browser.searchForServices(ofType: "_WE._tcp", inDomain: "local.")
browser.schedule(in: RunLoop.current, forMode: .commonModes)
RunLoop.current.run(until: Date.init(timeIntervalSinceNow: 300))
}
同時實現(xiàn)NetServiceBrowser的代理NetServiceBrowserDelegate
方法netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool)
來處理相應服務的解析。當前代碼實例中沒有選擇服務的服務迈喉,直接對掃描到的服務來做解析。
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
print("----netServiceBrowser didFind", service.domain, service.type, service.name, service.port,service)
// 在界面選擇對應的service
self.service = service
service.delegate = self
service.resolve(withTimeout: 5)
}
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
print("----netServiceBrowser didRemove")
}
客戶端/服務端交互
最后挨摸,可以在NetService代理的解析方法里func netServiceDidResolveAddress(_ sender: NetService)
,拿到名稱得运、類型、域澈圈、主機名和ip地址等信息彬檀。
func netServiceWillResolve(_ sender: NetService) {
print("----netService willResolve")
}
func netServiceDidResolveAddress(_ sender: NetService) {
print("----netService didResolveAddress", sender.name, sender.addresses, sender.hostName, sender.addresses?.first)
let data = sender.txtRecordData()
let dict = NetService.dictionary(fromTXTRecord: data!)
let info = String.init(data: dict["node"]!, encoding: String.Encoding.utf8)
print("mac info = ",info);
}
func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) {
print("----netService didNotResolve ", errorDict)
}
func netServiceDidStop(_ sender: NetService) {
print("----netServiceDidStop")
}
func netService(_ sender: NetService, didUpdateTXTRecord data: Data) {
print("----netService didUpdateTXTRecord")
}
func netService(_ sender: NetService, didAcceptConnectionWith inputStream: InputStream, outputStream: OutputStream) {
print("----netService didAcceptConnectionWith")
}
// MARK: util
func IPFrom(data: Data) -> String {
return ""
}
之后依靠以上獲取的信息,需要通過Socket/Streams建立連接來進行通信努潘,本篇文章不對這部分做更多的介紹坤学,后續(xù)有時間再補充完整。