版本記錄
| 版本號(hào) | 時(shí)間 |
|---|---|
| V1.0 | 2018.06.09 |
前言
CFNetwork框架訪問網(wǎng)絡(luò)服務(wù)并處理網(wǎng)絡(luò)配置的變化。 建立在網(wǎng)絡(luò)協(xié)議抽象的基礎(chǔ)上,可以簡(jiǎn)化諸如使用BSD套接字,管理HTTP和FTP服務(wù)器以及管理Bonjour服務(wù)等任務(wù)。接下來幾篇我們就一起看一下這個(gè)框架。感興趣的可以看上面幾篇文章。
1. CFNetwork框架詳細(xì)解析(一) —— 基本概覽
2. CFNetwork框架詳細(xì)解析(二) —— CFNetwork編程指導(dǎo)之簡(jiǎn)介(一)
3. CFNetwork框架詳細(xì)解析(三) —— CFNetwork編程指導(dǎo)之CFNetwork概念(二)
4. CFNetwork框架詳細(xì)解析(四) —— CFNetwork編程指導(dǎo)之流的處理(三)
5. CFNetwork框架詳細(xì)解析(五) —— CFNetwork編程指導(dǎo)之與HTTP服務(wù)器通信(四)
6. CFNetwork框架詳細(xì)解析(六) —— CFNetwork編程指導(dǎo)之與驗(yàn)證HTTP服務(wù)器通信(五)
Working with FTP Servers - 使用FTP服務(wù)器
本章介紹如何使用CFFTP API的一些基本功能。 管理FTP事務(wù)是異步執(zhí)行的,而管理文件傳輸是同步執(zhí)行的。
Downloading a File - 下載文件
使用CFFTP與使用CFHTTP非常相似,因?yàn)樗鼈兌蓟?code>CFStream。 與任何其他使用CFStream異步的API一樣,使用CFFTP下載文件需要為該文件創(chuàng)建讀取流,并為該讀取流創(chuàng)建回調(diào)函數(shù)。 當(dāng)讀取流接收到數(shù)據(jù)時(shí),回調(diào)函數(shù)將運(yùn)行,您將需要適當(dāng)?shù)叵螺d字節(jié)。 此過程通常應(yīng)分兩部分執(zhí)行:一個(gè)用于設(shè)置流,一個(gè)用作回調(diào)函數(shù)。
1. Setting Up the FTP Streams - 設(shè)置FTP流
首先使用CFReadStreamCreateWithFTPURL函數(shù)創(chuàng)建讀取流,并將要在遠(yuǎn)程服務(wù)器下載的文件的URL字符串傳遞給改函數(shù)。 一個(gè)URL字符串的例子可能是ftp://ftp.example.com/file.txt。 請(qǐng)注意,該字符串包含服務(wù)器名稱,路徑和文件。 接下來,為要下載文件的本地位置創(chuàng)建一個(gè)寫入流。 這是通過使用CFWriteStreamCreateWithFile函數(shù)完成的,將文件將被下載的路徑傳給它。
由于寫入流和讀取流需要保持同步,因此創(chuàng)建一個(gè)包含所有常用信息的結(jié)構(gòu)(如代理字典,文件大小,寫入的字節(jié)數(shù),剩下的字節(jié)和一個(gè)緩沖區(qū)。 這個(gè)結(jié)構(gòu)可能如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;
使用剛剛創(chuàng)建的讀取流和寫入流來初始化您的結(jié)構(gòu)。然后,您可以定義流客戶端上下文(CFStreamClientContext)的info字段以指向您的結(jié)構(gòu)。這將在稍后變得有用。
使用CFWriteStreamOpen函數(shù)打開您的寫入流,以便您可以開始寫入本地文件。為了確保流正確打開,調(diào)用函數(shù)CFWriteStreamGetStatus并檢查它是否返回kCFStreamStatusOpen或kCFStreamStatusOpening。
在寫入流打開的情況下,將回調(diào)函數(shù)與讀取流相關(guān)聯(lián)。調(diào)用函數(shù)CFReadStreamSetClient并傳遞讀取流,回調(diào)函數(shù)應(yīng)該接收的網(wǎng)絡(luò)事件,回調(diào)函數(shù)的名稱和CFStreamClientContext對(duì)象。通過早先設(shè)置流客戶端上下文的info字段,您的結(jié)構(gòu)現(xiàn)在將在運(yùn)行時(shí)發(fā)送到您的回調(diào)函數(shù)。
某些FTP服務(wù)器可能需要用戶名,有些可能還需要密碼。如果您正在訪問的服務(wù)器需要用戶名進(jìn)行身份驗(yàn)證,請(qǐng)調(diào)用CFReadStreamSetProperty函數(shù)并傳遞讀取流kCFStreamPropertyFTPUserName作為屬性,并引用包含用戶名的CFString對(duì)象。另外,如果您需要設(shè)置密碼,請(qǐng)?jiān)O(shè)置kCFStreamPropertyFTPPassword屬性。
某些網(wǎng)絡(luò)配置也可能使用FTP代理。您可以通過不同的方式獲取代理信息,具體取決于您的代碼是否在OS X或iOS中運(yùn)行。
- 在OS X中,您可以通過調(diào)用SCDynamicStoreCopyProxies函數(shù)并將其傳遞為
NULL來檢索字典中的代理設(shè)置。 - 在iOS中,您可以通過調(diào)用CFNetworkCopyProxiesForURL來檢索代理設(shè)置。
這些函數(shù)返回一個(gè)動(dòng)態(tài)存儲(chǔ)引用。您可以使用此值設(shè)置讀取流的kCFStreamPropertyFTPProxy屬性。這將設(shè)置代理服務(wù)器,指定端口,并返回一個(gè)布爾值,指示是否為FTP流實(shí)施被動(dòng)模式。
除了提到的屬性之外,還有一些可用于FTP流的其他屬性。完整的清單如下。
-
kCFStreamPropertyFTPUserName- 用于登錄的用戶名(可設(shè)置和可檢索;不設(shè)置匿名FTP連接) -
kCFStreamPropertyFTPPassword- 用于登錄的密碼(可設(shè)置和可檢索;不設(shè)置匿名FTP連接) -
kCFStreamPropertyFTPUsePassiveMode- 是否使用被動(dòng)模式(可設(shè)置和可檢索) -
kCFStreamPropertyFTPResourceSize- 正在下載的項(xiàng)目的預(yù)期大小(如果可用)(可檢索;僅適用于FTP讀取流) -
kCFStreamPropertyFTPFetchResourceInfo- 開始下載之前是否需要資源信息(如大小)(可設(shè)置和可檢索);設(shè)置此屬性可能會(huì)影響性能 -
kCFStreamPropertyFTPFileTransferOffset- 開始傳輸?shù)奈募屏浚稍O(shè)置和可檢索) -
kCFStreamPropertyFTPAttemptPersistentConnection- 是否嘗試重用連接(可設(shè)置和可檢索) -
kCFStreamPropertyFTPProxy- 包含代理字典(可設(shè)置和可檢索)的鍵值對(duì)的CFDictionary類型 -
kCFStreamPropertyFTPProxyHost- FTP代理主機(jī)的名稱(可設(shè)置和可檢索) -
kCFStreamPropertyFTPProxyPort- FTP代理主機(jī)的端口號(hào)(可設(shè)置和可檢索)
將正確的屬性分配給讀取流后,使用CFReadStreamOpen函數(shù)打開流。假設(shè)這不會(huì)返回錯(cuò)誤,所有的流都已正確設(shè)置。
2. Implementing the Callback Function - 實(shí)現(xiàn)回調(diào)函數(shù)
您的回調(diào)函數(shù)將接收三個(gè)參數(shù):讀取流,事件類型和MyStreamInfo結(jié)構(gòu)體。事件的類型決定了必須采取的行動(dòng)。
最常見的事件是kCFStreamEventHasBytesAvailable,它在讀取流從服務(wù)器接收到字節(jié)時(shí)發(fā)送。首先,通過調(diào)用CFReadStreamRead函數(shù)來檢查已讀取的字節(jié)數(shù)。確保返回值不小于零(一個(gè)錯(cuò)誤),或等于零(下載已完成)。如果返回值為正值,則可以開始將讀取流中的數(shù)據(jù)通過寫入流寫入磁盤。
調(diào)用CFWriteStreamWrite函數(shù)將數(shù)據(jù)寫入寫入流。有時(shí)CFWriteStreamWrite可以返回而無需從讀取流中寫入所有數(shù)據(jù)。出于這個(gè)原因,只要還有數(shù)據(jù)要寫入,就建立一個(gè)循環(huán)來運(yùn)行。這個(gè)循環(huán)的代碼在Listing 5-2中,其中info是來自Setting up the Streams的MyStreamInfo結(jié)構(gòu)。這種寫入寫入流的方法使用阻塞流。您可以通過驅(qū)動(dòng)寫入流事件來實(shí)現(xiàn)更好的性能,但代碼更復(fù)雜。
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;
只要讀取流中有可用的字節(jié),就重復(fù)這整個(gè)過程。
另外兩個(gè)需要監(jiān)聽的事件是kCFStreamEventErrorOccurred和kCFStreamEventEndEncountered。 如果發(fā)生錯(cuò)誤,請(qǐng)使用CFReadStreamGetError檢索錯(cuò)誤,然后退出。 如果文件結(jié)束,那么你的下載已經(jīng)完成,你可以退出。
一切完成后,請(qǐng)確保刪除所有流,并且沒有其他進(jìn)程正在使用流。 首先,關(guān)閉寫入流并將客戶端設(shè)置為NULL。 然后從運(yùn)行循環(huán)中取消調(diào)度流并釋放它。 完成后,從運(yùn)行循環(huán)中移除流。
Uploading a File - 上傳文件
上傳文件與下載文件類似。與下載文件一樣,您需要讀取流和寫入流。但是,上傳文件時(shí),讀取流將用于本地文件,寫入流將用于遠(yuǎn)程文件。按照Setting up the Streams中的說明進(jìn)行操作,但無論它指向讀取流的任何位置,將代碼調(diào)整為寫入流,反之亦然。
在回調(diào)函數(shù)中,而不是查找kCFStreamEventHasBytesAvailable事件,現(xiàn)在查找事件kCFStreamEventCanAcceptBytes。首先,使用讀取流從文件中讀取字節(jié),并將數(shù)據(jù)放入MyStreamInfo的緩沖區(qū)中。然后,運(yùn)行CFWriteStreamWrite函數(shù)將緩沖區(qū)中的字節(jié)推送到寫入流中。CFWriteStreamWrite返回已寫入流的字節(jié)數(shù)。如果寫入流的字節(jié)數(shù)少于從文件讀取的字節(jié)數(shù),則計(jì)算剩余字節(jié)數(shù)并將其存回緩沖區(qū)。在下一個(gè)寫周期期間,如果有剩余字節(jié),請(qǐng)將它們寫入寫入流,而不是從讀取流中載入新數(shù)據(jù)。只要寫入流可以接受字節(jié)(CFWriteStreamCanAcceptBytes),就重復(fù)這整個(gè)過程。在Listing 5-3的代碼中看到這個(gè)循環(huán)。
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));
與下載文件時(shí)一樣,也要考慮kCFStreamEventErrorOccurred和kCFStreamEventEndEncountered事件
Creating a Remote Directory - 創(chuàng)建一個(gè)遠(yuǎn)程目錄
要在遠(yuǎn)程服務(wù)器上創(chuàng)建目錄,請(qǐng)?jiān)O(shè)置寫入流,就好像您要上傳文件一樣。 但是,為傳遞給CFWriteStreamCreateWithFTPURL函數(shù)的CFURL對(duì)象提供目錄路徑而不是文件。 用正斜杠結(jié)束路徑。 例如,正確的目錄路徑是ftp://ftp.example.com/newDirectory/,而不是ftp://ftp.example.com/newDirectory/newFile.txt。 當(dāng)回調(diào)函數(shù)由運(yùn)行循環(huán)執(zhí)行時(shí),它會(huì)發(fā)送事件kCFStreamEventEndEncountered,這意味著該目錄已經(jīng)創(chuàng)建(或者如果出錯(cuò),則為kCFStreamEventErrorOccurred)。
每次調(diào)用CFWriteStreamCreateWithFTPURL時(shí),只能創(chuàng)建一級(jí)目錄。 另外,只有在服務(wù)器上擁有正確的權(quán)限時(shí)才會(huì)創(chuàng)建目錄。
Downloading a Directory Listing - 下載目錄列表
通過FTP下載目錄列表與下載或上傳文件稍有不同。這是因?yàn)閭魅氲臄?shù)據(jù)必須被解析。首先,設(shè)置一個(gè)讀取流來獲取目錄列表。這應(yīng)該像下載文件一樣完成:創(chuàng)建流,注冊(cè)回調(diào)函數(shù),使用運(yùn)行循環(huán)調(diào)度流(如有必要,設(shè)置用戶名,密碼和代理信息),最后打開流。在下面的示例中,當(dāng)檢索目錄列表時(shí),不需要讀取和寫入流,因?yàn)閭魅氲臄?shù)據(jù)將進(jìn)入屏幕而不是文件。
在回調(diào)函數(shù)中,請(qǐng)監(jiān)聽kCFStreamEventHasBytesAvailable事件。在從讀取流中加載數(shù)據(jù)之前,請(qǐng)確保在上次運(yùn)行回調(diào)函數(shù)時(shí)流中沒有剩余數(shù)據(jù)。加載MyStreamInfo結(jié)構(gòu)的leftOverByteCount字段的偏移量。然后,從流中讀取數(shù)據(jù),并考慮剛剛計(jì)算的偏移量。讀取的緩沖區(qū)大小和字節(jié)數(shù)也應(yīng)計(jì)算在內(nèi)。這一切都在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;
數(shù)據(jù)讀入緩沖區(qū)后,設(shè)置一個(gè)循環(huán)來解析數(shù)據(jù)。解析的數(shù)據(jù)不一定是整個(gè)目錄列表;它可能(也可能會(huì))是列表的大塊。使用函數(shù)CFFTPCreateParsedResourceListing創(chuàng)建循環(huán)來解析數(shù)據(jù),該函數(shù)應(yīng)傳遞數(shù)據(jù)緩沖區(qū),緩沖區(qū)大小和字典引用。它返回解析的字節(jié)數(shù)。只要這個(gè)值大于零,就繼續(xù)循環(huán)。CFFTPCreateParsedResourceListing創(chuàng)建的字典包含所有的目錄列表信息;有關(guān)密鑰的更多信息可在Setting up the Streams中找到。
CFFTPCreateParsedResourceListing可能會(huì)返回正值,但不會(huì)創(chuàng)建解析字典。例如,如果列表的末尾包含無法分析的信息,則CFFTPCreateParsedResourceListing將返回一個(gè)正值以告知調(diào)用方數(shù)據(jù)已被使用。但是,CFFTPCreateParsedResourceListing不會(huì)創(chuàng)建解析字典,因?yàn)樗鼰o法理解數(shù)據(jù)。
如果創(chuàng)建了解析字典,請(qǐng)重新計(jì)算讀取的字節(jié)數(shù)和緩沖區(qū)大小,如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);
當(dāng)流沒有更多可用字節(jié)時(shí),清理所有流并將它們從運(yùn)行循環(huán)中刪除。
后記
本篇主要講述了使用FTP服務(wù)器,感興趣的給個(gè)贊或者關(guān)注~~~
