ios网络编程.doc_第1页
ios网络编程.doc_第2页
ios网络编程.doc_第3页
ios网络编程.doc_第4页
ios网络编程.doc_第5页
已阅读5页,还剩19页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

iOS网络编程实践-NSStream实现TCP Socket iPhone客户端 客户端我们使用iPhone应用程序,画面比较简单。点击发送按钮,给服务器发送一些字符串过去。点击接收按钮就会从服务器读取一些字符串,并且显示在画面上。有关客户端应用的UI部分不再介绍了,我们直接看代码部分,Socket客户端可以采用CFStream或NSStream实现,CFStream 实现方式与服务器端基本一样。为了给读者介绍更多的知识,本例我们采用NSStream实现。NSStream实现采用Objective-C语言,一些 面向对象的类。下面我们看看客户端视图控制器ViewController.h#import #include #include #define PORT 9000 interface ViewController : UIViewControllerint flag ; /操作标志 0为发送 1为接收 property (nonatomic, retain) NSInputStream *inputStream;property (nonatomic, retain) NSOutputStream *outputStream; property (weak, nonatomic) IBOutlet UILabel *message; - (IBAction)sendData:(id)sender;- (IBAction)receiveData:(id)sender; end定义属性inputStream和outputStream,它们输入流NSInputStream和输出流NSOutputStream类。它们与服务器CFStream实现中的输入流CFReadStreamRef和输出流CFWriteStreamRef对应的。视图控制器ViewController.m的初始化网络方法initNetworkCommunication代码:- (void)initNetworkCommunicationCFReadStreamRef readStream;CFWriteStreamRef writeStream;CFStreamCreatePairWithSocketToHost(NULL,(CFStringRef)”03, PORT, &readStream, &writeStream); _inputStream = (_bridge_transfer NSInputStream *)readStream; _outputStream = (_bridge_transfer NSOutputStream*)writeStream; _inputStream setDelegate:self; _outputStream setDelegate:self; _inputStream scheduleInRunLoop:NSRunLoop currentRunLoopforMode:NSDefaultRunLoopMode; _outputStream scheduleInRunLoop:NSRunLoop currentRunLoopforMode:NSDefaultRunLoopMode; _inputStream open; _outputStream open; 点击发送和接收按钮触发的方法如下:/* 点击发送按钮 */- (IBAction)sendData:(id)sender flag = 0;self initNetworkCommunication;/* 点击接收按钮 */- (IBAction)receiveData:(id)sender flag = 1;self initNetworkCommunication;它们都调用initNetworkCommunication方法,并设置操作标识flag,如果flag为0发送数据,flag为1接收数据。流的状态的变化触发很多事件,并回调NSStreamDelegate协议中定义的方法stream:handleEvent:,其代码如下:-(void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent NSString *event;switch (streamEvent) case NSStreamEventNone:event = ”NSStreamEventNone”;break;case NSStreamEventOpenCompleted:event = ”NSStreamEventOpenCompleted”;break;case NSStreamEventHasBytesAvailable:event = ”NSStreamEventHasBytesAvailable”;if (flag =1 & theStream = _inputStream) NSMutableData *input = NSMutableData alloc init;uint8_t buffer1024; int len;while(_inputStream hasBytesAvailable) len = _inputStream read:buffer maxLength:sizeof(buffer); if (len 0)input appendBytes:buffer length:len;NSString *resultstring = NSString allocinitWithData:input encoding:NSUTF8StringEncoding;NSLog(”接收:%”,resultstring);_message.text = resultstring;break;case NSStreamEventHasSpaceAvailable:event = ”NSStreamEventHasSpaceAvailable”;if (flag =0 & theStream = _outputStream) /输出UInt8 buff = ”Hello Server!”; _outputStream write:buff maxLength: strlen(const char*)buff)+1; /关闭输出流_outputStream close;break;case NSStreamEventErrorOccurred:event = ”NSStreamEventErrorOccurred”;self close; break;case NSStreamEventEndEncountered:event = ”NSStreamEventEndEncountered”;NSLog(”Error:%d:%”,theStream streamError code,theStream streamError localizedDescription);break;default:self close; event = ”Unknown”;break;NSLog(”event%”,event);在读取数据分支(NSStreamEventHasBytesAvailable)中,代码第行为读取数据准备缓冲区,本例中设置的是1024个字节,这个大小会对流的读取有很多的影响。第行代码使用hasBytesAvailable方法判断是否流有数据可以读,如果有可读数据就进行循环读取。第行代码使用流的read:maxLength:方法读取数据到缓冲区,第1个参数是缓冲区对象buffer,第2个参数是读取的缓冲区的字节长度。在写入数据分支(NSStreamEventHasSpaceAvailable)中,代码第行是要写入的数据,第行代码 _outputStreamwrite:buffmaxLength:strlen(constchar*)buff)+1是写如数据方 法。第和第行代码selfclose调用close方法关闭,close方法代码如下:-(void)close_outputStream close;_outputStream removeFromRunLoop:NSRunLoop currentRunLoopforMode:NSDefaultRunLoopMode;_outputStream setDelegate:nil;_inputStream close;_inputStream removeFromRunLoop:NSRunLoop currentRunLoopforMode:NSDefaultRunLoopMode;_inputStream setDelegate:nil;深入浅出CocoaiOS网络编程之CFNetwork 深入浅出CocoaiOS网络编程之CFNetwork罗朝辉 (/kesalin/)本文遵循“署名-非商业用途-保持一致”创作公用协议一,CFNetwork 简介首先来回顾下。在前文深入浅出CocoaiOS网络编程之Socket中,提到iOS网络编程层次模型分为三层: Cocoa层:NSURL,Bonjour,Game Kit,WebKit Core Foundation层:基于 C 的CFNetwork 和 CFNetServices OS层:基于 C 的 BSD socket 前文讲的是最底层的 socket,本文将介绍位于 Core Foundation 中的 CFNetwork。CFNetwork 只是对 BSD socket 的进行了轻量级的封装,但在 iOS 中使用 CFNetwork 有一个显著的好处,那就是 CFNetwork 与系统级别的设置(如:天线设置)以及 run-loop 结合得很好。每一个线程都有自己的 run-loop,因此我们可以 CFNetwork 当中事件源加入到 run-loop 中,这样就可以在线程的 run-loop 中处理网络事件了。BTW,大名鼎鼎的 ASIHttpRequest 库就是基于 CFNetwork 封装的。本文示例代码就是这样做的,源码请查看:/kesalin/iOSSnippet/tree/master/KSNetworkDemo二,CFNetwork API 简介CFNetwork 接口是基于 C 的,下面的接口用于创建一对 socket stream,一个用于读取,一个用于写入:void CFStreamCreatePairWithSocketToHost(CFAllocatorRef alloc, CFStringRef host, UInt32 port, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);该函数使用 host 以及 port,CFNetwork 会将该 host 转换为 IP 地址,并转换为网络字节顺序。如果我们只需要一个 socket stream,我们可以将另外一个设置为 NULL。还有另外两个“重载”的创建 socket sream的接口:CFStreamCreatePairWithSocket 和CFStreamCreatePairWithPeerSocketSignature,在这里就不一一介绍了。注意:这些 socket stream 在使用之前就如原生 socket 一样,必须显式地调用其 open 函数:Boolean CFReadStreamOpen(CFReadStreamRef stream);Boolean CFWriteStreamOpen(CFWriteStreamRef stream);但与 socket 不同的是,这两个接口是异步的,当成功 open 之后,如果调用方设置了获取kCFStreamEventOpenCompleted 事件的标志的话就会其调用回调函数。而该回调函数及其参数设置是通过如下接口进行的:Boolean CFReadStreamSetClient(CFReadStreamRef stream, CFOptionFlags streamEvents, CFReadStreamClientCallBack clientCB, CFStreamClientContext *clientContext);Boolean CFWriteStreamSetClient(CFWriteStreamRef stream, CFOptionFlags streamEvents, CFWriteStreamClientCallBack clientCB, CFStreamClientContext *clientContext);该函数用于设置回调函数及相关参数。通过streamEvents 标志来设置我们对哪些事件感兴趣;clientCB 是一个回调函数,当事件标志对应的事件发生时,该回调函数就会被调用;clientContext 是用于传递参数到回调函数中去。当设置好回调函数之后,我们可以将 socket stream 当做事件源调度到 run-loop 中去,这样 run-loop 就能分发该 socket stream 的网络事件了。void CFReadStreamScheduleWithRunLoop(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);void CFWriteStreamScheduleWithRunLoop(CFWriteStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);注意,在我们不再关心该 socket stream 的网络事件时,记得要调用如下接口将 socket stream 从 run-loop 的事件源中移除。void CFReadStreamUnscheduleFromRunLoop(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);void CFWriteStreamUnscheduleFromRunLoop(CFWriteStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);当我们将 socket stream 的网络事件调度到 run-loop 之后,我们就能在回调函数中相应各种事件,比如kCFStreamEventHasBytesAvailable读取数据:Boolean CFReadStreamHasBytesAvailable(CFReadStreamRef stream);CFIndex CFReadStreamRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength);或kCFStreamEventCanAcceptBytes 写入数据:Boolean CFWriteStreamCanAcceptBytes(CFWriteStreamRef stream);CFIndex CFWriteStreamWrite(CFWriteStreamRef stream, const UInt8 *buffer, CFIndex bufferLength);最后,我们调用 close 方法关闭 socket stream:void CFReadStreamClose(CFReadStreamRef stream);void CFWriteStreamClose(CFWriteStreamRef stream);三,客户端示例代码与 socket 演示类似,在这里我只演示客户端示例。同样,我们也在一个后台线程中启动网络操作: NSURL * url = NSURL URLWithString:NSString stringWithFormat:%:%, serverHost, serverPort; NSThread * backgroundThread = NSThread alloc initWithTarget:self selector:selector(loadDataFromServerWithURL:) object:url; backgroundThread start;然后在loadDataFromServerWithURL 中创建 socket 流,并设置其回调函数,将其加入到 run-loop 的事件源中,然后启动之:- (void)loadDataFromServerWithURL:(NSURL *)url NSString * host = url host; NSInteger port = url port integerValue; / Keep a reference to self to use for controller callbacks / CFStreamClientContext ctx = 0, (_bridge void *)(self), NULL, NULL, NULL; / Get callbacks for stream data, stream end, and any errors / CFOptionFlags registeredEvents = (kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred); / Create a read-only socket / CFReadStreamRef readStream; CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (_bridge CFStringRef)host, port, &readStream, NULL); / Schedule the stream on the run loop to enable callbacks / if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx) CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); else self networkFailedWithErrorMessage:Failed to assign callback method; return; / Open the stream for reading / if (CFReadStreamOpen(readStream) = NO) self networkFailedWithErrorMessage:Failed to open read stream; return; CFErrorRef error = CFReadStreamCopyError(readStream); if (error != NULL) if (CFErrorGetCode(error) != 0) NSString * errorInfo = NSString stringWithFormat:Failed to connect stream; error % (code %ld), (_bridge NSString*)CFErrorGetDomain(error), CFErrorGetCode(error); self networkFailedWithErrorMessage:errorInfo; CFRelease(error); return; NSLog(Successfully connected to %, url); / Start processing / CFRunLoopRun();参考前面的接口说明,相信你不难理解上面的代码。前面唯一没有提到的接口就是CFReadStreamCopyError,该接口用于获取当前的错误信息,如果没有错误则返回 NULL。CFErrorRef CFReadStreamCopyError(CFReadStreamRef stream);CFErrorRef CFWriteStreamCopyError(CFWriteStreamRef stream);此外,我们还可以调用如下接口获取 socket stream 的当前状态:CFStreamStatus CFReadStreamGetStatus(CFReadStreamRef stream);CFStreamStatus CFWriteStreamGetStatus(CFWriteStreamRef stream);在上面的代码中,我们设置了当有数据可以读取,流到达结尾处时以及错误发生时调用回调函数socketCallback:void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void * myPtr) KSCFNetworkViewController * controller = (_bridge KSCFNetworkViewController *)myPtr; switch(event) case kCFStreamEventHasBytesAvailable: / Read bytes until there are no more / while (CFReadStreamHasBytesAvailable(stream) UInt8 bufferkBufferSize; int numBytesRead = CFReadStreamRead(stream, buffer, kBufferSize); controller didReceiveData:NSData dataWithBytes:buffer length:numBytesRead; break; case kCFStreamEventErrorOccurred: CFErrorRef error = CFReadStreamCopyError(stream); if (error != NULL) if (CFErrorGetCode(error) != 0) NSString * errorInfo = NSString stringWithFormat:Failed while reading stream; error % (code %ld), (_bridge NSString*)CFErrorGetDomain(error), CFErrorGetCode(error); controller networkFailedWithErrorMessage:errorInfo; CFRelease(error); break; case kCFStreamEventEndEncountered: / Finnish receiveing data / controller didFinishReceivingData; / Clean up / CFReadStreamClose(stream); CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFRunLoopStop(CFRunLoopGetCurrent(); break; default: break; 上面的代码也很好理解,当有数据可以读取时,读取之,然后更新 UI;当流到达结尾处时,关闭流,执行清理工作;当错误发送时,报告错误信息。四,扩展虽然上面的代码只演示了如何使用 CFNetwork 的 CFReadStream 来读取数据,写入数据使用 CFWriteStream,其工作流程也是一样的。在这里就不再介绍了。深入浅出CocoaiOS网络编程之Socket 深入浅出CocoaiOS网络编程之Socket罗朝辉 (/kesalin/)本文遵循“署名-非商业用途-保持一致”创作公用协议一,iOS网络编程层次模型在前文深入浅出Cocoa之Bonjour网络编程中我介绍了如何在Mac系统下进行 Bonjour 编程,在那篇文章中也介绍过 Cocoa 中网络编程层次结构分为三层,虽然那篇演示的是 Mac 系统的例子,其实对iOS系统来说也是一样的。iOS网络编程层次结构也分为三层: Cocoa层:NSURL,Bonjour,Game Kit,WebKit Core Foundation层:基于 C 的CFNetwork 和 CFNetServices OS层:基于 C 的 BSD socket Cocoa层是最上层的基于 Objective-C 的 API,比如 URL访问,NSStream,Bonjour,GameKit等,这是大多数情况下我们常用的 API。Cocoa 层是基于 Core Foundation 实现的。Core Foundation层:因为直接使用 socket 需要更多的编程工作,所以苹果对 OS 层的 socket 进行简单的封装以简化编程任务。该层提供了 CFNetwork 和 CFNetServices,其中 CFNetwork 又是基于 CFStream 和 CFSocket。OS层:最底层的 BSD socket 提供了对网络编程最大程度的控制,但是编程工作也是最多的。因此,苹果建议我们使用 Core Foundation 及以上层的 API 进行编程。本文将介绍如何在 iOS 系统下使用最底层的 socket 进行编程,这和在 window 系统下使用 C/C+ 进行 socket 编程并无多大区别。本文源码:/kesalin/iOSSnippet/tree/master/KSNetworkDemo运行效果如下:二,BSD socket API 简介BSD socket API 和 winsock API 接口大体差不多,下面将列出比较常用的 API:API接口讲解int socket(int addressFamily, int type,int protocol)int close(int socketFileDescriptor)socket 创建并初始化 socket,返回该 socket 的文件描述符,如果描述符为 -1 表示创建失败。close 关闭 socket。通常参数 addressFamily 是 IPv4(AF_INET) 或 IPv6(AF_INET6)。type 表示 socket 的类型,通常是流stream(SOCK_STREAM) 或数据报文datagram(SOCK_DGRAM)。protocol 参数通常设置为0,以便让系统自动为选择我们合适的协议,对于 stream socket 来说会是 TCP 协议(IPPROTO_TCP),而对于 datagram来说会是 UDP 协议(IPPROTO_UDP)。int bind(int socketFileDescriptor,sockaddr *addressToBind,int addressStructLength)将 socket 与特定主机地址与端口号绑定,成功绑定返回0,失败返回 -1。成功绑定之后,根据协议(TCP/UDP)的不同,我们可以对 socket 进行不同的操作:UDP:因为 UDP 是无连接的,绑定之后就可以利用 UDP socket 传送数据了。TCP:而 TCP 是需要建立端到端连接的,为了建立 TCP 连接服务器必须调用 listen(int socketFileDescriptor, int backlogSize) 来设置服务器的缓冲区队列以接收客户端的连接请求,backlogSize 表示客户端连接请求缓冲区队列的大小。当调用 listen 设置之后,服务器等待客户端请求,然后调用下面的 accept 来接受客户端的连接请求。int accept(int socketFileDescriptor,sockaddr *clientAddress, intclientAddressStructLength)接受客户端连接请求并将客户端的网络地址信息保存到 clientAddress 中。当客户端连接请求被服务器接受之后,客户端和服务器之间的链路就建立好了,两者就可以通信了。int connect(int socketFileDescriptor,sockaddr *serverAddress, intserverAddressLength)客户端向特定网络地址的服务器发送连接请求,连接成功返回0,失败返回 -1。当服务器建立好之后,客户端通过调用该接口向服务器发起建立连接请求。对于 UDP 来说,该接口是可选的,如果调用了该接口,表明设置了该 UDP socket 默认的网络地址。对TCP socket来说这就是传说中三次握手建立连接发生的地方。注意:该接口调用会阻塞当前线程,直到服务器返回。hostent* gethostbyname(char *hostname)使用 DNS 查找特定主机名字对应的 IP 地址。如果找不到对应的 IP 地址则返回 NULL。int send(int socketFileDescriptor, char*buffer, int bufferLength, int flags)通过 socket 发送数据,发送成功返回成功发送的字节数,否则返回 -1。一旦连接建立好之后,就可以通过 send/receive 接口发送或接收数据了。注意调用 connect 设置了默认网络地址的 UDP socket 也可以调用该接口来接收数据。int receive(int socketFileDescriptor, char*buffer, int bufferLength, int flags)从 socket 中读取数据,读取成功返回成功读取的字节数,否则返回 -1。一旦连接建立好之后,就可以通过 send/receive 接口发送或接收数据了。注意调用 connect 设置了默认网络地址的 UDP socket 也可以调用该接口来发送数据。int sendto(int socketFileDescriptor,char *buffer, int bufferLength, intflags, sockaddr *destinationAddress, intdestinationAddressLength)通过UDP socket 发送数据到特定的网络地址,发送成功返回成功发送的字节数,否则返回 -1。由于 UDP 可以向多个网络地址发送数据,所以可以指定特定网络地址,以向其发送数据。int recvfrom(int socketFileDescriptor,char *buffer, int bufferLength, intflags, sockaddr *fromAddress, int*fromAddressLength)从UDP socket 中读取数据,并保存发送者的网络地址信息,读取成功返回成功读取的字节数,否则返回 -1。由于 UDP 可以接收来自多个网络地址的数据,所以需要提供额外的参数,以保存该数据的发送者身份。三,服务器工作流程有了上面的 socket API 讲解,下面来总结一下服务器的工作流程。1. 服务器调用 socket(.) 创建socket; 2. 服务器调用 listen(.) 设置缓冲区; 3. 服务器通过 accept(.)接受客户端请求建立连接; 4. 服务器与客户端建立连接之后,就可以通过 send(.)/receive(.)向客户端发送或从客户端接收数据; 5. 服务器调用 close 关闭 socket; 由于 iOS 设备通常是作为客户端,因此在本文中不会用代码来演示如何建立一个iOS服务器,但可以参考前文:深入浅出Cocoa之Bonjour网络编程看看如何在 Mac 系统下建立桌面服务器。四,客户端工作流程由于 iOS 设备通常是作为客户端,下文将演示如何编写客户端代码。先来总结一下客户端工作流程。1. 客户端调用 socket(.)创建socket; 2. 客户端调用 connect(.) 向服务器发起连接请求以建立连接; 3. 客户端与服务器建立连接之后,就可以通过 send(.)/receive(.)向客户端发送或从客户端接收数据; 4. 客户端调用 close 关闭 socket; 五,客户端代码示例下面的代码就实现了上面客户端的工作流程:- (void)loadDataFromServerWithURL:(NSURL *)url NSString * host = url host; NSNumber * port = url port; / Create socket / int socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0); if (-1 = socketFileDescriptor) NSLog(Failed to create socket.); return; / Get IP address from host / struct hostent * remoteHostEnt = gethostbyname(host UTF8String); if (NULL = remoteHostEnt) close(socketFileDescriptor); self networkFailedWithErrorMessage:Unable to resolve the hostname of the warehouse server.; return; struct in_addr * remoteInAddr = (struct in_addr *)remoteHostEnt-h_addr_list0; / Set the socket parameters / struct sockaddr_in socketParameters; socketParameters.sin_family = AF_INET; socketParameters.sin_addr = *remoteInAddr; sock

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论