基于IOS的FTP详解(三)下载和断点续传
来源:互联网 发布:网络拓扑结构图软件 编辑:程序博客网 时间:2024/09/21 09:24
现在讲述一下下载和断点续传:
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 CFReadStreamCreateWithFTPURL
function 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 CFWriteStreamCreateWithFile
function, 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 CFReadStreamSetClient
and pass the read stream, the network events your callback function should receive, the callback function's name and the CFStreamClientContext
object. By having earlier set the info
field of the stream client context, your structure will now be sent to your callback function whenever it is run.
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
CFReadStreamSetProperty
function and pass the read stream, kCFStreamPropertyFTPUserName
for the property, and a reference to a CFString
object containing the user name. In addition, if you need to set a password, set the kCFStreamPropertyFTPPassword
property.当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 32768void 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)
项目中我们在不同的设备上面去分别搭建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详解(三)下载和断点续传
- ftp和http断点续传及下载的Delphi实现
- ftp和http断点续传及下载的Delphi实现
- 关于ftp和http下载断点续传
- 基于IOS的FTP详解(四)上传
- iOS NSURLSession后台下载和断点续传
- ftp和http断点续传及下载delphi实现
- FTP文件下载与断点续传
- Http/FTP多线程断点续传下载
- 基于IOS的FTP详解(一)获取列表
- 基于IOS的FTP详解(二)创建目录
- 基于IOS的FTP详解(五)删除文件或者目录
- ios 实现简单的断点续传下载 nsurlconnection
- IOS 基于HTTP协议的断点续传
- 基于iOS 10、realm封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)
- 基于iOS 10封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)
- iOS 文件下载断点续传
- iOS下载数据-断点续传
- C#中获取当前时间:System.DateTime.Now.ToString()用法
- android的原理,为什么不需要手动关闭程序
- sizeof
- Cocosbuilder3.x使用
- acm-Not That Kind of Graph-uva10800
- 基于IOS的FTP详解(三)下载和断点续传
- 数字整除
- JAVA对象转换为JSON字符串
- MFC的CListBox控件使用LBS_OWNERDRAWVARIABLE风格不能AddString的问题
- 变量 地址 值
- MySQL数据库常用操作语句大全
- 设计模式 - 代理模式(proxy pattern) 未使用代理模式 详解
- Caused by: java.lang.ClassNotFoundException: org.jbpm.pvm.internal.processengine.SpringHelper
- Flyweight 设计模式