版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.06.09 |
前言
CFNetwork框架访问网络服务并处理网络配置的变化。 建立在网络协议抽象的基础上,可以简化诸如使用BSD套接字,管理HTTP和FTP服务器以及管理Bonjour服务等任务。接下来几篇我们就一起看一下这个框架。感兴趣的可以看上面几篇文章。
1. CFNetwork框架详细解析(一) —— 基本概览
2. CFNetwork框架详细解析(二) —— CFNetwork编程指导之简介(一)
3. CFNetwork框架详细解析(三) —— CFNetwork编程指导之CFNetwork概念(二)
4. CFNetwork框架详细解析(四) —— CFNetwork编程指导之流的处理(三)
5. CFNetwork框架详细解析(五) —— CFNetwork编程指导之与HTTP服务器通信(四)
6. CFNetwork框架详细解析(六) —— CFNetwork编程指导之与验证HTTP服务器通信(五)
Working with FTP Servers - 使用FTP服务器
本章介绍如何使用CFFTP API
的一些基本功能。 管理FTP事务是异步执行的,而管理文件传输是同步执行的。
Downloading a File - 下载文件
使用CFFTP
与使用CFHTTP
非常相似,因为它们都基于CFStream
。 与任何其他使用CFStream异步的API一样,使用CFFTP下载文件需要为该文件创建读取流,并为该读取流创建回调函数。 当读取流接收到数据时,回调函数将运行,您将需要适当地下载字节。 此过程通常应分两部分执行:一个用于设置流,一个用作回调函数。
1. Setting Up the FTP Streams - 设置FTP流
首先使用CFReadStreamCreateWithFTPURL
函数创建读取流,并将要在远程服务器下载的文件的URL字符串传递给改函数。 一个URL字符串的例子可能是ftp://ftp.example.com/file.txt
。 请注意,该字符串包含服务器名称,路径和文件。 接下来,为要下载文件的本地位置创建一个写入流。 这是通过使用CFWriteStreamCreateWithFile
函数完成的,将文件将被下载的路径传给它。
由于写入流和读取流需要保持同步,因此创建一个包含所有常用信息的结构(如代理字典,文件大小,写入的字节数,剩下的字节和一个缓冲区。 这个结构可能如Listing 5-1
所示
Listing 5-1 A stream structure
typedef struct MyStreamInfo {
CFWriteStreamRef writeStream;
CFReadStreamRef readStream;
CFDictionaryRef proxyDict;
SInt64 fileSize;
UInt32 totalBytesWritten;
UInt32 leftOverByteCount;
UInt8 buffer[kMyBufferSize];
} MyStreamInfo;
使用刚刚创建的读取流和写入流来初始化您的结构。然后,您可以定义流客户端上下文(CFStreamClientContext)
的info
字段以指向您的结构。这将在稍后变得有用。
使用CFWriteStreamOpen函数打开您的写入流,以便您可以开始写入本地文件。为了确保流正确打开,调用函数CFWriteStreamGetStatus
并检查它是否返回kCFStreamStatusOpen
或kCFStreamStatusOpening
。
在写入流打开的情况下,将回调函数与读取流相关联。调用函数CFReadStreamSetClient并传递读取流,回调函数应该接收的网络事件,回调函数的名称和CFStreamClientContext
对象。通过早先设置流客户端上下文的info
字段,您的结构现在将在运行时发送到您的回调函数。
某些FTP服务器可能需要用户名,有些可能还需要密码。如果您正在访问的服务器需要用户名进行身份验证,请调用CFReadStreamSetProperty函数并传递读取流kCFStreamPropertyFTPUserName
作为属性,并引用包含用户名的CFString
对象。另外,如果您需要设置密码,请设置kCFStreamPropertyFTPPassword
属性。
某些网络配置也可能使用FTP代理。您可以通过不同的方式获取代理信息,具体取决于您的代码是否在OS X或iOS中运行。
- 在OS X中,您可以通过调用SCDynamicStoreCopyProxies函数并将其传递为
NULL
来检索字典中的代理设置。 - 在iOS中,您可以通过调用CFNetworkCopyProxiesForURL来检索代理设置。
这些函数返回一个动态存储引用。您可以使用此值设置读取流的kCFStreamPropertyFTPProxy
属性。这将设置代理服务器,指定端口,并返回一个布尔值,指示是否为FTP流实施被动模式。
除了提到的属性之外,还有一些可用于FTP流的其他属性。完整的清单如下。
-
kCFStreamPropertyFTPUserName
- 用于登录的用户名(可设置和可检索;不设置匿名FTP连接) -
kCFStreamPropertyFTPPassword
- 用于登录的密码(可设置和可检索;不设置匿名FTP连接) -
kCFStreamPropertyFTPUsePassiveMode
- 是否使用被动模式(可设置和可检索) -
kCFStreamPropertyFTPResourceSize
- 正在下载的项目的预期大小(如果可用)(可检索;仅适用于FTP读取流) -
kCFStreamPropertyFTPFetchResourceInfo
- 开始下载之前是否需要资源信息(如大小)(可设置和可检索);设置此属性可能会影响性能 -
kCFStreamPropertyFTPFileTransferOffset
- 开始传输的文件偏移量(可设置和可检索) -
kCFStreamPropertyFTPAttemptPersistentConnection
- 是否尝试重用连接(可设置和可检索) -
kCFStreamPropertyFTPProxy
- 包含代理字典(可设置和可检索)的键值对的CFDictionary
类型 -
kCFStreamPropertyFTPProxyHost
- FTP代理主机的名称(可设置和可检索) -
kCFStreamPropertyFTPProxyPort
- FTP代理主机的端口号(可设置和可检索)
将正确的属性分配给读取流后,使用CFReadStreamOpen
函数打开流。假设这不会返回错误,所有的流都已正确设置。
2. Implementing the Callback Function - 实现回调函数
您的回调函数将接收三个参数:读取流,事件类型和MyStreamInfo
结构体。事件的类型决定了必须采取的行动。
最常见的事件是kCFStreamEventHasBytesAvailable
,它在读取流从服务器接收到字节时发送。首先,通过调用CFReadStreamRead
函数来检查已读取的字节数。确保返回值不小于零(一个错误),或等于零(下载已完成)。如果返回值为正值,则可以开始将读取流中的数据通过写入流写入磁盘。
调用CFWriteStreamWrite
函数将数据写入写入流。有时CFWriteStreamWrite
可以返回而无需从读取流中写入所有数据。出于这个原因,只要还有数据要写入,就建立一个循环来运行。这个循环的代码在Listing 5-2
中,其中info
是来自Setting up the Streams的MyStreamInfo
结构。这种写入写入流的方法使用阻塞流。您可以通过驱动写入流事件来实现更好的性能,但代码更复杂。
Listing 5-2 Writing data to a write stream from the read stream
bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize);
//...make sure bytesRead > 0 ...
bytesWritten = 0;
while (bytesWritten < bytesRead) {
CFIndex result;
result = CFWriteStreamWrite(info->writeStream, info->buffer + bytesWritten, bytesRead - bytesWritten);
if (result <= 0) {
fprintf(stderr, "CFWriteStreamWrite returned %ld\n", result);
goto exit;
}
bytesWritten += result;
}
info->totalBytesWritten += bytesWritten;
只要读取流中有可用的字节,就重复这整个过程。
另外两个需要监听的事件是kCFStreamEventErrorOccurred
和kCFStreamEventEndEncountered
。 如果发生错误,请使用CFReadStreamGetError
检索错误,然后退出。 如果文件结束,那么你的下载已经完成,你可以退出。
一切完成后,请确保删除所有流,并且没有其他进程正在使用流。 首先,关闭写入流并将客户端设置为NULL
。 然后从运行循环中取消调度流并释放它。 完成后,从运行循环中移除流。
Uploading a File - 上传文件
上传文件与下载文件类似。与下载文件一样,您需要读取流和写入流。但是,上传文件时,读取流将用于本地文件,写入流将用于远程文件。按照Setting up the Streams中的说明进行操作,但无论它指向读取流的任何位置,将代码调整为写入流,反之亦然。
在回调函数中,而不是查找kCFStreamEventHasBytesAvailable
事件,现在查找事件kCFStreamEventCanAcceptBytes
。首先,使用读取流从文件中读取字节,并将数据放入MyStreamInfo
的缓冲区中。然后,运行CFWriteStreamWrite
函数将缓冲区中的字节推送到写入流中。CFWriteStreamWrite
返回已写入流的字节数。如果写入流的字节数少于从文件读取的字节数,则计算剩余字节数并将其存回缓冲区。在下一个写周期期间,如果有剩余字节,请将它们写入写入流,而不是从读取流中载入新数据。只要写入流可以接受字节(CFWriteStreamCanAcceptBytes)
,就重复这整个过程。在Listing 5-3
的代码中看到这个循环。
Listing 5-3 Writing data to the write stream
do {
// Check for leftover data
if (info->leftOverByteCount > 0) {
bytesRead = info->leftOverByteCount;
} else {
// Make sure there is no error reading from the file
bytesRead = CFReadStreamRead(info->readStream, info->buffer,
kMyBufferSize);
if (bytesRead < 0) {
fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
goto exit;
}
totalBytesRead += bytesRead;
}
// Write the data to the write stream
bytesWritten = CFWriteStreamWrite(info->writeStream, info->buffer, bytesRead);
if (bytesWritten > 0) {
info->totalBytesWritten += bytesWritten;
// Store leftover data until kCFStreamEventCanAcceptBytes event occurs again
if (bytesWritten < bytesRead) {
info->leftOverByteCount = bytesRead - bytesWritten;
memmove(info->buffer, info->buffer + bytesWritten,
info->leftOverByteCount);
} else {
info->leftOverByteCount = 0;
}
} else {
if (bytesWritten < 0)
fprintf(stderr, "CFWriteStreamWrite returned %ld\n", bytesWritten);
break;
}
} while (CFWriteStreamCanAcceptBytes(info->writeStream));
与下载文件时一样,也要考虑kCFStreamEventErrorOccurred
和kCFStreamEventEndEncountered
事件
Creating a Remote Directory - 创建一个远程目录
要在远程服务器上创建目录,请设置写入流,就好像您要上传文件一样。 但是,为传递给CFWriteStreamCreateWithFTPURL
函数的CFURL
对象提供目录路径而不是文件。 用正斜杠结束路径。 例如,正确的目录路径是ftp://ftp.example.com/newDirectory/
,而不是ftp://ftp.example.com/newDirectory/newFile.txt
。 当回调函数由运行循环执行时,它会发送事件kCFStreamEventEndEncountered
,这意味着该目录已经创建(或者如果出错,则为kCFStreamEventErrorOccurred
)。
每次调用CFWriteStreamCreateWithFTPURL
时,只能创建一级目录。 另外,只有在服务器上拥有正确的权限时才会创建目录。
Downloading a Directory Listing - 下载目录列表
通过FTP下载目录列表与下载或上传文件稍有不同。这是因为传入的数据必须被解析。首先,设置一个读取流来获取目录列表。这应该像下载文件一样完成:创建流,注册回调函数,使用运行循环调度流(如有必要,设置用户名,密码和代理信息),最后打开流。在下面的示例中,当检索目录列表时,不需要读取和写入流,因为传入的数据将进入屏幕而不是文件。
在回调函数中,请监听kCFStreamEventHasBytesAvailable
事件。在从读取流中加载数据之前,请确保在上次运行回调函数时流中没有剩余数据。加载MyStreamInfo
结构的leftOverByteCount
字段的偏移量。然后,从流中读取数据,并考虑刚刚计算的偏移量。读取的缓冲区大小和字节数也应计算在内。这一切都在Listing 5-4
中完成。
Listing 5-4 Loading data for a directory listing
// If previous call had unloaded data
int offset = info->leftOverByteCount;
// Load data from the read stream, accounting for the offset
bytesRead = CFReadStreamRead(info->readStream, info->buffer + offset,
kMyBufferSize - offset);
if (bytesRead < 0) {
fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
break;
} else if (bytesRead == 0) {
break;
}
bufSize = bytesRead + offset;
totalBytesRead += bufSize;
数据读入缓冲区后,设置一个循环来解析数据。解析的数据不一定是整个目录列表;它可能(也可能会)是列表的大块。使用函数CFFTPCreateParsedResourceListing
创建循环来解析数据,该函数应传递数据缓冲区,缓冲区大小和字典引用。它返回解析的字节数。只要这个值大于零,就继续循环。CFFTPCreateParsedResourceListing
创建的字典包含所有的目录列表信息;有关密钥的更多信息可在Setting up the Streams中找到。
CFFTPCreateParsedResourceListing
可能会返回正值,但不会创建解析字典。例如,如果列表的末尾包含无法分析的信息,则CFFTPCreateParsedResourceListing
将返回一个正值以告知调用方数据已被使用。但是,CFFTPCreateParsedResourceListing
不会创建解析字典,因为它无法理解数据。
如果创建了解析字典,请重新计算读取的字节数和缓冲区大小,如Listing 5-5
所示。
Listing 5-5 Loading the directory listing and parsing it
do
{
bufRemaining = info->buffer + totalBytesConsumed;
bytesConsumed = CFFTPCreateParsedResourceListing(NULL, bufRemaining,
bufSize, &parsedDict);
if (bytesConsumed > 0) {
// Make sure CFFTPCreateParsedResourceListing was able to properly
// parse the incoming data
if (parsedDict != NULL) {
// ...Print out data from parsedDict...
CFRelease(parsedDict);
}
totalBytesConsumed += bytesConsumed;
bufSize -= bytesConsumed;
info->leftOverByteCount = bufSize;
} else if (bytesConsumed == 0) {
// This is just in case. It should never happen due to the large buffer size
info->leftOverByteCount = bufSize;
totalBytesRead -= info->leftOverByteCount;
memmove(info->buffer, bufRemaining, info->leftOverByteCount);
} else if (bytesConsumed == -1) {
fprintf(stderr, "CFFTPCreateParsedResourceListing parse failure\n");
// ...Break loop and cleanup...
}
} while (bytesConsumed > 0);
当流没有更多可用字节时,清理所有流并将它们从运行循环中删除。
后记
本篇主要讲述了使用FTP服务器,感兴趣的给个赞或者关注~~~