IOS網絡開發實戰(一)

 

1 局域網羣聊軟件

1.1 問題

UDP協議將獨立的數據包從一臺計算機傳輸到另一臺計算機,可是並不保證接受方可以接收到該數據包,也不保證接收方所接收到的數據和發送方所發送的數據在內容和順序上是徹底一致的。html

UDP廣播就是創建於UDP協議上的數據傳輸,當網絡中的某一臺計算機向交換機或路由發送一個廣播數據時,交換機或路由則會將此廣播數據發送到其節點下的全部接收者。本案例使用第三方Socket編程框架AsyncUdpSocket框架,基於UDP廣播實現一個局域網羣聊軟件,一個基於UD廣播的聊天室程序,不須要任何的服務端程序作數據中轉,如圖-1所示:編程

圖-1數組

1.2 方案

首先建立一個SingleViewApplication應用,導入AsyncUdpSocket框架。在Storyboard中搭建聊天界面,場景左邊拖放一個大的TableView控件用於展現聊天記錄,設置tag值爲0。右邊拖放一個小的TableView控件,用於展現參與羣聊的用戶IP,tag值設置爲1,並將這兩個TableView控件關聯成ViewController的輸出口屬性myChatRecordTV和usersList。服務器

在場景的下方拖放一個Textfield控件用於輸入接受用戶輸入的聊天信息端,該控件上方是一個選擇全部人的按鈕,右邊是一個發送按鈕,將Textfield控件關聯成ViewController的輸出口屬性myTF,將按鈕分別關聯成動做方法sendAll:和send:。網絡

接下來首先實現聊天功能,在ViewController中定義一個屬性AsyncUdpSocket類型的udpSocket。在viewDidLoad方法中建立服務器端udpSocket,將端口號設置爲8000,委託對象設置爲self,並設置廣播屬性。併發

而後再定義兩個NSMutableArray類型的屬性users和chatRecord,分別用於記錄參與聊天用戶的IP和聊天記錄,在viewDidLoad方法中進行初始化。而後實現兩個TableView的協議方法,展現數據。app

接下來定義一個NSString類型的currentHost屬性,該屬性記錄用戶所選擇的聊天的對象,若該屬性爲空則表示聊天對象是全部用戶,而後實現sendAll:方法和send:方法。框架

當用戶點擊輸入框準備輸入的時候會彈出鍵盤,這時候須要將整個聊天界面上移,這裏使用註冊鍵盤通知的方式調整self.view的座標位置。在viewDidLoad方法中註冊鍵盤即將出現和鍵盤即將消失兩個通知,分別實現對應的方法便可。最後在viewWillDisappear:方法中註銷通知。socket

sendAll:方法中直接將屬性currentHost設置爲nil便可,send:方法中將根據是否存在currentHost進行消息發送,若是currentHost存在則將消息發送給currentHost,若是不存在則發送給255.255.255.255,即發送給全員,並更新myChatRecordTV的顯示內容。tcp

最後實現AsyncUdpSocketDelegate的協議方法onUdpSocket:didReceiveData:withTag:fromHost:port:,讀取數據和在線用戶,分別更新顯示聊天記錄內容和用戶列表。

1.3 步驟

實現此案例須要按照以下步驟進行。

步驟一:搭建聊天界面

首先建立一個SingleViewApplication應用,導入AsyncUdpSocket框架。在Storyboard中搭建聊天界面,場景左邊拖放一個大的TableView控件用於展現聊天記錄,設置tag值爲0。右邊拖放一個小的TableView控件,用於展現參與羣聊的用戶IP,tag值設置爲1,並將這兩個TableView控件關聯成ViewController的輸出口屬性myChatRecordTV和usersList,代碼以下所示:

  1. @interface ViewController ()
  2. @property (strong, nonatomic) IBOutlet UITableView *myChatRecordTV;
  3. @property (strong, nonatomic) IBOutlet UITableView *usersList;
  4. @end

而後在場景的下方拖放一個Textfield控件用於輸入接受用戶輸入的聊天信息端,該控件上方是一個選擇全部人的按鈕,右邊是一個發送按鈕,將Textfield控件關聯成ViewController的輸出口屬性myTF,將按鈕分別關聯成動做方法sendAll:和send:,如圖-2所示:

圖-2

步驟二:實現聊天功能

首先實現聊天功能,在ViewController中定義一個屬性AsyncUdpSocket類型的udpSocket。在viewDidLoad方法中建立服務器端udpSocket,將端口號設置爲8000,委託對象設置爲self,並設置廣播屬性,代碼以下所示:

  1. //建立服務器端
  2. self.udpSocket [[AsyncUdpSocket alloc]initWithDelegate:self];
  3. [self.udpSocket bindToPort:8000 error:nil];
  4. [self.udpSocket enableBroadcast:YES error:nil];
  5. [self.udpSocket joinMulticastGroup:@"192.168.1.104" error:nil];
  6. //持續接受
  7. [self.udpSocket receiveWithTimeout:-1 tag:0];

再定義兩個NSMutableArray類型的屬性users和chatRecord,分別用於記錄參與聊天用戶的IP和聊天記錄,在viewDidLoad方法中進行初始化,代碼以下所示:

  1. @property (strong,nonatomic) NSMutableArray *users;
  2. @property (strong,nonatomic) NSMutableArray *chatRecord;
  3. (void)viewDidLoad {
  4. [super viewDidLoad];
  5. //初始化數組
  6. self.users [NSMutableArray array];
  7. self.chatRecord [NSMutableArray array];
  8. }

接下來實現兩個TableView的協議方法,展現數據,代碼以下所示:

  1. //表視圖協議方法
  2. (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  3. if (tableView.tag == 0{
  4. return self.chatRecord.count;
  5. }else {
  6. return self.users.count;
  7. }
  8. }
  9. (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  10. static NSString *identifier = @"Cell";
  11. UITableViewCell *cell [tableView dequeueReusableCellWithIdentifier:identifier];
  12. if (!cell{
  13. cell [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
  14. }
  15. switch (tableView.tag{
  16. case 0:
  17. cell.textLabel.text = self.chatRecord[indexPath.row];
  18. [cell.textLabel setFont:[UIFont systemFontOfSize:12]];
  19. break;
  20. case 1:
  21. cell.textLabel.text = self.users[indexPath.row];
  22. [cell.textLabel setFont:[UIFont systemFontOfSize:10]];
  23. break;
  24. }
  25. return cell;
  26. }

接下來定義一個NSString類型的currentHost屬性,該屬性記錄用戶所選擇的聊天的對象,若該屬性爲空則表示聊天對象是全部用戶。在選擇一個聊天對象時進行賦值,代碼以下所示:

  1. -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  2. self.currentHost [self.users objectAtIndex:indexPath.row];
  3. }

而後實現sendAll:方法和send:方法。當用戶點擊輸入框準備輸入的時候會彈出鍵盤,這時候須要將整個聊天界面上移,這裏使用註冊鍵盤通知的方式調整self.view的座標位置。在viewDidLoad方法中註冊鍵盤即將出現和鍵盤即將消失兩個通知,分別實現對應的方法便可,代碼以下所示:

  1. //註冊鍵盤信息
  2. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardShow:) name:UIKeyboardWillShowNotification object:nil];
  3. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardHidden:) name:UIKeyboardWillHideNotification object:nil];
  4. //當鍵盤即將出現的時候self.view根據鍵盤的高度上移
  5. -(void)keyboardShow:(NSNotification*) notification {
  6. CGRect keyboardRect [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
  7. NSTimeInterval duration =
  8. [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  9. UIViewAnimationOptions options =
  10. [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
  11. duration -= 0.1;
  12. [UIView animateWithDuration:duration
  13. delay:0
  14. options:options
  15. animations:
  16. ^{
  17. self.view.center CGPointMake(p.x, p.y-keyboardRect.size.height);
  18. } completion:nil];
  19. }
  20. //當鍵盤即將消失的時候self.view恢復到原來的位置
  21. -(void)keyboardHidden:(NSNotification*)notification {
  22. NSTimeInterval duration =
  23. [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  24. UIViewAnimationOptions options =
  25. [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
  26. duration -= 0.1;
  27. [UIView animateWithDuration:duration
  28. delay:0
  29. options:options
  30. animations:
  31. ^{
  32. self.view.center CGPointMake(p.x, p.y);
  33. } completion:nil];
  34. }

最後須要在viewWillDisappear:方法中註銷通知,代碼以下所示:

  1. //註銷鍵盤通知
  2. -(void)viewWillDisappear:(BOOL)animated {
  3. [super viewWillDisappear:animated];
  4. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
  5. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
  6. }

sendAll:方法中直接將屬性currentHost設置爲nil便可,代碼以下所示:

  1. (IBAction)sendAll:(UIButton *)sender {
  2. self.currentHost = nil;
  3. }

send:方法中將根據是否存在currentHost進行消息發送,若是currentHost存在則將消息發送給currentHost,若是不存在則發送給255.255.255.255,即發送給全員,並更新myChatRecordTV的顯示內容,代碼以下所示:

  1. (IBAction)send:(UIButton *)sender {
  2. [self.myTF resignFirstResponder];
  3. if (self.currentHost{
  4. NSString *chat [NSString stringWithFormat:@"我saidto%@:%@",self.currentHost,self.myTF.text];
  5. NSString *chatSend [NSString stringWithFormat:@"%@",self.myTF.text];
  6. [self.udpSocket sendData:[chatSend dataUsingEncoding:NSUTF8StringEncoding] toHost:self.currentHost port:8000 withTimeout:-1 tag:0];
  7. [self.chatRecord addObject:chat];
  8. }else {
  9. [self.udpSocket sendData:[self.myTF.text dataUsingEncoding:NSUTF8StringEncoding] toHost:@"255.255.255.255" port:8000 withTimeout:-1 tag:0];
  10. NSString *chat [NSString stringWithFormat:@"我saidToAll:%@",self.myTF.text];
  11. [self.chatRecord addObject:chat];
  12. }
  13. [self.myChatRecordTV reloadData];
  14. [self.udpSocket receiveWithTimeout:-1 tag:0];
  15. }

最後實現AsyncUdpSocketDelegate的協議方法onUdpSocket:didReceiveData: withTag:fromHost:port:,讀取數據和在線用戶,分別更新顯示聊天記錄內容和用戶列表,代碼以下所示:

  1. -(BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port {
  2. NSString *str [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
  3. if([str isEqualToString:@"誰在線"]) {
  4. [self.udpSocket sendData:[@"我在線" dataUsingEncoding:NSUTF8StringEncoding] toHost:@"255.255.255.255" port:8000 withTimeout:-1 tag:0];
  5. NSString *chat [NSString stringWithFormat:@"%@:我在線",host];
  6. [self.chatRecord addObject:chat];
  7. [self.myChatRecordTV reloadData];
  8. }else if([str isEqualToString:@"我在線"]) {
  9. //更新聊天用戶列表
  10. NSLog(@"%@",host);
  11. [self.users addObject:host];
  12. [self.usersList reloadData];
  13. }else {
  14. NSString *chat [NSString stringWithFormat:@"%@saidToMe:%@",host,str];
  15. [self.chatRecord addObject:chat];
  16. [self.myChatRecordTV reloadData];
  17. }
  18. return YES;
  19. }

1.4 完整代碼

本案例中,ViewController.m文件中的完整代碼以下所示:

 
  1. #import "ViewController.h"
  2. #import "AsyncUdpSocket.h"
  3. #import "AppDelegate.h"
  4. @interface ViewController ()<UITableViewDataSource,UITableViewDelegate,UITextFieldDelegate>{
  5. CGPoint p;
  6. }
  7. @property (strong, nonatomic) IBOutlet UITextField *myTF;
  8. @property (strong, nonatomic) IBOutlet UITableView *myChatRecordTV;
  9. @property (strong, nonatomic) IBOutlet UITableView *usersList;
  10. @property (strong,nonatomic) AsyncUdpSocket *udpSocket;
  11. @property (strong,nonatomic) NSMutableArray *users;
  12. @property (strong,nonatomic) NSMutableArray *chatRecord;
  13. @property (nonatomic,retain) NSString *currentHost;
  14. @end
  15. @implementation ViewController
  16. (void)viewDidLoad
  17. {
  18. [super viewDidLoad];
  19. //初始化數組
  20. self.users [NSMutableArray array];
  21. self.chatRecord [NSMutableArray array];
  22. //記錄self.view的中心位置
  23. = self.view.center;
  24. //建立服務器端
  25. self.udpSocket [[AsyncUdpSocket alloc]initWithDelegate:self];
  26. [self.udpSocket bindToPort:8000 error:nil];
  27. [self.udpSocket enableBroadcast:YES error:nil];
  28. [self.udpSocket joinMulticastGroup:@"192.168.1.104" error:nil];
  29. //持續接受
  30. [self.udpSocket receiveWithTimeout:-1 tag:0];
  31. //註冊鍵盤信息
  32. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardShow:) name:UIKeyboardWillShowNotification object:nil];
  33. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardHidden:) name:UIKeyboardWillHideNotification object:nil];
  34. }
  35. //當鍵盤即將出現的時候self.view根據鍵盤的高度上移
  36. -(void)keyboardShow:(NSNotification*) notification {
  37. CGRect keyboardRect [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
  38. NSTimeInterval duration =
  39. [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  40. UIViewAnimationOptions options =
  41. [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
  42. duration -= 0.1;
  43. [UIView animateWithDuration:duration
  44. delay:0
  45. options:options
  46. animations:
  47. ^{
  48. self.view.center CGPointMake(p.x, p.y-keyboardRect.size.height);
  49. } completion:nil];
  50. }
  51. //當鍵盤即將消失的時候self.view恢復到原來的位置
  52. -(void)keyboardHidden:(NSNotification*)notification {
  53. NSTimeInterval duration =
  54. [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  55. UIViewAnimationOptions options =
  56. [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
  57. duration -= 0.1;
  58. [UIView animateWithDuration:duration
  59. delay:0
  60. options:options
  61. animations:
  62. ^{
  63. self.view.center CGPointMake(p.x, p.y);
  64. } completion:nil];
  65. }
  66. //註銷鍵盤通知
  67. -(void)viewWillDisappear:(BOOL)animated {
  68. [super viewWillDisappear:animated];
  69. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
  70. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
  71. }
  72. -(BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port {
  73. NSString *str [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
  74. if([str isEqualToString:@"誰在線"]) {
  75. [self.udpSocket sendData:[@"我在線" dataUsingEncoding:NSUTF8StringEncoding] toHost:@"255.255.255.255" port:8000 withTimeout:-1 tag:0];
  76. NSString *chat [NSString stringWithFormat:@"%@:我在線",host];
  77. [self.chatRecord addObject:chat];
  78. [self.myChatRecordTV reloadData];
  79. }else if([str isEqualToString:@"我在線"]) {
  80. //更新聊天用戶列表
  81. NSLog(@"%@",host);
  82. [self.users addObject:host];
  83. [self.usersList reloadData];
  84. }else {
  85. NSString *chat [NSString stringWithFormat:@"%@saidToMe:%@",host,str];
  86. [self.chatRecord addObject:chat];
  87. [self.myChatRecordTV reloadData];
  88. }
  89. return YES;
  90. }
  91. //表視圖協議方法
  92. (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  93. if (tableView.tag == 0{
  94. return self.chatRecord.count;
  95. }else {
  96. return self.users.count;
  97. }
  98. }
  99. (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  100. static NSString *identifier = @"Cell";
  101. UITableViewCell *cell [tableView dequeueReusableCellWithIdentifier:identifier];
  102. if (!cell{
  103. cell [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
  104. }
  105. switch (tableView.tag{
  106. case 0:
  107. cell.textLabel.text = self.chatRecord[indexPath.row];
  108. [cell.textLabel setFont:[UIFont systemFontOfSize:12]];
  109. break;
  110. case 1:
  111. cell.textLabel.text = self.users[indexPath.row];
  112. [cell.textLabel setFont:[UIFont systemFontOfSize:10]];
  113. break;
  114. }
  115. return cell;
  116. }
  117. -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  118. self.currentHost [self.users objectAtIndex:indexPath.row];
  119. }
  120. -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  121. return 25;
  122. }
  123. (IBAction)done:(UITextField *)sender {
  124. [self.myTF resignFirstResponder];
  125. }
  126. (IBAction)send:(UIButton *)sender {
  127. [self.myTF resignFirstResponder];
  128. if (self.currentHost{
  129. NSString *chat [NSString stringWithFormat:@"我saidto%@:%@",self.currentHost,self.myTF.text];
  130. NSString *chatSend [NSString stringWithFormat:@"%@",self.myTF.text];
  131. [self.udpSocket sendData:[chatSend dataUsingEncoding:NSUTF8StringEncoding] toHost:self.currentHost port:8000 withTimeout:-1 tag:0];
  132. [self.chatRecord addObject:chat];
  133. }else {
  134. [self.udpSocket sendData:[self.myTF.text dataUsingEncoding:NSUTF8StringEncoding] toHost:@"255.255.255.255" port:8000 withTimeout:-1 tag:0];
  135. NSString *chat [NSString stringWithFormat:@"我saidToAll:%@",self.myTF.text];
  136. [self.chatRecord addObject:chat];
  137. }
  138. [self.myChatRecordTV reloadData];
  139. [self.udpSocket receiveWithTimeout:-1 tag:0];
  140. }
  141. (IBAction)sendAll:(UIButton *)sender {
  142. self.currentHost = nil;
  143. }
  144. -(BOOL)prefersStatusBarHidden {
  145. return YES;
  146. }
  147. @end
 

2 基於服務發現的Socket通訊

2.1 問題

Socket須要指定服務器的端口和IP地址,在有些狀況下得到服務器的這些信息是很困難的,蘋果公司提供一種零配置服務發現協議,命名爲Bonjour,使應用在沒必要指定服務器端口和IP地址就能夠動態發現。

蘋果提供的Bonjour編程的相關類主要是兩個,NSNetService和NSNetServiceBrowser,以及和這兩個類配套的協議NSNetServiceDelegate和NSNetServiceBrowserDelegate。本案例經過Bonjour服務發現實現Socket通訊。

2.2 方案

首先建立服務器端應用NetServiceServer,用Xcode建立一個Command Line命令行項目,發現服務並不包含Socket服務器的啓動,所以啓動Socket服務器的代碼須要先編寫,服務器啓動後會得到動態端口,再把這個端口做爲參數傳遞給Bonjour發現服務,發佈成功創建Socket,本案例使用NSStream和CFStream實現服務器代碼。

接下來建立一個NetServiceServer類,幾乎所有的代碼都在該類中實現,在該類中定義兩個私有屬性,一個是NSNetService類型的service,用於發佈Bonjour服務並重寫setter方法進行初始化,另外一個屬性是short類型的port,用於記錄端口號。

其次啓動服務器,將啓動服務器的代碼封裝在setupServer方法,本案例使用NSStream和CFStream來實現服務器的啓動,而後在init方法中調用setupServer方法。

而後發佈服務,將發佈服務的代碼封裝在publishService方法中,並在init方法中調用。而後再實現協議方法netServiceDidPublish:,該方法在服務發佈結束後被調用,能夠經過該方法查看服務是否發佈成功。

最後在main函數中建立NetServiceServer實例對象,並調用CFRunLoopRun()函數,該函數能夠在當前線程啓動一個Runloop循環,使得服務器一直在運行狀態。

接下來建立客戶端應用NetServiceClient,使用Xcode建立一個SingleViewApplication應用,在Storyboard中搭建應用的界面,拖放兩個Button控件和一個Label控件,將Label關聯成TRViewController的輸出口屬性displayLabel,將兩個Button分別關聯成動做方法sendMessage和recvMessage,分別用於發送消息和接受消息。

建立NetServiceClient客戶端類,用於發現Bonjour服務,該類有一個NSMutableArray類型的屬性services用於記錄發現的服務對象,在.h文件中該屬性是隻讀的,在.m文件中該屬性是可讀可寫的。

另外還有一個私用屬性NSNetService類型的service,用於發現解析服務,在init方法中對以上兩個屬性進行初始化併發布服務,最後實現NSNetServiceDelegate協議相關方法。

而後完成ViewController類中的代碼,該類中沒有任何與服務發現相關的代碼,它從NetServiceClient類中得到輸入和輸出流對象,而後進行通訊就能夠了,這裏的讀寫數據流的操做一樣也是使用NSStream和CFStream類來實現。

在ViewController類中定義兩個私有屬性NSInputStream類型的inputStream,以及NSOutputStream類型的outputStream,分別用於記錄輸入流和輸出流,他們分別和服務器中的輸出流CFWriteStreamRef和輸入流CFReadStreamRef對應。

最後實現sendMessage和recvMessage方法,更新displayLabel的顯示。

2.3 步驟

實現此案例須要按照以下步驟進行。

步驟一:建立服務器端應用

首先建立服務器端應用NetServiceServer,用Xcode建立一個Command Line命令行項目,發現服務並不包含Socket服務器的啓動,所以啓動Socket服務器的代碼須要先編寫,服務器啓動後會得到動態端口,再把這個端口做爲參數傳遞給Bonjour發現服務,發佈成功創建Socket,本案例使用NSStream和CFStream實現服務器代碼,所以須要導入頭文件CoreFoundation.h,還須要包含頭文件sys/socket.h和netinet/in.h。

接下來建立一個NetServiceServer類,幾乎所有的代碼都在該類中實現,在該類中定義兩個私有屬性,一個是NSNetService類型的service,用於發佈Bonjour服務並重寫setter方法進行初始化,另外一個屬性是short類型的port,用於記錄端口號,代碼以下所示:

 
  1. @interface NetServiceServer () <NSNetServiceDelegate>
  2. @property (strong,nonatomic)NSNetService *service;
  3. @property (nonatomic) short port;
  4. @end
  5. //重寫setter方法初始化
  6. (NSNetService *)service
  7. {
  8. if (!_service{
  9. _service [[NSNetService alloc]initWithDomain:@"local." type:@"_tarenaipp._tcp." name:@"tarena" port:self.port];
  10. }
  11. return _service;
  12. }

其次啓動服務器,將啓動服務器的代碼封裝在setupServer方法,本案例使用NSStream和CFStream來實現服務器的啓動,代碼以下所示:

 
  1. (void)setupServer
  2. {
  3. CFSocketContext CTX {};
  4. //建立Socket,其實就是指針
  5. CFSocketRef serverSocket;
  6. //設置回調函數
  7. serverSocket CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, AcceptCallBack&CTX);
  8. if(serverSocket == NULL){
  9. NSLog(@"socket建立失敗");
  10. return;
  11. }
  12. //設置一些socket的屬性
  13. //布爾值類型, 能夠重複使用一個已經使用過的地址和端口
  14. int yes 1;
  15. setsockopt(CFSocketGetNative(serverSocket), SOL_SOCKET, SO_REUSEADDR(void*)&yessizeof(yes));
  16. //設置地址
  17. struct sockaddr_in addr {};
  18. //設置IPv4
  19. addr.sin_family = PF_INET;
  20. //內核分配,本機地址,htonl函數將無符號短整型數轉換成網絡字節序
  21. addr.sin_addr.s_addr htonl(INADDR_ANY);
  22. //端口號設置爲0
  23. addr.sin_port 0;
  24. addr.sin_len sizeof(addr);
  25. //將struct sockaddr_in ==> CFDataRef,從指定字節緩衝區複試一個不可變的CFData對象
  26. CFDataRef address CFDataCreate(kCFAllocatorDefault(UInt8*)&addrsizeof(addr));
  27. //設置Socket
  28. if(CFSocketSetAddress(serverSocket, address!= kCFSocketSuccess){
  29. NSLog(@"綁定失敗");
  30. return;
  31. }
  32. NSLog(@"綁定成功");
  33. //在Bonjour廣播時須要port
  34. NSData *socketAddressActualData (__bridge NSData *)CFSocketCopyAddress(serverSocket);
  35. struct sockaddr_in socketAddressActual;
  36. memcpy(&socketAddressActual[socketAddressActualData bytes], [socketAddressActualData length]);
  37. self.port ntohs(socketAddressActual.sin_port);
  38. NSLog(@"ServerSocket監聽的端口號:%hu\n", self.port);
  39. //建立Run Loop Socket源
  40. CFRunLoopSourceRef sourceRef CFSocketCreateRunLoopSource(kCFAllocatorDefault, serverSocket0);
  41. //將socket源加入到Run Loop中
  42. CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
  43. CFRelease(sourceRef);
  44. }

實現Socket的回調函數,並在init方法中調用setupServer方法,代碼以下所示:

 
  1. //在init方法中調用setupServer
  2. (instancetype)init
  3. {
  4. self [super init];
  5. if(self){
  6. [self setupServer];
  7. }
  8. return self;
  9. }
  10. #pragma mark - 回調函數
  11. //CFSocket回調函數, 有客戶端鏈接上來時調用
  12. void AcceptCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef addressconst void *data, void *info)
  13. {
  14. NSLog(@"....");
  15. CFReadStreamRef readStream = NULL;
  16. CFWriteStreamRef writeStream = NULL;
  17. //若是回調類型是kCFSocketAcceptCallBack, data就是CFSocketNativeHandle類型的指針,指向生成的socket
  18. CFSocketNativeHandle sock *(CFSocketNativeHandle*)data;
  19. //建立讀寫socket流 readStream, writeStream
  20. CFStreamCreatePairWithSocket(kCFAllocatorDefault, sock&readStream&writeStream);
  21. if(!readStream || !writeStream){
  22. NSLog(@"建立socket的讀寫流失敗.");
  23. close(sock);
  24. return;
  25. }
  26. //註冊讀寫回調函數
  27. CFStreamClientContext streamCTX {};
  28. CFReadStreamSetClient(readStream, kCFStreamEventHasBytesAvailable, ReadStreamClientCallBack&streamCTX);
  29. CFWriteStreamSetClient(writeStream, kCFStreamEventCanAcceptBytes, WriteStreamClientCallBack&streamCTX);
  30. //加入循環
  31. CFReadStreamScheduleWithRunLoop(readStreamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  32. CFWriteStreamScheduleWithRunLoop(writeStreamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  33. //打開讀寫
  34. CFReadStreamOpen(readStream);
  35. CFWriteStreamOpen(writeStream);
  36. }
  37. //讀數據的回調函數, 讀取客戶端數據時調用
  38. void ReadStreamClientCallBack(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
  39. {
  40. if(stream){
  41. UInt8 buf[1024{};
  42. CFReadStreamRead(stream, bufsizeof(buf));
  43. NSLog(@"從客戶端讀到數據:%s", buf);
  44. CFReadStreamClose(stream);
  45. CFReadStreamUnscheduleFromRunLoop(streamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  46. }
  47. }
  48. //寫數據的回調函數,向客戶端寫出數據時調
  49. void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
  50. {
  51. if(stream){
  52. UInt8 buf[1024"嗨, 您好客戶端, 哈哈哈哈";
  53. CFWriteStreamWrite(stream, bufstrlen((const char*)buf)+1);
  54. CFWriteStreamClose(stream);
  55. CFWriteStreamUnscheduleFromRunLoop(streamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  56. }
  57. }

而後發佈服務,將發佈服務的代碼封裝在publishService方法中,該方法中將服務添加到Runloop循環,並設置委託對象發佈服務,最後在init方法中調用該方法,代碼以下所示:

  1. (void)publishService
  2. {
  3. //添加服務到當前的Run Loop
  4. [self.service scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  5. //設置委託對象
  6. self.service.delegate = self;
  7. //發佈服務
  8. [self.service publish];
  9. }
  10. (instancetype)init
  11. {
  12. self [super init];
  13. if(self){
  14. [self setupServer];
  15. [self publishService];
  16. }
  17. return self;
  18. }

接下來實現協議方法netServiceDidPublish:,該方法在服務發佈結束後被調用,能夠經過該方法查看服務是否發佈成功,代碼以下所示:

  1. #pragma mark - NSNetServiceDelegate
  2. (void)netServiceDidPublish:(NSNetService *)sender
  3. {
  4. NSLog(@"服務發佈結束");
  5. if([sender.name isEqualToString:@"tarena"]){
  6. // [sender getInputStream:<#(out NSInputStream *__strong *)#> outputStream:<#(out NSOutputStream *__strong *)#>];
  7. }
  8. }

最後在main函數中建立NetServiceServer實例對象,並調用CFRunLoopRun()函數,該函數能夠在當前線程啓動一個Runloop循環,使得服務器一直在運行狀態,代碼以下所示:

 
  1. int main(int argcconst char * argv[])
  2. {
  3. @autoreleasepool {
  4. NetServiceServer *server [[NetServiceServer alloc]init];
  5. CFRunLoopRun();
  6. server = nil
  7. }
  8. return 0;
  9. }

運行服務器端的應用,能夠看到在控制檯輸出以下結果,如圖-3所示:

圖-3

步驟二:建立客戶端應用

建立客戶端應用NetServiceClient,使用Xcode建立一個SingleViewApplication應用,在Storyboard中搭建應用的界面,拖放兩個Button控件和一個Label控件,將Label關聯成TRViewController的輸出口屬性displayLabel,將兩個Button分別關聯成動做方法sendMessage和recvMessage,分別用於發送消息和接受消息。

完成的Storyboard界面如圖-4所示:

圖-4

接下來建立NetServiceClient客戶端類,用於發現Bonjour服務,該類有一個NSMutableArray類型的屬性services用於記錄發現的服務對象,在.h文件中該屬性是隻讀的,在.m文件中該屬性是可讀可寫的。

另外還有一個私用屬性NSNetService類型的service,用於發現解析服務,代碼以下所示:

 
  1. //NetServiceClient.h
  2. @interface NetServiceClient : NSObject
  3. //發現的全部service
  4. @property (strong, nonatomic, readonly) NSMutableArray *services;
  5. @end
  6. //NetServiceClient.m
  7. #import "NetServiceClient.h"
  8. @interface NetServiceClient () <NSNetServiceDelegate>
  9. @property (strong, nonatomic, readwrite) NSMutableArray *services;
  10. @property (strong, nonatomic) NSNetService *service;
  11. @end

在init方法中對以上兩個屬性進行初始化併發布服務,最後實現NSNetServiceDelegate協議相關方法,代碼以下所示:

 
  1. (instancetype)init
  2. {
  3. self [super init];
  4. if (self{
  5. _services [[NSMutableArray alloc]init];
  6. _service [[NSNetService alloc]initWithDomain:@"local." type:@"_tarenaipp._tcp." name:@"tarena"];
  7. _service.delegate = self;
  8. //設置解析超時時間
  9. [_service resolveWithTimeout:1.0];
  10. }
  11. return self;
  12. }
  13. //NSNetServiceDelegate方法,解析成功調用如下方法
  14. (void)netServiceDidResolveAddress:(NSNetService *)sender
  15. {
  16. NSLog(@"發現Bonjour服務.");
  17. [self.services addObject:sender];
  18. }
  19. //錯誤處理,解析失敗調用如下方法
  20. (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
  21. {
  22. NSLog(@"%@", errorDict);
  23. }
  24. @end

而後完成ViewController類中的代碼,該類中沒有任何與服務發現相關的代碼,它從NetServiceClient類中得到輸入和輸出流對象,而後進行通訊就能夠了,所以它有一個NetServiceClient類型的屬性client。

這裏的讀寫數據流的操做一樣也是使用NSStream和CFStream類來實現。在ViewController類中定義兩個私有屬性NSInputStream類型的inputStream,以及NSOutputStream類型的outputStream,分別用於記錄輸入流和輸出流,他們分別和服務器中的輸出流CFWriteStreamRef和輸入流CFReadStreamRef對應,代碼以下所示:

 
  1. @interface TRViewController () <NSStreamDelegate>{
  2. //進行讀寫操做的標記 flag==0 寫 flag==1 讀
  3. int flag;
  4. }
  5. @property (weak, nonatomic) IBOutlet UILabel *displayLabel;
  6. @property (strong, nonatomic) NetServiceClient *client;
  7. @property (strong, nonatomic) NSInputStream *inputStream;
  8. @property (strong, nonatomic) NSOutputStream *outputStream;
  9. @end

讀寫操做代碼以下所示:

 
  1. (void)openStream
  2. {
  3. for (NSNetService *service in self.client.services{
  4. if ([@"tarena" isEqualToString:service.name]) {
  5. if(![service getInputStream:&_inputStream outputStream:&_outputStream]){
  6. NSLog(@"鏈接服務器失敗");
  7. return;
  8. }
  9. break;
  10. }
  11. }
  12. //使用輸入輸出流進行通訊
  13. self.inputStream.delegate = self;
  14. [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  15. [self.inputStream open];
  16. self.outputStream.delegate = self;
  17. [self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  18. [self.outputStream open];
  19. }
  20. #pragma mark - NSStreamDelegate
  21. (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
  22. {
  23. //進行讀寫操做 flag==0 寫 flag==1 讀
  24. switch (eventCode{
  25. case NSStreamEventNone:
  26. break;
  27. case NSStreamEventOpenCompleted:
  28. break;
  29. case NSStreamEventHasBytesAvailable://讀
  30. if(flag==&& aStream==self.inputStream){
  31. uint8_t buffer[1024{};
  32. if([self.inputStream hasBytesAvailable]){
  33. int len [self.inputStream read:buffer maxLength:sizeof(buffer)];
  34. if(len>0){
  35. NSString *string [NSString stringWithCString:(const char*)buffer encoding:NSUTF8StringEncoding];
  36. self.displayLabel.text [@"接收到數據:" stringByAppendingString:string];
  37. }
  38. }
  39. }
  40. break;
  41. case NSStreamEventHasSpaceAvailable:
  42. if(flag==&& aStream==self.outputStream){
  43. UInt8 buffer[] "Hello Server.";
  44. [self.outputStream write:buffer maxLength:strlen((const char*)buffer)+1];
  45. [self.outputStream close];
  46. }
  47. break;
  48. default:
  49. break;
  50. }
  51. }

最後實現sendMessage和recvMessage方法,更新displayLabel的顯示,代碼以下所示:

 
  1. (IBAction)sendMessage
  2. {
  3. flag 0;
  4. [self openStream];
  5. }
  6. (IBAction)recvMessage:(id)sender
  7. {
  8. flag 1;
  9. [self openStream];
  10. }

運行服務器端和客戶端程序,結果如圖-5所示:

圖-5

2.4 完整代碼

本案例中,服務器端應用中的NetServiceServer.m文件中的完整代碼以下所示:

 
  1. #import "NetServiceServer.h"
  2. #import <sys/socket.h>
  3. #import <netinet/in.h>
  4. #import <string.h>
  5. @interface NetServiceServer () <NSNetServiceDelegate>
  6. @property (strong,nonatomic)NSNetService *service;
  7. @property (nonatomic) short port;
  8. @end
  9. @implementation NetServiceServer
  10. (instancetype)init
  11. {
  12. self [super init];
  13. if(self){
  14. [self setupServer];
  15. [self publishService];
  16. }
  17. return self;
  18. }
  19. (void)setupServer
  20. {
  21. CFSocketContext CTX {};
  22. //建立Socket,其實就是指針
  23. CFSocketRef serverSocket;
  24. //設置回調函數
  25. serverSocket CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, AcceptCallBack&CTX);
  26. if(serverSocket == NULL){
  27. NSLog(@"socket建立失敗");
  28. return;
  29. }
  30. //設置一些socket的屬性
  31. //布爾值類型, 能夠重複使用一個已經使用過的地址和端口
  32. int yes 1;
  33. setsockopt(CFSocketGetNative(serverSocket), SOL_SOCKET, SO_REUSEADDR(void*)&yessizeof(yes));
  34. //設置地址
  35. struct sockaddr_in addr {};
  36. //設置IPv4
  37. addr.sin_family = PF_INET;
  38. //內核分配,本機地址,htonl函數將無符號短整型數轉換成網絡字節序
  39. addr.sin_addr.s_addr htonl(INADDR_ANY);
  40. //端口號設置爲0
  41. addr.sin_port 0;
  42. addr.sin_len sizeof(addr);
  43. //將struct sockaddr_in ==> CFDataRef,從指定字節緩衝區複試一個不可變的CFData對象
  44. CFDataRef address CFDataCreate(kCFAllocatorDefault(UInt8*)&addrsizeof(addr));
  45. //設置Socket
  46. if(CFSocketSetAddress(serverSocket, address!= kCFSocketSuccess){
  47. NSLog(@"綁定失敗");
  48. return;
  49. }
  50. NSLog(@"綁定成功");
  51. //在Bonjour廣播時須要port
  52. NSData *socketAddressActualData (__bridge NSData *)CFSocketCopyAddress(serverSocket);
  53. struct sockaddr_in socketAddressActual;
  54. memcpy(&socketAddressActual[socketAddressActualData bytes], [socketAddressActualData length]);
  55. self.port ntohs(socketAddressActual.sin_port);
  56. NSLog(@"ServerSocket監聽的端口號:%hu\n", self.port);
  57. //建立Run Loop Socket源
  58. CFRunLoopSourceRef sourceRef CFSocketCreateRunLoopSource(kCFAllocatorDefault, serverSocket0);
  59. //將socket源加入到Run Loop中
  60. CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
  61. CFRelease(sourceRef);
  62. }
  63. (NSNetService *)service
  64. {
  65. if (!_service{
  66. _service [[NSNetService alloc]initWithDomain:@"local." type:@"_tarenaipp._tcp." name:@"tarena" port:self.port];
  67. }
  68. return _service;
  69. }
  70. (void)publishService
  71. {
  72. //添加服務到當前的Run Loop
  73. [self.service scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  74. //設置委託對象
  75. self.service.delegate = self;
  76. //發佈服務
  77. [self.service publish];
  78. }
  79. #pragma mark - NSNetServiceDelegate
  80. (void)netServiceDidPublish:(NSNetService *)sender
  81. {
  82. NSLog(@"服務發佈結束");
  83. if([sender.name isEqualToString:@"tarena"]){
  84. // [sender getInputStream:<#(out NSInputStream *__strong *)#> outputStream:<#(out NSOutputStream *__strong *)#>];
  85. }
  86. }
  87. #pragma mark - 回調函數
  88. //CFSocket回調函數, 有客戶端鏈接上來時調用
  89. void AcceptCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef addressconst void *data, void *info)
  90. {
  91. NSLog(@"....");
  92. CFReadStreamRef readStream = NULL;
  93. CFWriteStreamRef writeStream = NULL;
  94. //若是回調類型是kCFSocketAcceptCallBack, data就是CFSocketNativeHandle類型的指針,指向生成的socket
  95. CFSocketNativeHandle sock *(CFSocketNativeHandle*)data;
  96. //建立讀寫socket流 readStream, writeStream
  97. CFStreamCreatePairWithSocket(kCFAllocatorDefault, sock&readStream&writeStream);
  98. if(!readStream || !writeStream){
  99. NSLog(@"建立socket的讀寫流失敗.");
  100. close(sock);
  101. return;
  102. }
  103. //註冊讀寫回調函數
  104. CFStreamClientContext streamCTX {};
  105. CFReadStreamSetClient(readStream, kCFStreamEventHasBytesAvailable, ReadStreamClientCallBack&streamCTX);
  106. CFWriteStreamSetClient(writeStream, kCFStreamEventCanAcceptBytes, WriteStreamClientCallBack&streamCTX);
  107. //加入循環
  108. CFReadStreamScheduleWithRunLoop(readStreamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  109. CFWriteStreamScheduleWithRunLoop(writeStreamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  110. //打開讀寫
  111. CFReadStreamOpen(readStream);
  112. CFWriteStreamOpen(writeStream);
  113. }
  114. //讀數據的回調函數, 讀取客戶端數據時調用
  115. void ReadStreamClientCallBack(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
  116. {
  117. if(stream){
  118. UInt8 buf[1024{};
  119. CFReadStreamRead(stream, bufsizeof(buf));
  120. NSLog(@"從客戶端讀到數據:%s", buf);
  121. CFReadStreamClose(stream);
  122. CFReadStreamUnscheduleFromRunLoop(streamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  123. }
  124. }
  125. //寫數據的回調函數, 向客戶端寫出數據時調
  126. void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
  127. {
  128. if(stream){
  129. UInt8 buf[1024"嗨, 您好客戶端, 哈哈哈哈";
  130. CFWriteStreamWrite(stream, bufstrlen((const char*)buf)+1);
  131. CFWriteStreamClose(stream);
  132. CFWriteStreamUnscheduleFromRunLoop(streamCFRunLoopGetCurrent(), kCFRunLoopCommonModes);
  133. }
  134. }
  135. @end
 

本案例中,服務器端應用中的main.m文件中的完整代碼以下所示:

 
  1. #import <Foundation/Foundation.h>
  2. #import "NetServiceServer.h"
  3. int main(int argcconst char * argv[])
  4. {
  5. @autoreleasepool {
  6. NetServiceServer *server [[NetServiceServer alloc]init];
  7. CFRunLoopRun();
  8. server = nil;
  9. }
  10. return 0;
  11. }

本案例中,客戶端應用中的ViewController.m文件中的完整代碼以下所示:

 
  1. #import "TRViewController.h"
  2. #import "NetServiceClient.h"
  3. @interface TRViewController () <NSStreamDelegate>{
  4. //進行讀寫操做的標記 flag==0 寫 flag==1 讀
  5. int flag;
  6. }
  7. @property (weak, nonatomic) IBOutlet UILabel *displayLabel;
  8. @property (strong, nonatomic) NetServiceClient *client;
  9. @property (strong, nonatomic) NSInputStream *inputStream;
  10. @property (strong, nonatomic) NSOutputStream *outputStream;
  11. @end
  12. @implementation TRViewController
  13. (NetServiceClient *)client
  14. {
  15. if(!_client)_client [[NetServiceClient alloc]init];
  16. return _client;
  17. }
  18. (IBAction)sendMessage
  19. {
  20. flag 0;
  21. [self openStream];
  22. }
  23. (IBAction)recvMessage:(id)sender
  24. {
  25. flag 1;
  26. [self openStream];
  27. }
  28. (void)openStream
  29. {
  30. for (NSNetService *service in self.client.services{
  31. if ([@"tarena" isEqualToString:service.name]) {
  32. if(![service getInputStream:&_inputStream outputStream:&_outputStream]){
  33. NSLog(@"鏈接服務器失敗");
  34. return;
  35. }
  36. break;
  37. }
  38. }
  39. //使用輸入輸出流進行通訊
  40. self.inputStream.delegate = self;
  41. [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  42. [self.inputStream open];
  43. self.outputStream.delegate = self;
  44. [self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  45. [self.outputStream open];
  46. }
  47. #pragma mark - NSStreamDelegate
  48. (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
  49. {
  50. //進行讀寫操做 flag==0 寫 flag==1 讀
  51. switch (eventCode{
  52. case NSStreamEventNone:
  53. break;
  54. case NSStreamEventOpenCompleted:
  55. break;
  56. case NSStreamEventHasBytesAvailable://讀
  57. if(flag==&& aStream==self.inputStream){
  58. uint8_t buffer[1024{};
  59. if([self.inputStream hasBytesAvailable]){
  60. int len [self.inputStream read:buffer maxLength:sizeof(buffer)];
  61. if(len>0){
  62. NSString *string [NSString stringWithCString:(const char*)buffer encoding:NSUTF8StringEncoding];
  63. self.displayLabel.text [@"接收到數據:" stringByAppendingString:string];
  64. }
  65. }
  66. }
  67. break;
  68. case NSStreamEventHasSpaceAvailable:
  69. if(flag==&& aStream==self.outputStream){
  70. UInt8 buffer[] "Hello Server.";
  71. [self.outputStream write:buffer maxLength:strlen((const char*)buffer)+1];
  72. [self.outputStream close];
  73. }
  74. break;
  75. default:
  76. break;
  77. }
  78. }
  79. @end
 

本案例中,客戶端應用中的NetServiceClient.h文件中的完整代碼以下所示:

 
  1. #import <Foundation/Foundation.h>
  2. @interface NetServiceClient : NSObject
  3. //發現的全部service
  4. @property (strong, nonatomic, readonly) NSMutableArray *services;
  5. @end
 

本案例中,客戶端應用中的NetServiceClient.m文件中的完整代碼以下所示:

 
  1. #import "NetServiceClient.h"
  2. @interface NetServiceClient () <NSNetServiceDelegate>
  3. @property (strong, nonatomic, readwrite) NSMutableArray *services;
  4. @property (strong, nonatomic) NSNetService *service;
  5. @end
  6. @implementation NetServiceClient
  7. (instancetype)init
  8. {
  9. self [super init];
  10. if (self{
  11. _services [[NSMutableArray alloc]init];
  12. _service [[NSNetService alloc]initWithDomain:@"local." type:@"_tarenaipp._tcp." name:@"tarena"];
  13. _service.delegate = self;
  14. //設置解析超時時間
  15. [_service resolveWithTimeout:1.0];
  16. }
  17. return self;
  18. }
  19. //NSNetServiceDelegate
  20. (void)netServiceDidResolveAddress:(NSNetService *)sender
  21. {
  22. NSLog(@"發現Bonjour服務.");
  23. [self.services addObject:sender];
  24. }
  25. //錯誤處理
  26. (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
  27. {
  28. NSLog(@"%@", errorDict);
  29. }
  30. @end
相關文章
相關標籤/搜索