ios网络编程.docx
《ios网络编程.docx》由会员分享,可在线阅读,更多相关《ios网络编程.docx(24页珍藏版)》请在冰豆网上搜索。
ios网络编程
iOS网络编程实践--NSStream实现TCPSocketiPhone客户端
客户端我们使用iPhone应用程序,画面比较简单。
点击发送按钮,给服务器发送一些字符串过去。
点击接收按钮就会从服务器读取一些字符串,并且显示在画面上。
有关客户端应用的UI部分不再介绍了,我们直接看代码部分,Socket客户端可以采用CFStream或NSStream实现,CFStream实现方式与服务器端基本一样。
为了给读者介绍更多的知识,本例我们采用NSStream实现。
NSStream实现采用Objective-C语言,一些面向对象的类。
下面我们看看客户端视图控制器ViewController.h
#import
#include
#include
#definePORT9000
@interfaceViewController:
UIViewController
{
intflag;//操作标志0为发送1为接收
}
@property(nonatomic,retain)NSInputStream*inputStream;
@property(nonatomic,retain)NSOutputStream*outputStream;
@property(weak,nonatomic)IBOutletUILabel*message;
-(IBAction)sendData:
(id)sender;
-(IBAction)receiveData:
(id)sender;
@end
定义属性inputStream和outputStream,它们输入流NSInputStream和输出流NSOutputStream类。
它们与服务器CFStream实现中的输入流CFReadStreamRef和输出流CFWriteStreamRef对应的。
视图控制器ViewController.m的初始化网络方法initNetworkCommunication代码:
-(void)initNetworkCommunication
{
CFReadStreamRefreadStream;
CFWriteStreamRefwriteStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)@”192.168.1.103″,PORT,&readStream,&writeStream);①
_inputStream=(__bridge_transferNSInputStream*)readStream;②
_outputStream=(__bridge_transferNSOutputStream*)writeStream;③
[_inputStreamsetDelegate:
self];④
[_outputStreamsetDelegate:
self];⑤
[_inputStreamscheduleInRunLoop:
[NSRunLoopcurrentRunLoop]
forMode:
NSDefaultRunLoopMode];⑥
[_outputStreamscheduleInRunLoop:
[NSRunLoopcurrentRunLoop]
forMode:
NSDefaultRunLoopMode];⑦
[_inputStreamopen];⑧
[_outputStreamopen];⑨
}
点击发送和接收按钮触发的方法如下:
/*点击发送按钮*/
-(IBAction)sendData:
(id)sender{
flag=0;
[selfinitNetworkCommunication];
}
/*点击接收按钮*/
-(IBAction)receiveData:
(id)sender{
flag=1;
[selfinitNetworkCommunication];
}
它们都调用initNetworkCommunication方法,并设置操作标识flag,如果flag为0发送数据,flag为1接收数据。
流的状态的变化触发很多事件,并回调NSStreamDelegate协议中定义的方法stream:
handleEvent:
,其代码如下:
-(void)stream:
(NSStream*)theStreamhandleEvent:
(NSStreamEvent)streamEvent{
NSString*event;
switch(streamEvent){
caseNSStreamEventNone:
event=@”NSStreamEventNone”;
break;
caseNSStreamEventOpenCompleted:
event=@”NSStreamEventOpenCompleted”;
break;
caseNSStreamEventHasBytesAvailable:
event=@”NSStreamEventHasBytesAvailable”;
if(flag==1&&theStream==_inputStream){
NSMutableData*input=[[NSMutableDataalloc]init];
uint8_tbuffer[1024];①
intlen;
while([_inputStreamhasBytesAvailable])②
{
len=[_inputStreamread:
buffermaxLength:
sizeof(buffer)];③
if(len>0)
{
[inputappendBytes:
bufferlength:
len];
}
}
NSString*resultstring=[[NSStringalloc]
initWithData:
inputencoding:
NSUTF8StringEncoding];
NSLog(@”接收:
%@”,resultstring);
_message.text=resultstring;
}
break;
caseNSStreamEventHasSpaceAvailable:
event=@”NSStreamEventHasSpaceAvailable”;
if(flag==0&&theStream==_outputStream){
//输出
UInt8buff[]=”HelloServer!
”;④
[_outputStreamwrite:
buffmaxLength:
strlen((constchar*)buff)+1];⑤
//关闭输出流
[_outputStreamclose];
}
break;
caseNSStreamEventErrorOccurred:
event=@”NSStreamEventErrorOccurred”;
[selfclose];⑥
break;
caseNSStreamEventEndEncountered:
event=@”NSStreamEventEndEncountered”;
NSLog(@”Error:
%d:
%@”,[[theStreamstreamError]code],
[[theStreamstreamError]localizedDescription]);
break;
default:
[selfclose];⑦
event=@”Unknown”;
break;
}
NSLog(@”event——%@”,event);
}
在读取数据分支(NSStreamEventHasBytesAvailable)中,代码第①行为读取数据准备缓冲区,本例中设置的是1024个字节,这个大小会对流的读取有很多的影响。
第②行代码使用hasBytesAvailable方法判断是否流有数据可以读,如果有可读数据就进行循环读取。
第③行代码使用流的read:
maxLength:
方法读取数据到缓冲区,第1个参数是缓冲区对象buffer,第2个参数是读取的缓冲区的字节长度。
在写入数据分支(NSStreamEventHasSpaceAvailable)中,代码第④行是要写入的数据,第⑤行代码[_outputStream write:
buff maxLength:
strlen((const char*)buff)+1]是写如数据方法。
第⑥和第⑦行代码[self close]调用close方法关闭,close方法代码如下:
-(void)close
{
[_outputStreamclose];
[_outputStreamremoveFromRunLoop:
[NSRunLoopcurrentRunLoop]
forMode:
NSDefaultRunLoopMode];
[_outputStreamsetDelegate:
nil];
[_inputStreamclose];
[_inputStreamremoveFromRunLoop:
[NSRunLoopcurrentRunLoop]
forMode:
NSDefaultRunLoopMode];
[_inputStreamsetDelegate:
nil];
}
[深入浅出Cocoa]iOS网络编程之CFNetwork
[深入浅出Cocoa]iOS网络编程之CFNetwork
罗朝辉(
本文遵循“署名-非商业用途-保持一致”创作公用协议
一,CFNetwork简介
首先来回顾下。
在前文《[深入浅出Cocoa]iOS网络编程之Socket》中,提到iOS网络编程层次模型分为三层:
∙Cocoa层:
NSURL,Bonjour,GameKit,WebKit
∙CoreFoundation层:
基于C的 CFNetwork和CFNetServices
∙OS层:
基于C的BSDsocket
前文讲的是最底层的socket,本文将介绍位于CoreFoundation中的CFNetwork。
CFNetwork只是对BSDsocket的进行了轻量级的封装,但在iOS中使用CFNetwork有一个显著的好处,那就是CFNetwork与系统级别的设置(如:
天线设置)以及run-loop结合得很好。
每一个线程都有自己的run-loop,因此我们可以CFNetwork当中事件源加入到run-loop中,这样就可以在线程的run-loop中处理网络事件了。
BTW,大名鼎鼎的ASIHttpRequest库就是基于CFNetwork封装的。
本文示例代码就是这样做的,源码请查看:
二,CFNetworkAPI简介
CFNetwork接口是基于C的,下面的接口用于创建一对socketstream,一个用于读取,一个用于写入:
voidCFStreamCreatePairWithSocketToHost(CFAllocatorRefalloc,CFStringRefhost,UInt32port,CFReadStreamRef*readStream,CFWriteStreamRef*writeStream);
该函数使用host以及port,CFNetwork会将该host转换为IP地址,并转换为网络字节顺序。
如果我们只需要一个socketstream,我们可以将另外一个设置为NULL。
还有另外两个“重载”的创建socketsream的接口:
CFStreamCreatePairWithSocket和 CFStreamCreatePairWithPeerSocketSignature,在这里就不一一介绍了。
注意:
这些socketstream在使用之前就如原生socket一样,必须显式地调用其open函数:
BooleanCFReadStreamOpen(CFReadStreamRefstream);
BooleanCFWriteStreamOpen(CFWriteStreamRefstream);
但与socket不同的是,这两个接口是异步的,当成功open之后,如果调用方设置了获取 kCFStreamEventOpenCompleted事件的标志的话就会其调用回调函数。
而该回调函数及其参数设置是通过如下接口进行的:
BooleanCFReadStreamSetClient(CFReadStreamRefstream,CFOptionFlagsstreamEvents,CFReadStreamClientCallBackclientCB,CFStreamClientContext*clientContext);
BooleanCFWriteStreamSetClient(CFWriteStreamRefstream,CFOptionFlagsstreamEvents,CFWriteStreamClientCallBackclientCB,CFStreamClientContext*clientContext);
该函数用于设置回调函数及相关参数。
通过 streamEvents标志来设置我们对哪些事件感兴趣;clientCB是一个回调函数,当事件标志对应的事件发生时,该回调函数就会被调用;clientContext是用于传递参数到回调函数中去。
当设置好回调函数之后,我们可以将socketstream当做事件源调度到run-loop中去,这样run-loop就能分发该socketstream的网络事件了。
voidCFReadStreamScheduleWithRunLoop(CFReadStreamRefstream,CFRunLoopRefrunLoop,CFStringRefrunLoopMode);
voidCFWriteStreamScheduleWithRunLoop(CFWriteStreamRefstream,CFRunLoopRefrunLoop,CFStringRefrunLoopMode);
注意,在我们不再关心该socketstream的网络事件时,记得要调用如下接口将socketstream从run-loop的事件源中移除。
voidCFReadStreamUnscheduleFromRunLoop(CFReadStreamRefstream,CFRunLoopRefrunLoop,CFStringRefrunLoopMode);
voidCFWriteStreamUnscheduleFromRunLoop(CFWriteStreamRefstream,CFRunLoopRefrunLoop,CFStringRefrunLoopMode);
当我们将socketstream的网络事件调度到run-loop之后,我们就能在回调函数中相应各种事件,比如 kCFStreamEventHasBytesAvailable 读取数据:
BooleanCFReadStreamHasBytesAvailable(CFReadStreamRefstream);
CFIndexCFReadStreamRead(CFReadStreamRefstream,UInt8*buffer,CFIndexbufferLength);
或 kCFStreamEventCanAcceptBytes写入数据:
BooleanCFWriteStreamCanAcceptBytes(CFWriteStreamRefstream);
CFIndexCFWriteStreamWrite(CFWriteStreamRefstream,constUInt8*buffer,CFIndexbufferLength);
最后,我们调用close方法关闭socketstream:
voidCFReadStreamClose(CFReadStreamRefstream);
voidCFWriteStreamClose(CFWriteStreamRefstream);
三,客户端示例代码
与socket演示类似,在这里我只演示客户端示例。
同样,我们也在一个后台线程中启动网络操作:
NSURL*url=[NSURLURLWithString:
[NSStringstringWithFormat:
@"%@:
%@",serverHost,serverPort]];
NSThread*backgroundThread=[[NSThreadalloc]initWithTarget:
self
selector:
@selector(loadDataFromServerWithURL:
)
object:
url];
[backgroundThreadstart];
然后在 loadDataFromServerWithURL中创建socket流,并设置其回调函数,将其加入到run-loop的事件源中,然后启动之:
-(void)loadDataFromServerWithURL:
(NSURL*)url
{
NSString*host=[urlhost];
NSIntegerport=[[urlport]integerValue];
//Keepareferencetoselftouseforcontrollercallbacks
//
CFStreamClientContextctx={0,(__bridgevoid*)(self),NULL,NULL,NULL};
//Getcallbacksforstreamdata,streamend,andanyerrors
//
CFOptionFlagsregisteredEvents=(kCFStreamEventHasBytesAvailable|kCFStreamEventEndEncountered|kCFStreamEventErrorOccurred);
//Createaread-onlysocket
//
CFReadStreamRefreadStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,(__bridgeCFStringRef)host,port,&readStream,NULL);
//Schedulethestreamontherunlooptoenablecallbacks
//
if(CFReadStreamSetClient(readStream,registeredEvents,socketCallback,&ctx)){
CFReadStreamScheduleWithRunLoop(readStream,CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
else{
[selfnetworkFailedWithErrorMessage:
@"Failedtoassigncallbackmethod"];
return;
}
//Openthestreamforreading
//
if(CFReadStreamOpen(readStream)==NO){
[selfnetworkFailedWithErrorMessage:
@"Failedtoopenreadstream"];
return;
}
CFErrorReferror=CFReadStreamCopyError(readStream);
if(error!
=NULL){
if(CFErrorGetCode(error)!
=0){
NSString*errorInfo=[NSStringstringWithFormat:
@"Failedtoconnectstream;error'%@'(code%ld)",(__bridgeNSString*)CFErrorGetDomain(error),CFErrorGetCode(error)];
[selfnetworkFailedWithErrorMessage:
errorInfo];
}
CFRelease(error);
return;
}
NSLog(@"Successfullyconnectedto%@",url);
//Startprocessing
//
CFRunLoopRun();
}
参考前面的接口说明,相信你不难理解上面的代码。
前面唯一没有提到的接口就是 CFReadStreamCopyError,该接口用于获取当前的错误信息,如果没有错误则返回NULL。
CFErrorRefCFReadStreamCopyError(CFReadStreamRefstream);
CFErrorRefCFWriteStreamCopyError(CFWriteStreamRefstream);
此外,我们还可以调用如下接口获取socketstream的当前状态:
CFStreamStatusCFReadStreamGetStatus(CFReadStreamRefstream);
CFStreamStatusCFWriteStreamGetStatus(CFWriteStreamRefstream);
在上面的代码中,我们设置了当有数据可以读取,流到达结尾处时以及错误发生时调用回调函数 socketCallback:
voidsocketCallback(CFReadStreamRefstream,CFStreamEventTypeevent,void*myPtr)
{
KSCFNetworkViewController*controller=(__bridgeKSCF