网络访问

来源:互联网 发布:discuz json api接口 编辑:程序博客网 时间:2024/05/18 00:37

《OBjective C 2.0 Phrasebook - 17 network access》

开发跨平台网络可知应用程序的标准方法是使用Berkeley套接字API。它也是开发TCP/IP的一部分,并且是协议无关的。大多数语言都对这个API进行了实现。在Objective-C中,你仍然可以尽情使用C语言,它提供了所有底层接口的能力。但是大多数时候,这么做往往事倍功半。Foundation框架提供了一些类,使开发网络应用变得容易。使用这些抽象类可能会失去底层API的一些功能,但并不是很多。

封装C套接字

return [[[NSFileHandle alloc] initWithFileDescriptor: s closeOnDealloc: YES] autorelease];
在第13章我们简单介绍了NSFileHandle类,并提到它封装了一个文件描述符。套接字API的设计是基于UNIX的“所有东西都是文件,即使这没有什么意义”概念的。你可以向对待文件一样使用相同的函数读取或写入一个套接字。还可以使用NSFileHandle封装套接字。这样我们就可以使用底层API来按需创建套接字,然后采用一种更为抽象的方式来与所创建的套接字进行交互。在需要使用底层套接字API但又不方便开放给更高层API的情况下,这种方式是你唯一的选择。在其他情况下,这种方式通常也是最方便的。

注意:记住Objective-C的目标是让C程序员的生活变轻松。不到万不得已不要使用Objecive-C,仅在比使用C的API更有意义时再考虑使用它。Objective-C API的使用通常仅仅是对C API的封装,徒增了负载和复杂性,并不能使你的代码简单。

将自己的套接字封装进一个文件描述符对象的最大好处在于,你可以很方便地将它与循环整合,从而便于编写事件驱动代码。另外一大好处是你可以为你的套接字使用Objective-C引用计数。如果在创建NSFileHandle时,向closeOnDealloc:传递了YES参数,那么在该对象销毁时会关闭文件描述符。如果向你的对象发送-waitForDataInBackgroundAndNotify消息,它将被添加到文件描述符列表中,供循环轮询。在下一轮循环迭代时,如果有数据等待,NSFileHandle对象将提交一个通知。如果该通知已经被注册过,那么你就可以读取新数据了。这个方法的文档有一些误导,它宣称这个方法可以创建另外一个线程,但实际上,在OS X上,循环使用的是kqueue() API(GNUstep可能使用的是poll()或select()),并且调用该方法仅仅将另外一个文件描述符添加到其所等待的列表中。在OS X 10.6上,这是通过使用Grand Central Dispatch提交一个通知来实现的,但核心功能是一样的。最后一个好处在于,你不需要考虑缓冲区。当你从一个套接字读取数据时,你不得不首先分配缓冲区,而后传递一个指针给它,用于read()和recv()系统调用。对于文件描述符对象,你只需要读NSData实例。

如果你使用了很多字符串,可能就需要考虑在NSFileHandle上添加分类,提供-writeUTF8String:和-readUTF8String:方法,或者你的协议所使用的此类编码的等价方法,从而可以直接接收和发送字符串。

连接服务器

Objective-C为网络通信提供了一套类似Java的stream类,与底层UNIX接口一样。不幸的是,该类并不是特别有用,因为它们并未提供诸如压缩等功能,也未提供在通信期间的协商加密功能,而这些是流行协议所必须的。

NSInputStream *in;NSOutputStream *out;NSHost *host = [NSHost hostWithName:@"example.com"];[NSStream getStreamsToHost: host port: 80 inputStream: &in outputStream: &out];[in open];

其最大的限制在于,要使它们支持SRV记录很不容易。这些是DNS记录,用于标识服务和端口之间的映射,正如主机名和IP地址的映射一样。它们被用于DNS-SD,但IP地址资源的短缺使其在Internet上分层DNS系统中越来越常见。

如果在OS X上使用POSIX的getaddrinfo()函数来查询一个服务器的地址,你需要提供主机名和服务名。系统会将它们映射到所支持协议的网络地址和端口号。

当构造NSStream时,你必须将端口号指定为一个整数。这就意味着它在所有使用DNS SRV记录的服务上都会失败。但是,如果使用getaddrinfo()而后再将结果套接字封装在一个NSFileHandle中,就会正常工作。在EtoileFoundation中,我们提供了HSFileHandle上的category,可以直接由主机名和协议名来创建文件处理程序,并封装该调用。

hints.ai_family = PF_UNSPEC;hints.ai_socktype = SOCK_STREAM;//Ask for a stream address.error = getaddrinfo(server, service, &hints, &res0);if (error) { return nil; }int s = -1;for (struct addrinfo *res = res0;res != NULL && s < 0 ;res = res->ai_next){s = socket(res->ai_family, res->ai_socktype,res->ai_protocol);//If the socket failed, try the next addressif (s < 0) { continue ; }//If the connection failed, try the next addressif (connect(s, res->ai_addr, res->ai_addrlen) <0){close(s);s = -1;continue;}}freeaddrinfo(res0);


当调用getaddrinfo()时,要以C字符串形式提交服务器和服务类型,以及hint集合等参数,用于指明你所希望的套接字类型。而后就会返回addrinfo结构数组,包含构造连接套接字的必需信息。

它们会以解析器认为最好的顺序排列,你可以按顺序一个个尝试,直到找到为止。如果同时存在IPv6和v4的连接,并且DNS有指向两者的记录,那么代码就会全部尝试。

在网络上共享对象

NSMutableDictionary *object = [NSMutableDictionary new]; NSConnection *conn = [NSConnection new]; [conn setRootObject: object]; if ([conn registerName: @"sharedDict"]) { [[NSRunLoop currentRunLoop] run]; }

Objective-C最强大的同时也是未被充分利用的功能之一就是分布式对象(distributed objects)系统。如果向某个对象发送一条消息,而该对象不知道该如何处理该消息,那么它将收到一条-forwardInvocation:消息,并带有将该消息封装为参数的一个对象。

和Objective-C的其他对象一样,你可以仔细考虑一下这个对象。它将告诉你有关选择器、参数数量,以及所有参数的类型和值。

Foundation框架还包括一个使用这种方法的NSDistantObject类。远端对象一般是用于向远程对象转发消息的代理。远程对象可以放在任何一个不同的进程中,甚至是不同的计算机中。

如果你将对象作为一个参数放在传递给发往远端对象的消息,那么这些对象可能会被复制,或者在远端创建远程代理回传消息。

分布式对象的使用很简单,使用分布式对象的任何程序都有两个部分:客户端和服务器。服务器为其他进程提供对象,客户端连接服务器和访问服务器。

默认情况下,对象仅为本机共享。要想通过网络共享对象,必须在注册对象时使用NSSocketPortNameServer。

NSMutableDictionary *object = (id)[NSConnection rootProxyForConnectionWithRegisteredName:@"sharedDict"                                    host:nil]; NSLog(@"Object: %@", object); [object setObject: @"aValue" forKey: @"aKey"]; NSLog(@"Object: %@", object);


这个例子展示了在进程之间共享一个NSMutableDictionary实例。要注意的是必须用NSRunLoop来处理分布式对象。分布式对象系统会添加一个IPC隧道(一个Mach端口或一个套接字),用来与循环通信。当其他进程给该端口发送消息,一些数据将通过该隧道到达,并通知DO系统。

而后它将根据收到的数据构造一个NSInvocation并调用它,然后返回结果。

doAccess[85613:903] Object:{ } doAccess[85613:903] Object: { aKey = aValue; }

 

寻找网络Peer

NSProcessInfo *pi = [NSProcessInfo processInfo]; NSString *name = [NSString stringWithFormat: @"%@/%@", [pi hostName],NSFullUserName()]; NSNetService *service = [[NSNetService alloc]                         initWithDomain: @""                         type: type name: name port: 123]; [service publish]; NSNetServiceBrowser *sb = [NSNetServiceBrowser new]; [sb setDelegate: self]; [sb searchForServicesOfType: type inDomain: @""];

多年来,有很多用来寻找本地网络服务的协议,包括NetBIOS和AppleTalk,它们直到90年代在本地网络中还很常见,直到后来被TCP/IP取代。

TCP/IP协议栈并不包括发现服务的全部功能,它的最新内容是广播地址。如果向广播地址发送包,那么这个网段上所有人都会收到。这就会带来很多问题,它仅仅工作在本地物理网段内,在现在的交换网络中效率不高,并且它是很底层的一种方法。

IETF的ZeroConf工作组建议使用多播DNS(mDNS)来解决这一问题。DNS是发布映射的一种可扩展方式,通常用来解析主机名和IP地址的映射,但也可以用于诸如电话号码、地利信息和其他类型的主机之类的东西上。

通过mDNS,个人计算机可以在.local域中发布DNS记录。它被用于DNS服务发现(DNS-SD)的基础,描述了一种通过DNS广告任意服务的方式。可以将该标准和传统的层级DNS一起用,但最好的方式是与mDNS一起用。

这两种标准的组合经常被称为Bonjour,并通过NSNetService 类开放给Objective-C。它允许应用程序发布DNS-SD记录并为特定服务找到所有记录。

OS X上的很多程序都用到它。Safari用它共享书签;iChat用它来为无服务器消息找到通讯簿;iTunes用它来共享音乐;系统用它来查找共享文件夹和打印机。如果你正在开发多种类型的网络应用程序,你会发现使用NSService向peer发布广告很有用。

在发布服务时,必须指定唯一名。如果正在使用分布式对象(在本章末尾将介绍它),你就可以用它作为你正在服务的对象的名称,并忽略Bonjour系统的DNS相关的基础。至于其他的,你所发布的端口通常都更为有用,名字则是发布给用户的。

使用NSNetServiceBrowser类搜索peer。如何正确使用它有些棘手。发布服务是同步的,向NSNetService实例发送-publish消息,服务就被发布了。搜索则是异步的,需要激活一个循环。

当你向浏览器发送搜索消息时,它将传送一个DNS请求。在返回应答之前可能需要一段时间,在此期间,你肯定不希望阻止用户做别的事情,因此这个方法应立刻返回。

当收到响应时,该类将发送一个消息给它的委托,消息中带有一个新的NSNetService实例,描述了它所找到的所有服务器。而后它将持续侦听,直到你向它发送-Stop消息。

很多情况下,你从不发送这个消息。在进程运行期间,你可以任由浏览器在后台运行,每当在本地网络中发现一个新的peer时都会得到一个通知。而后你可以更新用户接口为用户显示这个新peer。这个有点象iTunes,添加一个出现在网络上的新的共享列表到库中。

 

通过URL加载数据

[NSAutoreleasePool new];NSArray *args = [[NSProcessInfo processInfo] arguments];NSString *target = [args objectAtIndex:1];NSURL *targetURL = [NSRUL URLWithString:target];if(nil == targetURL) {return 1;}NSURLRequest *req = [NSURLRequest requestWithURL:targetURL];id delegate = [DownloadDelegate new];NUSURLDownload *dl = [[NSURLDownload alloc] initWithRequest:req delegate:delegate];[dl setDestination:[targetURL lastPathComponent] allowOverwrite:YES];[[NSRunLoop currentRunLoop] run];


在编写Internet程序时,你可能会希望从由统一资源定位符(URL)所标识的远端位置获取数据。URL是全球标识符,拥有一套机制及相关的数据。Foundation框架的NSURL类用于封装URL,可以通过字符串解析URL并返回一个抽象表示。

Foundation还有两个从URL获取数据的类,其中NSURLDownload类较为简单,封装了从远端URL下载数据的方法。复杂一些的是NSURLConnection类,它提供了处理请求和响应的底层方法。

URL加载系统是协议无关的,但需要符合请求-响应格式。请求被封装在NSURLRequest中,然后发送给上述两个类中的一个。它将构造连接并发送该请求,而后在回应中给你提供一个或多个NSURLResponse对象。

Foundation框架可以加载来自file://, http://, https://和ftp://等类型的URL。也可以通过实现一个NSURLProtocol子类使之支持其他协议。虽然很少需要这么做,但你可能希望实现对SFTP的支持或类似的类。而后你可以重用已有代码委托文件访问URL加载系统。

本节的例子展示了如何从用户指定的远程URL获取一个文件,它使用了一个委托类来向用户报告进程,尽管这并不是必需的。

委托存储了响应的大小,因此它可以报告进度。如果传输失败,它会报告一个本地错误消息。如果正常,它会在每下载1%内容是打印出一个点。

URL加载子系统很强大,但在大多数时候你应该将其限制在上述例子的子集中。这使你使用非常少的代码来实现一个简单的web服务。



 

 

 





原创粉丝点击