基于IOS的FTP详解(三)下载和断点续传
2014-07-12 14:00
429 查看
现在讲述一下下载和断点续传:
Downloading a File
Using CFFTP is very similar to using CFHTTP because they are both based on CFStream. As with any other API that uses CFStream asynchronously,
downloading a file with CFFTP requires that you create a read stream for the file, and a callback function for that read stream. When the read stream receives data, the callback function will be run and you will need to appropriately download the bytes. This
procedure should normally be performed using two functions: one to set up the streams and one to act as the callback function.
Setting Up the FTP Streams
Begin by creating a read stream using the
and passing it the URL string of the file to be downloaded on the remote server. An example of a URL string might be
the path, and the file. Next, create a write stream for the local location where the file will be downloaded. This is accomplished using the
passing the path where the file will be downloaded.
先实现第一个功能:设置读写流
先设置输出流,这个输出流通过本地的一个文件路径来创建,从ftp下载的字节流将会写到这个输出流中,我们不用把它加入到当前运行时循环中,最后要打开流才能往里面写数据
With the write stream open, associate a callback function with the read stream. Call the function
pass the read stream, the network events your callback function should receive, the callback function's name and the
By having earlier set the
function whenever it is run.
然后我们创建通过ftp文件的路径链接来创建输入流,这里我们没有创建结构体参数,我们直接把当前对象传给回调函数:
Some FTP servers may require a user name, and some may also require a password. If the server you are accessing needs a user name for authentication, call
the
and pass the read stream,
containing the user name. In addition, if you need to set a password, set the
当ftp服务器需要登录用户名和密码的时候,我们可以通过完整的链接来直接完成我们的请求,比如说ftp://admin:admin@192.168.0.1/
也可以通过如下方法来设置用户名和密码:
FTP connections)
FTP connections)
这个时候我们就不用在请求链接里面去格式化用户名和密码了。
现在实现第二个功能:实现回调
当kCFStreamEventHasBytesAvailable事件到达的时候,我们可以从输入流读取数据到缓冲区中,由于我们不知道读取数据是否能够一次写到输出流中去,因此我们总要开启一个循环来读取缓冲去中的数据直到全部读完。
这里有一个读写流获取和设置属性的方法:
这里在获取到kCFStreamEventOpenCompleted事件的时候我们可以通过kCFStreamPropertyFTPResourceSize属性来获取请求文件的大小,这里有个开关kCFStreamPropertyFTPFetchResourceInfo,我们必须打开它才能获取到请求文件的大小,不过设置这个选项会影响到程序的性能,因此我们可以通过另外一种方法来实现,还记得我们在获取列表的时候解析出来的每一条数据都有一个kCFFTPResourceSize字段,我们可以通过这个保存文件的大小
available only for FTP read streams)
before starting a download (settable and retrievable); setting this property may impact performance
这里还有一个属性方法可以实现断点续传:
我们通过kCFStreamPropertyFTPFileTransferOffset属性来设置我们要获取ftp请求文件的偏移字节位置,这个字段我们是通过本地已经下载文件的大小获取的,我们还要通过kCFStreamPropertyAppendToFile属性为kCFBooleanTrue来设置输出流是从文件末尾开始写数据;
项目中我们在不同的设备上面去分别搭建ftp服务器,通过app会来回切换到不同的设备,于是就会出现问题,比如说,我ios设备首先连接到第一个FTP主机,然后获取列表,在不退出APP的情况下,我再连接到第二个FTP主机,他们有相同的IP地址或者IP地址不一样,那么我再次获取列表,就会获取不到,就像我之前说的,控制连接是个长连接,它这里有个属性如下,默认是kCFBooleanTrue,我再次获取列表的时候由于它是默认使用之前的连接状态信息,因此我们就会操作失败,我们将它设置为kCFBooleanFalse就可以了,这样可以关闭持续的连接,网络环境切换的时候会重新创建连接,每次通过读流读取数据的时候都会是新的连接。
to try to reuse connections (settable and retrievable)
以上我们可以通过抓包看到:我们设置该属性为kCFBooleanTrue的时候,同时获取两个列表信息是在同一个控制连接上面,kCFBooleanFalse的时候,我们同时获取两个列表的时候是分别在两个控制连接上面,客户端两个端口分别和ftp服务器的21端口建立连接,第一个列表获取完数据以后会向服务器发送RST标示,断开连接:
demo地址:http://download.csdn.net/detail/junjun150013652/7629007
参考:
《CFNetwork Programming Guide:Working with FTP Servers》
Downloading a File
Using CFFTP is very similar to using CFHTTP because they are both based on CFStream. As with any other API that uses CFStream asynchronously,
downloading a file with CFFTP requires that you create a read stream for the file, and a callback function for that read stream. When the read stream receives data, the callback function will be run and you will need to appropriately download the bytes. This
procedure should normally be performed using two functions: one to set up the streams and one to act as the callback function.
Setting Up the FTP Streams
Begin by creating a read stream using the
CFReadStreamCreateWithFTPURLfunction
and passing it the URL string of the file to be downloaded on the remote server. An example of a URL string might be
ftp://ftp.example.com/file.txt. Note that the string contains the server name,
the path, and the file. Next, create a write stream for the local location where the file will be downloaded. This is accomplished using the
CFWriteStreamCreateWithFilefunction,
passing the path where the file will be downloaded.
先实现第一个功能:设置读写流
先设置输出流,这个输出流通过本地的一个文件路径来创建,从ftp下载的字节流将会写到这个输出流中,我们不用把它加入到当前运行时循环中,最后要打开流才能往里面写数据
if (self.fileStream == nil) { self.fileStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, (__bridge CFURLRef)(url)); if (!CFWriteStreamOpen(self.fileStream)) { // CFStreamError myErr = CFWriteStreamGetError(myWriteStream); // An error has occurred. NSLog(@"CFWriteStreamOpen error"); return; } }
With the write stream open, associate a callback function with the read stream. Call the function
CFReadStreamSetClientand
pass the read stream, the network events your callback function should receive, the callback function's name and the
CFStreamClientContextobject.
By having earlier set the
infofield of the stream client context, your structure will now be sent to your callback
function whenever it is run.
然后我们创建通过ftp文件的路径链接来创建输入流,这里我们没有创建结构体参数,我们直接把当前对象传给回调函数:
readStream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef)resourceUrl); CFReadStreamSetProperty(readStream, kCFStreamPropertyFTPFetchResourceInfo, kCFBooleanTrue); CFStreamClientContext clientContext; clientContext.version = 0; clientContext.info = CFBridgingRetain(self) ; clientContext.retain = nil; clientContext.release = nil; clientContext.copyDescription = nil; if (CFReadStreamSetClient (readStream, kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable | kCFStreamEventCanAcceptBytes | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, myGetSocketReadCallBack, &clientContext ) ) { NSLog(@"Set read callBack Succeeded"); CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); } else { NSLog(@"Set read callBack Failed"); } BOOL success = CFReadStreamOpen(readStream); if (!success) { printf("stream open fail\n"); return; }
Some FTP servers may require a user name, and some may also require a password. If the server you are accessing needs a user name for authentication, call
the
CFReadStreamSetPropertyfunction
and pass the read stream,
kCFStreamPropertyFTPUserNamefor the property, and a reference to a
CFStringobject
containing the user name. In addition, if you need to set a password, set the
kCFStreamPropertyFTPPasswordproperty.
当ftp服务器需要登录用户名和密码的时候,我们可以通过完整的链接来直接完成我们的请求,比如说ftp://admin:admin@192.168.0.1/
也可以通过如下方法来设置用户名和密码:
kCFStreamPropertyFTPUserName— user name to use to log in (settable and retrievable; do not set for anonymous
FTP connections)
kCFStreamPropertyFTPPassword— password to use to log in (settable and retrievable; do not set for anonymous
FTP connections)
// user name to use to log in (settable and retrievable; do not set for anonymous FTP connections) CFReadStreamSetProperty(readStream , kCFStreamPropertyFTPUserName, @"ftp"); // password to use to log in (settable and retrievable; do not set for anonymous FTP connections) CFReadStreamSetProperty(readStream , kCFStreamPropertyFTPPassword, @"");
这个时候我们就不用在请求链接里面去格式化用户名和密码了。
现在实现第二个功能:实现回调
当kCFStreamEventHasBytesAvailable事件到达的时候,我们可以从输入流读取数据到缓冲区中,由于我们不知道读取数据是否能够一次写到输出流中去,因此我们总要开启一个循环来读取缓冲去中的数据直到全部读完。
#define BUFSIZE 32768 void myGetSocketReadCallBack (CFReadStreamRef stream, CFStreamEventType event, void *myPtr) { JUNFTPGetRequest* request = (__bridge JUNFTPGetRequest *)myPtr; CFNumberRef cfSize; UInt64 size; switch(event) { case kCFStreamEventOpenCompleted: cfSize = CFReadStreamCopyProperty(stream, kCFStreamPropertyFTPResourceSize); if (cfSize) { if (CFNumberGetValue(cfSize, kCFNumberLongLongType, &size)) { printf("File size is %llu\n", size); request.bytesTotal = size; } CFRelease(cfSize); } else { printf("File size is unknown.\n"); } break; case kCFStreamEventHasBytesAvailable: { UInt8 recvBuffer[BUFSIZE]; CFIndex bytesRead = CFReadStreamRead(stream, recvBuffer, BUFSIZE); printf("bytesRead:%ld\n",bytesRead); if (bytesRead > 0) { NSInteger bytesOffset = 0; do { CFIndex bytesWritten = CFWriteStreamWrite(request.fileStream, &recvBuffer[bytesOffset], bytesRead-bytesOffset ); if (bytesWritten > 0) { bytesOffset += bytesWritten; request.bytesDownloaded +=bytesWritten; request.progressBlock((float)request.bytesDownloaded/(float)request.bytesTotal); } else if (bytesWritten == 0) { break; } else { request.failBlock(); return; } }while ((bytesRead-bytesOffset)>0); } else if(bytesRead == 0) { request.finishedBlock(); [request stop]; } else { request.failBlock(); } } break; case kCFStreamEventErrorOccurred: { CFStreamError error = CFReadStreamGetError(stream); printf("kCFStreamEventErrorOccurred-%d\n",error.error); [request stop]; request.failBlock(); } break; case kCFStreamEventEndEncountered: printf("request finished\n"); request.finishedBlock(); [request stop]; break; default: break; } }
这里有一个读写流获取和设置属性的方法:
CFTypeRef CFReadStreamCopyProperty(CFReadStreamRef stream, CFStringRef propertyName); CFTypeRef CFWriteStreamCopyProperty(CFWriteStreamRef stream, CFStringRef propertyName); Boolean CFReadStreamSetProperty(CFReadStreamRef stream, CFStringRef propertyName, CFTypeRef propertyValue); Boolean CFWriteStreamSetProperty(CFWriteStreamRef stream, CFStringRef propertyName, CFTypeRef propertyValue);
这里在获取到kCFStreamEventOpenCompleted事件的时候我们可以通过kCFStreamPropertyFTPResourceSize属性来获取请求文件的大小,这里有个开关kCFStreamPropertyFTPFetchResourceInfo,我们必须打开它才能获取到请求文件的大小,不过设置这个选项会影响到程序的性能,因此我们可以通过另外一种方法来实现,还记得我们在获取列表的时候解析出来的每一条数据都有一个kCFFTPResourceSize字段,我们可以通过这个保存文件的大小
kCFStreamPropertyFTPResourceSize— the expected size of an item that is being downloaded, if available (retrievable;
available only for FTP read streams)
kCFStreamPropertyFTPFetchResourceInfo— whether to require that resource information, such as size, be required
before starting a download (settable and retrievable); setting this property may impact performance
这里还有一个属性方法可以实现断点续传:
kCFStreamPropertyFTPFileTransferOffset— file offset at which to start a transfer (settable and retrievable)
我们通过kCFStreamPropertyFTPFileTransferOffset属性来设置我们要获取ftp请求文件的偏移字节位置,这个字段我们是通过本地已经下载文件的大小获取的,我们还要通过kCFStreamPropertyAppendToFile属性为kCFBooleanTrue来设置输出流是从文件末尾开始写数据;
项目中我们在不同的设备上面去分别搭建ftp服务器,通过app会来回切换到不同的设备,于是就会出现问题,比如说,我ios设备首先连接到第一个FTP主机,然后获取列表,在不退出APP的情况下,我再连接到第二个FTP主机,他们有相同的IP地址或者IP地址不一样,那么我再次获取列表,就会获取不到,就像我之前说的,控制连接是个长连接,它这里有个属性如下,默认是kCFBooleanTrue,我再次获取列表的时候由于它是默认使用之前的连接状态信息,因此我们就会操作失败,我们将它设置为kCFBooleanFalse就可以了,这样可以关闭持续的连接,网络环境切换的时候会重新创建连接,每次通过读流读取数据的时候都会是新的连接。
kCFStreamPropertyFTPAttemptPersistentConnection—whether
to try to reuse connections (settable and retrievable)
以上我们可以通过抓包看到:我们设置该属性为kCFBooleanTrue的时候,同时获取两个列表信息是在同一个控制连接上面,kCFBooleanFalse的时候,我们同时获取两个列表的时候是分别在两个控制连接上面,客户端两个端口分别和ftp服务器的21端口建立连接,第一个列表获取完数据以后会向服务器发送RST标示,断开连接:
demo地址:http://download.csdn.net/detail/junjun150013652/7629007
参考:
《CFNetwork Programming Guide:Working with FTP Servers》
相关文章推荐
- 基于IOS的FTP详解(二)创建目录
- 基于IOS的FTP详解(五)删除文件或者目录
- 基于IOS的FTP详解(一)获取列表
- 基于IOS的FTP详解(四)上传
- FTP上传下载的断点续传实现
- c#基于ftp自动升级 自动更新 自动下载
- FTP文件下载与断点续传
- curl用ftp方式断点续传下载上传文件
- Delphi实现Ftp客户端下载(支持断点续传,多线程传输)
- FTP上传下载的断点续传实现
- (转)IOS开发网络篇之──ASIHTTPRequest下载示例(支持断点续传)
- [转]c#上传下载ftp(支持断点续传)
- IOS开发网络篇之──ASIHTTPRequest下载示例(支持断点续传)
- iOS 基于服务端的App下载打包教程
- IOS开发网络篇之──ASIHTTPRequest下载示例(支持断点续传)
- IOS开发网络篇之──ASIHTTPRequest下载示例(支持断点续传)
- FTP上传下载文件案例详解
- 在vb中轻松制作支持断点续传的FTP、HTTP下载软件
- c#上传下载ftp(支持断点续传)
- c#上传下载ftp(支持断点续传)