您的位置:首页 > 移动开发 > IOS开发

基于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
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.

然后我们创建通过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
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 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》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: