<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [Cocoa]深入淺出Cocoa之Bonjour網絡編程

      深入淺出Cocoa之Bonjour網絡編程

      羅朝輝 (http://www.rzrgm.cn/kesalin/)

      本文遵循“署名-非商業用途-保持一致”創作公用協議

       

      本文高度參考自 Tutorial: Networking and Bonjour on iPhone,在那個帖子里 iphone 版本的代碼采用的是 MIT 開源協議,所以本例子中的 Mac 版本亦采用 MIT 開源協議。E文較好的童鞋建議閱讀原文。

      本文通過使用 Bonjour 實現了一個簡單的服務器/客戶端聊天程序,演示了 CFSocket,NSNetService/NSNetServiceBrowser, NSInStream/NSOutStream 的用法。

      代碼下載:點擊這里
      效果圖如下:

       

      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。

      Sockets vs Streams:

      Socket 相當于一條通信信道,應用程序通過創建 socket,然后使用這個 socket 連接到其他應用程序進行數據交換。我們可以通過同一個 socket 來發送數據或者接收數據。每個 socket 有一個 ip 地址和 port(通信端口,介于 1 ~ 65535之間)。

      Stream 是傳送數據的單向通道,正因為是單向的,所以我們有輸入/輸出兩種 streams:instream/outstream。stream 只是臨時緩存數據,我們需要將它與文件或內存綁定,從而可以從/向文件或內存中讀/寫數據。在這個教程中,我們使用 stream 結合 socket 在網絡上傳送和接收數據。

      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 也可以處理它們。

      程序框架:

      理論介紹得差不多了,更多細節,請翻閱官方文檔。下面我們來看看整個程序的框架設計圖:

       

      從上圖可以清晰地看出,程序分為三個主要模塊:UI模塊,邏輯模塊,網絡模塊。下面我們打開工程,看看代碼實現:

       

      從工程圖可以看出,代碼結構相當清晰,所有的類被分為四個 group:

      Networking: 網絡相關的代碼,包括 socket 的創建,連接的建立,service 的 publish 和 browser;
      Business Logic:業務邏輯相關代碼。在這個例子中,我們通過 room service 來提供聊天服務。我們通過建立一個 localroom 來創建服務器,并發布一個 room service,客戶端(remoteroom)能夠連接到一個已有的 room service,從而加入該 room 進行對話活動。
      UI :在這個例子中,UI 很簡單,只有兩個 view,一個顯示當前網絡中的 service 列表,另一個顯示 room,以及在該 room service 上進行的對話。
      Misc:一個輔助類,用于存儲用戶設定名稱。

      網絡類

      Server class:創建 server,并發布 service;
      Connection class:決議 service;與服務器建立連接;通過 socket stream 交換數據;
      ServerBrowser class:查詢可用的 service;

      Room類:

      Room class: Room 基類
      LocalRoom class: 創建服務器,發布 service,相應客戶端的連接請求
      RemoteRoom:  連接到服務器已有的 service,

      網絡數據傳輸過程:


      從上圖可以看出,數據從 A 的邏輯層,經 outgoing buffer 寫入 write stream,然后經 socket 通過網絡傳輸到 B 的網絡層,然后 B 端的  read stream 從 socket 中讀取數據,寫入 incoming buffer,然后在 B 的邏輯層以及 UI 上顯示出來。

      用戶交互操作都在 UI layer 上進行,當用戶通過 broadcastChatMessage:fromUser: 發送一條聊天信息,由邏輯層來決定是發送給服務器(由 Remote room 處理),還是發送給連接到服務器自身的所有客戶端(由 Local room 處理)。當從網絡連接接收到一條聊天信息時,邏輯層會得到通知,客戶端只會簡單地將消息顯示在 UI 上,而服務器首先將收到的聊天信息轉發給所有連接到它的客戶端,然后將該信息在 UI 上顯示出來。

      Socket+Streams+Buffers = Connection

      Connection 類對一些的交互進行了封裝:
      兩個 socket stream,一個用來寫入,一個用來讀取;兩塊 data buffer,每個 socket stream 對應一個 data buffer;以及各種控制 flag 和值

      因為 stream 是單向的,所以我們需要為每一個 socket 建立兩個 stream,一個用來從 socket 讀取數據,一個用來向 socket 寫入數據。我們在 connect 和 setupSocketStreams 中初始化它們。

      在本例中,我們通過兩種方式來創建 socket:
      1,(客戶端)通過創建 socket 連接到指定 ip 和 port 的服務器;
      2,(服務器)通過接收來自客戶端的連接請求,在這種情況下,OS 會自動創建一個用于響應的 socket,并通過 native socket handle 傳遞給我們使用;

      無論 socket 是由哪種方式建立的,我們都是通過相同的代碼 setupSocketStreams 來初始化 stream。

      創建 server
      聊天至少需要同時運行兩個 MacChatty 終端,其中至少有一個作為服務器,其他終端才能作為客戶端連接到服務器進行對話。作為服務器的終端,需要創建一個 socket 來監聽(listen)其他終端的連接請求(請參考 Sever class 中的 listeningSocket)。這項工作是在 Server 類中的 createServer 中完成的。

      客戶端如何知道怎樣連接到服務器呢?每一個網絡終端必須有獨一無二的 ip 和 port,ip 地址是由動態獲取的或由用戶設定的,因此我們在這里無需操心 ip 地址問題,因此在代碼中我們使用了 INADDR_ANY。那又如何設定我們想要監聽的 port 呢?一些服務必須監聽約定的 port 才能工作,比如 80,20, 21等端口都是有約定用途的。在這里我們把端口設定問題交給 OS 來處理,OS 會為我們設定一個沒有被占用的 port。為了實現這個目的,我們傳入 port 為 0。為了讓其他客戶端能夠連接到服務器,我們需要告知其他客戶端服務器實際使用的 port,因此,我們在 createServer 方法 PART 3中獲取實際使用 port。

          //// PART 3: Find out what port kernel assigned to our socket
      //
      // We need it to advertise our service via Bonjour
      NSData *socketAddressActualData = [(NSData *)CFSocketCopyAddress(listeningSocket) autorelease];

      // Convert socket data into a usable structure
      struct sockaddr_in socketAddressActual;
      memcpy(&socketAddressActual, [socketAddressActualData bytes], [socketAddressActualData length]);

      self.port = ntohs(socketAddressActual.sin_port);

       

      然后在 PART 4 中,我們將 listening socket 注冊為 application run loop 的消息源,這樣當有新連接到來的時候, OS 就會調用 serverAcceptCallback 這個回調函數通知我們。

          //// PART 4: Hook up our socket to the current run loop
      //
      CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
      CFRunLoopSourceRef runLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, listeningSocket, 0);
      CFRunLoopAddSource(currentRunLoop, runLoopSource, kCFRunLoopCommonModes);
      CFRelease(runLoopSource);

       

      在 serverAcceptCallback 回調處理中,我們創建一個新的 Connection 對象,然后將它與 OS 自動創建的響應新連接的 socket 綁定起來。然后再將這個 Connection 對象傳遞給 Server delegate。

      // Handle new connections
      - (void) handleNewNativeSocket:(CFSocketNativeHandle)nativeSocketHandle
      {
      Connection* connection = [[[Connection alloc] initWithNativeSocketHandle:nativeSocketHandle] autorelease];

      // In case of errors, close native socket handle
      if ( connection == nil ) {
      close(nativeSocketHandle);
      return;
      }

      // finish connecting
      BOOL succeed = [connection connect];
      if ( !succeed ) {
      [connection close];
      return;
      }

      // Pass this on to our delegate
      [delegate handleNewConnection:connection];
      }


      // This function will be used as a callback while creating our listening socket via 'CFSocketCreate'
      static void serverAcceptCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
      {
      // We can only process "connection accepted" calls here
      if ( type != kCFSocketAcceptCallBack ) {
      return;
      }

      // for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle
      CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;

      Server *server = (Server *)info;
      [server handleNewNativeSocket:nativeSocketHandle];

      NSLog(@" >> server accepted connection with socket %d", nativeSocketHandle);
      }

       

      通過 Bonjour 發布服務

      Bonjour 并非在網絡查找服務的唯一途徑,但它是最容易使用的方法之一。我們在 publishService 方法中創建一個 NSNetService 對象來發布服務。我們根據服務類型在網絡查找感興趣的服務,本聊天服務使用“_chatty._tcp.”作為服務類型。在同一網絡中,服務類型名必須唯一,這樣才能精準定位服務,而不至于引發沖突。

      Bonjour 操作也如 socket 一樣需要異步進行,以避免長時間阻塞主線程。因此在實際發布服務時,我們將發布任務交給當前 run loop 去調度,然后設定其 delegate,由 delegate 來處理相關事件:“Publishing succeeded”, “Publishing failed”等。

      - (BOOL) publishService
      {
      // come up with a name for our chat room
      NSString* chatRoomName = [NSString stringWithFormat:@"%@'s chat room", [[AppConfig sharedInstance] name]];

      // create new instance of netService
      self.netService = [[NSNetService alloc] initWithDomain:@"" type:@"_chatty._tcp." name:chatRoomName port:self.port];
      if (self.netService == nil)
      return NO;

      // Add service to current run loop
      [self.netService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

      // NetService will let us know about what's happening via delegate methods
      [self.netService setDelegate:self];

      // Publish the service
      [self.netService publish];

      return YES;
      }

       

      通過 Bonjour 查詢服務

      我們在 ServerBrowser 類中實現 Bonjour 查詢網絡服務的功能。我們創建一個 NSNetServiceBrowser 對象來查詢類型為 “_chatty._tcp.” 的服務。當前網絡中發現有服務被添加到或移除時,NSNetServiceBrowser 的 delegate 即我們的 ServerBrowser 就能得到通知,以進行相應的邏輯處理:更新服務列表,刷新 UI 等。

      // New service was found
      - (void) netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
      didFindService:(NSNetService *)netService
      moreComing:(BOOL)moreServicesComing
      {
      // Make sure that we don't have such service already (why would this happen? not sure)
      if ( ! [servers containsObject:netService] ) {
      // Add it to our list
      [servers addObject:netService];
      }

      // If more entries are coming, no need to update UI just yet
      if ( moreServicesComing ) {
      return;
      }

      // Sort alphabetically and let our delegate know
      [self sortServers];

      [delegate updateServerList];
      }


      // Service was removed
      - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
      didRemoveService:(NSNetService *)netService
      moreComing:(BOOL)moreServicesComing
      {
      // Remove from list
      [servers removeObject:netService];

      // If more entries are coming, no need to update UI just yet
      if ( moreServicesComing ) {
      return;
      }

      // Sort alphabetically and let our delegate know
      [self sortServers];

      [delegate updateServerList];
      }

       

       

      通過 Bonjour 決議服務

      當用戶選擇其中一個 chat room,并加入其中時,客戶端將會連接到發布該 chat room 服務的服務器。這個連接過程在 ChattyViewController 類的 joinChatRoom: 方法中實現。首選我們通過選擇的 NSNetService 發送 resolveWithTimeout: 消息來進行決議應該連接到哪個服務器(請參考 Connection 類的 connect 方法中最后一種情形),同時設定 NSNetService 的 delegate 來響應決議相關的事件:didNotResolve: 和 netServiceDidResolveAddress:。當決議完成之后,在 netServiceDidResolveAddress: 方法中,我們可以建立到服務的 socket 連接并創建用于數據傳輸的 stream 了。

      // Called when net service has been successfully resolved
      - (void)netServiceDidResolveAddress:(NSNetService *)sender
      {
      if ( sender != netService ) {
      return;
      }

      // Save connection info
      self.host = netService.hostName;
      self.port = netService.port;

      // Don't need the service anymore
      self.netService = nil;

      // Connect!
      if ( ![self connect] ) {
      [delegate connectionAttemptFailed:self];
      [self close];
      }
      }

       

      至此,Bonjour 網絡編程介紹就結束了,代碼中的注釋相當詳細,細節就不多羅嗦了。

      為了演示效果,我們需要運行該程序的兩個實例,可以在如下路徑找到可執行文件:

      /Users/username/Library/Developer/Xcode/DerivedData/MacChatty-XXXX/Build/Products/Debug

       

      參考資料
      Tutorial: Networking and Bonjour on iPhone:http://mobileorchard.com/tutorial-networking-and-bonjour-on-iphone/
      Introduction to Bonjour Overview:http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NetServices/Introduction.html
      Introduction to NSNetServices and CFNetServices Programming Guide:http://developer.apple.com/library/mac/#documentation/Networking/Conceptual/NSNetServiceProgGuide/Introduction.html#//apple_ref/doc/uid/TP40002736

      posted @ 2011-09-15 21:42  飄飄白云  閱讀(16104)  評論(8)    收藏  舉報
      本博客遵循 Creative Commons License “署名-非商業用途-保持一致”創作共用協議。 與我聯系
      主站蜘蛛池模板: 日韩一区二区三区av在线| 亚洲视频欧美不卡| 欧美人与动zozo在线播放| 国产在线观看免费观看不卡| 国产高清视频一区二区乱| 成全影院电视剧在线观看| 日本亚洲一区二区精品久久| 麻豆久久天天躁夜夜狠狠躁| 97色伦97色伦国产| av偷拍亚洲一区二区三区| 国产精品一区二区色综合| 久久中精品中文字幕入口| 中国少妇人妻xxxxx| 国产片av在线观看国语| 99国产精品永久免费视频| 国产一级区二级区三级区| 大地资源中文第二页日本| 日韩不卡手机视频在线观看| 老色99久久九九爱精品| 国产AV福利第一精品| 起碰免费公开97在线视频| 午夜爽爽爽男女污污污网站| 久久精品免视看国产成人| 精品一卡2卡三卡4卡乱码精品视频 | 日韩免费视频一一二区| 昭通市| 亚洲精品日韩在线观看| 色九月亚洲综合网| 会东县| 亚洲高清偷拍一区二区三区| 精品一区二区三区在线观看l| 2022最新国产在线不卡a| 亚洲永久精品日本久精品| 精品人妻无码一区二区三区性| 亚洲无人区码二码三码区| 亚洲成在人线在线播放无码| 国产盗摄xxxx视频xxxx| 亚洲自拍偷拍福利小视频| 亚洲人妻系列中文字幕| 县级市| 亚洲午夜精品国产电影在线观看 |