Cocoa 網絡框架:
Cocoa 網絡框架有三層,最底層的是基於 BSD socket庫,然后是 Cocoa 中基於 C 的 CFNetwork,最上面一層是 Cocoa 中 Bonjour。通常我們無需與 socket 打交道,我們會使用經 Cocoa 封裝的 CFNetwork 和 Bonjour 來完成大多數工作。注:cocoa 很多組件都有兩種實現,一種是基於 C 的以 CF 開頭的類(CF=Core Foundation),這是比較底層的;另一種是基於 Obj-C 的以 NS 開頭的類(NS=Next Step),這種類抽象層次更高,易於使用。對於網絡框架也一樣。Bonjour 中 NSNetService 也有對應的 CFNetService,NSInputStream 有對應的 CFInputStream。
Bonjour 簡介:
Bonjour(法語中的你好)是一種能夠自動查詢接入網絡中的設備或應用程序的協議。Bonjour 抽象掉 ip 和 port 的概念,讓我們聚焦於更容易為人類思維理解的 service。通過 Bonjour,一個應用程序 publish 一個網絡服務 service,然后網絡中的其他程序就能自動發現這個 service,從而可以向這個 service 查詢其 ip 和 port,然后通過獲得的 ip 和 port 建立 socket 鏈接進行通信。通常我們是通過 NSNetService 和 NSNetServiceBrowser 來使用 Bonjour 的,前者用於建立與發布 service,后者用於監聽查詢網絡上的 service。
同步與異步操作:
大多數網絡操作是阻塞模式的,比如鏈接的建立,等待接收數據,或發送數據給網絡另一端。因此如果我們不進行異步處理的話,當在進行網絡通信時,我們的 UI 機會被阻塞。有兩種辦法來處理阻塞問題:啟用多個線程或更有效地利用當前線程。在這個例子中,我們使用后一種辦法,我們通過 cocoa 提供的 run loop 來做這個事情,其工作原理是:將網絡消息當作普通的事件丟到當前的 run loop 中,從而我們可以異步處理它們。
Run loops 簡介:
run loop 是 thread 中的消息處理循環,有事件來則處理,無事件則啥也不做。cocoa 中的 run loop 可以處理用戶 UI 消息,網絡連接消息,timer 消息等。我們也可以添加其他的消息來源,如 socket 和 stream,從而讓 run loop 也可以處理它們。
通過NSNetService發布socket
// 初始化服務,指定服務的域,類型,名稱和端 NSNetService *service = [[NSNetService alloc] initWithDomain:@"local." type:@"_WE._tcp" name:@"WE_iPhone" port:0]; service.delegate = self; NSData *data = [NSNetService dataFromTXTRecordDictionary:@{@"node":@"http://www.we.com"}]; [service setTXTRecordData:data]; self.service = service; // 發布 [service publish];
如果需要發布一個服務,需要在發布服務之前准備好服務並啟動它。不過NSNetService的publish方法並不依賴它所發布的服務,不管服務是否准備好,是否啟動,NSNetService的publish都可以成功將服務發布出去,只不過服務發布出去后其它使用這個服務的客戶端會發現這個發布出來的服務是個無效服務。
發布服務成功之后,開始查找。
NSNetServiceDelegate代理
/* * 即將發布 */ - (void)netServiceWillPublish:(NSNetService *)sender { NSLog(@"---------------netServiceWillPublish"); } /* * 發布成功 */ - (void)netServiceDidPublish:(NSNetService *)sender {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(concurrentQueue, ^{ NSRunLoop *mainRunLoop = [NSRunLoop currentRunLoop]; NSNetServiceBrowser* browser = [[NSNetServiceBrowser alloc] init]; browser.delegate = self; [browser scheduleInRunLoop:mainRunLoop forMode:NSRunLoopCommonModes]; [browser searchForServicesOfType:@"_WE._tcp" inDomain:@"local."]; [mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:30]]; });
}
/* * 發布失敗,返回錯誤 */ - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary<NSString *, NSNumber *> *)errorDict { } /* * 開始解析 */ - (void)netServiceWillResolve:(NSNetService *)sender { NSLog(@"--------------netServiceWillResolve"); } /* * 解析成功 */ - (void)netServiceDidResolveAddress:(NSNetService *)sender { NSLog(@"netServiceDidResolveAddress---------=%@ =%@ =%@",service.name,service.addresses,service.hostName); } /* * 解析服務失敗,解析出錯 */ - (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary<NSString *, NSNumber *> *)errorDict { NSLog(@"------------netService didNotResolve =%@",errorDict); } /* * 停止服務 */ - (void)netServiceDidStop:(NSNetService *)sender { NSLog(@"--------------netServiceDidStop"); } /* * 服務數據更新 */ - (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data { NSLog(@"--------------netService didUpdateTXTRecordData"); } /* * 連接成功輸出流和輸入流 */ - (void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream { NSLog(@"--------------netService didAcceptConnectionWithInputStream"); }
使用NSNetService框架中的NSNetServiceBrowser類去發現本地服務
接着使用NSNetServiceBrowser實例的searchForServicesOfType方法查找服務,方法中可以指定需要查找的服務類型和查找的域。以下樣例查找“local.”域中的“_WE._tcp”服務
// 發現本地服務 NSNetServiceBrowser* browser = [[NSNetServiceBrowser alloc] init]; browser.delegate = self; [browser scheduleInRunLoop:mainRunLoop forMode:NSRunLoopCommonModes]; [browser searchForServicesOfType:@"_WE._tcp" inDomain:@"local."]; [mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:30]];
在“NSNetServiceBrowserDelegate”的以下方法中響應“didFindService”事件,就是找到服務的事件。其中的netService參數就是找到的服務,在netService參數中可以得到服務地址,服務主機名等信息
實現NSNetServiceBrowser實例的代理
/* * 即將查找服務 */ - (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser { NSLog(@"-----------------netServiceBrowserWillSearch"); } /* * 停止查找服務 */ - (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser { NSLog(@"-----------------netServiceBrowserDidStopSearch"); } /* * 查找服務失敗 */ - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary<NSString *, NSNumber *> *)errorDict { NSLog(@"----------------netServiceBrowser didNotSearch"); } /* * 發現域名服務 */ - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindDomain:(NSString *)domainString moreComing:(BOOL)moreComing { NSLog(@"---------------netServiceBrowser didFindDomain"); } /* * 發現客戶端服務 */ - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing { NSLog(@"didFindService---------=%@ =%@ =%@",service.name,service.addresses,service.hostName);
[aNetService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; aNetService.delegate = self; [aNetService resolveWithTimeout:6]; CFRunLoopRun();
} /* * 域名服務移除 */ - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveDomain:(NSString *)domainString moreComing:(BOOL)moreComing { NSLog(@"---------------netServiceBrowser didRemoveDomain"); } /* * 客戶端服務移除 */ - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing { NSLog(@"---------------netServiceBrowser didRemoveService"); }
在發現客戶端里,設置NSNetService代理,在解析成功-(void)netServiceDidResolveAddress;方法里解析
可以拿到名稱、類型、域、主機名、ip地址
/* Returns the name of the discovered or published service. */ @property (readonly, copy) NSString *name; /* Returns the type of the discovered or published service. */ @property (readonly, copy) NSString *type; /* Returns the domain of the discovered or published service. */ @property (readonly, copy) NSString *domain; /* Returns the DNS host name of the computer hosting the discovered or published service. If a successful resolve has not yet occurred, this method will return nil. */ @property (nullable, readonly, copy) NSString *hostName; /* The addresses of the service. This is an NSArray of NSData instances, each of which contains a single struct sockaddr suitable for use with connect(2). In the event that no addresses are resolved for the service or the service has not yet been resolved, an empty NSArray is returned. */ @property (nullable, readonly, copy) NSArray<NSData *> *addresses;
當解析成功客戶端的IP、端口、類型、域之后,就可以通過建立連接進行通信。