深入浅出 Cocoa 之 Bonjour 网络编程
罗朝辉(http://blog.csdn.net/kesalin/)
CC许可,转载请注明出处
本文高度参考自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 4 中,我们将 listening socket 注册为 application run loop 的消息源,这样当有新连接到来的时候, OS 就会调用 serverAcceptCallback 这个回调函数通知我们。
在 serverAcceptCallback 回调处理中,我们创建一个新的 Connection 对象,然后将它与 OS 自动创建的响应新连接的 socket 绑定起来。然后再将这个 Connection 对象传递给 Server delegate。
通过 Bonjour 发布服务
Bonjour 并非在网络查找服务的唯一途径,但它是最容易使用的方法之一。我们在 publishService 方法中创建一个 NSNetService 对象来发布服务。我们根据服务类型在网络查找感兴趣的服务,本聊天服务使用“_chatty._tcp.”作为服务类型。在同一网络中,服务类型名必须唯一,这样才能精准定位服务,而不至于引发冲突。
Bonjour 操作也如 socket 一样需要异步进行,以避免长时间阻塞主线程。因此在实际发布服务时,我们将发布任务交给当前 run loop 去调度,然后设定其 delegate,由 delegate 来处理相关事件:“Publishing succeeded”, “Publishing failed”等。
通过 Bonjour 查询服务
我们在 ServerBrowser 类中实现 Bonjour 查询网络服务的功能。我们创建一个 NSNetServiceBrowser 对象来查询类型为 “_chatty._tcp.” 的服务。当前网络中发现有服务被添加到或移除时,NSNetServiceBrowser 的 delegate 即我们的 ServerBrowser 就能得到通知,以进行相应的逻辑处理:更新服务列表,刷新 UI 等。
通过 Bonjour 决议服务
当用户选择其中一个 chat room,并加入其中时,客户端将会连接到发布该 chat room 服务的服务器。这个连接过程在 ChattyViewController 类的 joinChatRoom: 方法中实现。首选我们通过选择的 NSNetService 发送 resolveWithTimeout: 消息来进行决议应该连接到哪个服务器(请参考 Connection 类的 connect 方法中最后一种情形),同时设定 NSNetService 的 delegate 来响应决议相关的事件:didNotResolve:
和 netServiceDidResolveAddress:。当决议完成之后,在 netServiceDidResolveAddress: 方法中,我们可以建立到服务的 socket连接并创建用于数据传输的 stream 了。
至此,Bonjour 网络编程介绍就结束了,代码中的注释相当详细,细节就不多罗嗦了。
参考资料:
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
分享到:
相关推荐
- [深入浅出Cocoa]之Bonjour网络编程 - [深入浅出Cocoa]之Framework - [深入浅出Cocoa]之Plugin - [深入浅出Cocoa]之CoreData - [调试]XCode下的iOS单元测试 - [调试]XCode的一些调试技巧 - [版本管理]Mac下配置Git...
《Learning Cocoa With Objective-C》是一本专为初学者设计的Cocoa编程教程,它深入浅出地介绍了如何在苹果的Mac OS X和iOS平台上开发应用程序。这本书以其丰富的图文并茂的讲解方式,使得复杂的编程概念变得易于...
iOS 网络编程之 Socket iOS 网络编程层次结构可以分为三层:Cocoa 层、Core Foundation 层和 OS 层。Cocoa 层是最上层的基于 Objective-C 的 API,提供了诸如 URL 访问、NSStream、Bonjour、GameKit 等 API。Core ...
- **Bonjour**:本地网络发现服务。 - **通信端口管理**:用于设备间的通信。 - **国际化支持**:多语言和地区的适配。 ##### 3. Address Book UI框架 Address Book UI框架简化了联系人的创建、编辑和选择过程,...
同时,这也为教学和研究提供了宝贵的资源,让学生和研究人员能够深入理解Bonjour服务以及Cocoa应用程序的开发。 总的来说,Bonjour Browser是一个强大而直观的工具,它利用Cocoa框架来探索和理解Bonjour服务。开源...
Cocoa层是最上层,提供了一系列Objective-C API,包括URL访问(NSURL)、流(NSStream)、Bonjour服务发现、Game Kit以及WebKit,这些都是iOS开发者日常开发中最常见的网络功能。这一层的API基于Core Foundation实现...
Cocoa Touch层是iPhoneOS架构中最为关键的部分之一,它为开发者提供了三个主要框架:UIKit框架、Foundation框架以及Address Book UI框架。 1. **UIKit框架** - **功能概述**:UIKit框架提供了实现图形、事件驱动的...
CFNetwork通过提供一套更加高级的抽象层,使得网络编程不需要深入了解底层网络协议的细节。 #### CFNetwork的优势 CFNetwork相比直接使用BSD sockets有几个主要优势: - 集成了“运行循环”(Run Loop)机制,...
- **外部资源**:推荐《UNIX网络编程》等书籍作为补充学习资料,深入理解socket编程。 #### 6. CFNetwork概念详解 - **低层次与高性能**:CFNetwork是一个低层次且性能优秀的框架,允许开发者灵活操作网络协议栈。 ...
本文将深入探讨iOS中的Socket编程,包括C/S三次握手过程、iOS Socket技术的层次结构以及NSStream的使用。 首先,我们来理解C/S三次握手的过程。三次握手是TCP连接建立的关键步骤,确保双方都准备好进行通信: 1. ...
本书深入探讨了Mac OS X平台上的高级编程技术,旨在帮助读者掌握Objective-C语言以及如何在Cocoa框架下构建高质量的应用程序。 ### 一、Objective-C语言 Objective-C是一种面向对象的编程语言,它在C语言的基础上...
本书详细介绍了如何利用Cocoa框架构建功能丰富的Mac应用,包括用户界面设计、事件处理、数据模型管理、网络通信和多线程编程等方面的知识。 #### 3. Interface Builder Interface Builder是Xcode集成开发环境的一...
13. **Cocoa Touch**:Cocoa Touch是iOS应用开发的基础框架,包含了UI组件、触摸事件处理、动画等,如OpenAL(音频处理)、Bonjour(发现网络服务)、WebKit(网页渲染)和BSD Sockets(网络编程接口)都是在其上...
书中还将涵盖iOS的网络服务,如Bonjour和Bonjour SDK,以及如何利用这些服务进行设备间的通信。 除了操作系统的基本结构,本书还可能涉及开发者关心的主题,如Objective-C和Swift编程语言的使用,以及Xcode开发环境...
书中所涉及的内容包括了使用Swift和Objective-C语言进行iOS应用开发的各种知识点,例如App Store、Apple、Bonjour、Cocoa、Cocoa Touch、Finder、Instruments、Interface Builder、iPad、iPhone、iPod、iPod touch、...
2. **Objective-C基础**:Objective-C是iOS开发的主要编程语言,这本书会深入浅出地介绍其语法特性,包括消息传递、类别、协议、实例变量和属性等。 3. **Cocoa Touch框架**:这是iOS应用程序的用户界面构建块,书...
iPhone OS iPhone OS 由4个主要部分组成。下面简单地罗列一下它们的功能。 Cocoa Touch • 窗口和视图 • 事件管理 • 用户接口 • 加速传感器 • 照相机 ...• Bonjour(利用无线网络连接其他机器)
这个压缩包“IOS应用源码之局域网或蓝牙连接.zip”很可能是包含了实现这两种通信方式的示例代码,对于学习iOS开发尤其是网络和硬件交互的开发者来说,是非常宝贵的资源。 首先,让我们来了解一下局域网(LAN)连接...