使用FTP(IOS FTP客户端开发教程)

2023-11-06

     本文翻译自新近Wrox出版社出版的,由Peter van de Put所著的《Professional.iOS.Programming》。该书题材比较新颖,结构合理,是一本不错的IOS开发书籍。本文译自该书第八章《Using FTP》。本文开放使用,不局限于转载、修改、增删,引用,请保留出处说明。禁止任何商业用途。欢迎任何修改建议。


本章有哪些内容?

Ø  理解文件传输协议

Ø  开发一个简单FTP客户端

Ø  实现网络流(Network streams)

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

The wrox.com code downloads for thischapter are found at www.wrox.com/go/proiosprog on the Download Code tab. Thecode is in the Chapter 8 download and individually named according to the namesthroughout the chapter.

在第七章,你已经学习了网络部分,包括从web服务器下载文件,向RESTful和SOAP服务器发送post请求。在某些情况下,你可能还需要处理视频之类的大文件,下载到你的应用或者上传到服务器进行处理。

你可以将图片或者PDF文件编码成base64字符串,然后发送给服务器,但是这不是十分高效、迅速的方法。创建base64字符串需要花费很长时间,然后你仍然要传输这些数据。

文件传输协议FTP是一个用于FTP服务器和客户端之间进行通信的协议。

 

开发一个FTP客户端

       为IOS应用开发一个FTP客户端的同时,你应当了解一些FTP协议的基本要素。客户端使用预先确定端口上的Internet连接来创建到服务器的连接。默认地,一个FTP系统配置使用以下端口:

Ø  端口20用于服务器创建到客户端的连接

Ø  端口21用于客户端创建到服务器的连接

 

译注:控制连接以通常的客户服务器方式建立。服务器以被动方式打开众所周知的用于F T P 的端口(21),等待客户的连接。客户则以主动方式打开TCP端口21,来建立连接。控制连接始终等待客户与服务器之间的通信。该连接将命令从客户传给服务器, 并传回服务器的应答。



       FTP服务器的配置可以有所不同,所以你同意FTP服务器管理员关于使用那个端口就很重要了。

       除了对某个端口号用于建立网络流达成一致外,FTP协议预定义了交换的命令,因此客户端可以告诉服务器它需要什么,反过来也一样。最常用的命令包括:

Ø  open:打开一个连接

Ø  close:关闭一个连接

Ø  get:从服务器拷贝一个文件到客户端(下载)

Ø  mget:从服务器拷贝多份文件到客户端(下载)

Ø  put:从客户端拷贝一个文件到服务端(上传)

Ø  mput:从客户端拷贝多份文件到服务端(上传)

Ø  delete:从当前远端目录删除一个文件

Ø  cd:改变服务器上的目录(由客户端发起)

Ø  lcd:改变客户端上的目录

Ø  mkdir或者mkd:在服务端创建一个目录(由客户端发起)

 

如果你以前从来没用过FTP客户端,你可以从http://filezilla-project.org/下载FileZilla项目。FileZilla是一个开源的FTP客户端和服务端,可用于分析在客户端和服务端两者之间交换的特定的命令。

       根据你应用的需求,你有两个基本的选项要考虑。如果你仅仅是从FTP服务端上传或者下载文件,你可以使用由CFNetwork提供的高层API。这个方案能力有限,确定地说不是一个完整的FTP客户端。

 

写一个简单的FTP客户端

           启动Xcode并创建一个使用Single View ApplicationProject模板的工程,命名为SimpleFTPClinet,使用表8-1所示选项。



将CFNetwork framework添加到你的工程。

       创建一个继承自NSObject、命名为FTPManager的类。打开FTPManager.h

文件,使用“#includes<CFNetwork/CFNetwork.h>”将CFNetworkframework包含进去。

       接着定义FTPManagerDelegate协议,该协议包括一些将向delegate提供反馈的方法。

       创建一个接受用于FTP连接的server, username, 以及password的初始化器。实现方面假定你总是需要一个username和password来建立FTP连接。因为打开一个公众的FTP服务端对于你应用的意图来说毫无意义。

       接下去,声明四个方法用于与FTP命令相关的、受支持的操作。如表8—1所示。


       最后,为该类的delegate创建一个共有property。

       完整的代码如清单8-1所示。

LISTING 8-1: Chapter8/SimpleFTPClient/FTPManager.h 
#import <Foundation/Foundation.h>
#include <CFNetwork/CFNetwork.h> 

enum {
kSendBufferSize = 32768 
};

@protocol FTPManagerDelegate <NSObject>

-(void)ftpUploadFinishedWithSuccess:(BOOL)success;
 -(void)ftpDownloadFinishedWithSuccess:(BOOL)success;
- -(void)directoryListingFinishedWithSuccess:(NSArray *)arr; 
-(void)ftpError:(NSString *)err;


@end


@interface FTPManager : NSObject<NSStreamDelegate>

- (id)initWithServer:(NSString *)server user:(NSString *)username
password:(NSString *)pass;

- (void)downloadRemoteFile:(NSString *)filename localFileName:(NSString *)localname;

- (void)uploadFileWithFilePath:(NSString *)filePath;
- (void)createRemoteDirectory:(NSString *)dirname;

- (void)listRemoteDirectory;
@property (nonatomic, assign) id<FTPManagerDelegate> 
@end

       打开FTPManager.m文件,在import头文件语句后写入私有接口。

       因为你在为网络通信使用流(streams),所以你要用到两种不同的流:NSOutputStream和NSInputStream。

       这时候,理解FTP的put和mkd是你发送给服务端的命令,因此要用到NSOutputStream是很重要的。List和get命令通过打开的socket传送,并且作为回应,将收到你请求的数据。你需要去读取这些数据,因此你将用到NSInputStream。

       定义两个BOOL型properties(isReceiving 和isSending)用于跟踪流的状态,避免流操作间的数据混淆。

       你可以在本章的下载中找到FTPManager的完整实现。

       这里,TPManager.m的实现被分解开来,并且一步一步解释。第一步,用自定义初始化器初始化FTPManager对象,你可以向该初始化器传连接和认证所需的properties,如清单8-2所示。

LISTING 8-2: The initWithServer method
-(id)initWithServer:(NSString *)server user:(NSString *)username password:(NSString *)pass
{

if ((self = [super init]))
{

	self.ftpServer = server; 
self.ftpUsername=username; 
self.ftpPassword=pass;

}
return self;
 }

流处理的一个潜在问题是锁住。一个正在从一个流里写入或者读取的线程也许不得不无限期等待直到流里有空间进行写入,或者流里有可用数据(bytes)进行读取。为了克服这个问题,你需要一个接受NSStream作为参数的方法,并将它加入到当前NSRunLoop中进行调度安排(schedule),那样delegate就能只收到流相关事件报告的信息,那时锁住就不会发生。为了这个目的,写一个如清单8-3所示的简单helper方法。

NSRunLoop类声明了面向管理输入资源的对象的编程接口。一个NSRunLoop对象处理输入资源,例如窗口系统的鼠标和键盘事件,NSPort和NSConnetion对象。一个NSRunLoop对象也处理NSTimer事件。

你的应用既不能创建,也不能显式地管理NSrunLoop对象。每个NSThread对象,包括应用的主线程,出于需要,都有一个自动创建的NSRunLoop对象。假如你要访问当前线程的runloop,你使用类方法currentRunLoop。

LISTING 8-3: The scheduleInCurrentThread method
-	(void)scheduleInCurrentThread:(NSStream*)aStream 
{
[aStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
}

smartURLForString:方法接受一个字符串并将它转化为一个有效的NSURL对象。假如你传入一个类似127.0.0.1这样的IP地址,它返回一个ftp://127.0.0.1这样的NSURL对象。

       smartURLForString:如清单8-4所示。

LISTING 8-4: The smartURLForString method
-(NSURL *)smartURLForString:(NSString *)str
 {
NSURL * result; 
NSString * trimmedStr;
NSRange schemeMarkerRange;

NSString * scheme;

result = nil;

trimmedStr = [str stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceCharacterSet]];

	if ( (trimmedStr != nil) && ([trimmedStr length] != 0) ) {
 		schemeMarkerRange = [trimmedStr rangeOfString:@"://"];
if (schemeMarkerRange.location == NSNotFound) {

result = [NSURL URLWithString:[NSString stringWithFormat: @"ftp://%@", trimmedStr]];
} else {

scheme = [trimmedStr substringWithRange:
NSMakeRange(0, schemeMarkerRange.location)];
 if ( ([scheme compare:@"http" options:
NSCaseInsensitiveSearch] == NSOrderedSame) ) {
 result = [NSURL URLWithString:trimmedStr];
} else {
//unsupported url schema
} 
}
}
return result; 
}
isReceiving方法用来检查dataStream是否已初始化,isSending方法用来检查commandStream是否已初始化。这些方法用于网络通信中以避免在一条命令尚在处理中又执行另一条命令的情况。       这两个方法在清单8-5中。

<span style="font-family:'AvenirLTStd';font-size: 8.000000pt; font-weight: 800">LISTING 8-5: </span><span style="font-family:'AvenirLTStd';font-size: 10.000000pt; font-weight: 500">The isReceiving and isSending methods
</span>- (BOOL)isReceiving {
return (_dataStream != nil); 
}
- (BOOL)isSending {
return (_commandStream != nil); 
}

<span style="font-family:'AvenirLTStd';font-size: 8.000000pt; font-weight: 800">LISTING 8-6: </span><span style="font-family:'AvenirLTStd';font-size: 10.000000pt; font-weight: 500">The closeAll metho
</span>-(void)closeAll {
if (_commandStream != nil) { 
[_commandStream removeFromRunLoop:
[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
_commandStream.delegate = nil;
[_commandStream close];
_commandStream = nil;
}
if (_uploadStream != nil) {
[_uploadStream close];
_uploadStream = nil; }
if (_downloadfileStream != nil) { 
    [_downloadfileStream close]; 
    _downloadfileStream = nil;
}
if (_dataStream != nil) {
[_dataStream removeFromRunLoop:
[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_dataStream.delegate = nil; 
[_dataStream close]; 
_dataStream = nil;
}
_currentOperation = @""; 
}

下载一个远程文件

       你调用downloadRemoteFile:localFileName:方法,并传给它一个服务端文件的文件名,例如picture1.png,和一个本地文件名,来从FTP服务端下载一个文件。已下载的文件将会以传入的本地文件名命名,写入到你的应用的根目录下的临时目录。

       该方法先用“FTP服务端地址/远程文件名”生成一个smartURLForString,返回NSURL对象,例如ftp://127.0.0.1/picture1.png.假如isReceiving方法返回YES,代理方法ftpError被调用,并传入一个错误信息。否则,用已创建的路径初始化downloadStream。downloadStream被创建后,数据就可以从该流中读取,currentOperation property设置为GET。流代理(stream delegate)负责从不同的流中读取和写入数据,需要为GET命令(从流中读取数据,写入到一个文件)LIST命令(以流形式传送将要分析的目录列表)进行不同的操作。出于这个目的,你需要一个currentOperation的property。

注意:你应当使用FTP服务端地址代替127.0.0.1

       使用CFBridgingRelease( CFReadStreamCreateWithFTPURL(NULL,

(__bridge CFURLRef)url));传入一个早先创建的url,创建一个commandStream。因为在使用认证,你需要将传给FTPManager初始化器的用户名和密码设置给commandStream。最后将delegate设置为self。调用scheduleInCurrentThread:将commandStream调度安排到当前线程,并打开commandStream。

       当commandStream被打开,连接被建立,用户名和密码被用于认证该流。服务端响应在stream:handleEvent:方法中被捕获,该方法将在后面讲解,因为当前你在学习的其他方法也同样使用该方法进行结果处理。

       downloadRemoteFile:方法如清单8-7所示。

-(void)downloadRemoteFile:(NSString *)filename localFileName:(NSString *)localname 
{
BOOL success;

NSURL * url;

url = [self smartURLForString:[NSString stringWithFormat:
@"%@/%@",_ftpServer,filename]];
success = (url != nil); 
if ( ! success) {
[self.delegate ftpError:@"invalid url for downloadRemoteFile method"]; 
} else {
if (self.isReceiving){

[self.delegate ftpError:@"receiving in progress"]; 
return ;
		}

NSString *path = [NSTemporaryDirectory()
stringByAppendingPathComponent:localname];
_downloadfileStream = [NSOutputStream outputStreamToFileAtPath:
path append:NO];
 		[_downloadfileStream open];
_currentOperation = @"GET";
_dataStream=CFBridgingRelease( CFReadStreamCreateWithFTPURL(NULL,(__bridge CFURLRef) url)); 
[_dataStream  setProperty:_ftpUsername
forKey:(id)kCFStreamPropertyFTPUserName];
[_dataStream  setProperty:_ftpPassword 
forKey:(id)kCFStreamPropertyFTPPassword];
_dataStream.delegate = self;

[self performSelector:@selector(scheduleInCurrentThread:)
onThread:[[self class] networkThread]
 					withObject:_dataStream waitUntilDone:YES];
[_dataStream open];
}
}

创建一个远程目录

       你可以使用createRemotedirectory:方法在服务端创建一个目录。当然你提供的用于建立连接的证书需要有权限创建目录。实现方法和刚才你学习的下载文件的例子很相似。使用CFURLCreateCopyAppendingPathComponent将请求创建的目录名拼接到已创建的URL后面,形成的新的URL类似ftp://127.0.0.1/newdirname。因为你在发送一个命令到FTP服务端,commandStream被创建而不是dataStream,认证信息被传递,流被打开。同样地,这里也由stream:handleEvent:方法负责处理响应。createRemotedirectory:方法如清单8-8所示。

- (void)createRemoteDirectory:(NSString *)dirname
 {
BOOL success; NSURL * url;
url = [self smartURLForString:_ftpServer]; 
success = (url != nil);

if (success) {
url=CFBridgingRelease( CFURLCreateCopyAppendingPathComponent(NULL,(__bridge CFURLRef) url,
(__bridge CFStringRef) dirname, true) );
success = (url != nil); }
if ( ! success) {

[self.delegate ftpError:@"invalid url for createRemoteDirectory method"];
} else {

if (self.isSending){
	[self.delegate ftpError:@"sending in progress"];
return ; 
}
_		commandStream=CFBridgingRelease(
CFWriteStreamCreateWithFTPURL(NULL,
 (__bridge CFURLRef) url)
);
//set credentials

[_commandStream setProperty:_ftpUsername
forKey:(id)kCFStreamPropertyFTPUserName]; 
	[_commandStream setProperty:_ftpPassword
forKey:(id)kCFStreamPropertyFTPPassword]; 
	_commandStream.delegate = self;
[self performSelector:@selector(scheduleInCurrentThread:) 
onThread:[[self class] 
networkThread] withObject:_commandStream
waitUntilDone:YES];
[_commandStream open];
} 
}

列出一个远程目录

       listRemoteDirectory方法等同于FTP的list命令,该方法列出服务端某个目录下的内容。尽管一些RFC适用于FTP程序,但是FTP list命令的结果正如你所希望的那样,不是100%标准化的,因此它可以很容易解析到一个数组里。

       检查完你并没在dataStream上接收数据之后,你设置listDataproperty并初始化listEntries property。你将currentOperation property设置为LIST,以此区别GET和LIST正如前面解释的那样,调用CFReadStreamCreateWithFTPURL,使用创建的URL来初始化dataStream。

       再次地,设置dataStream的认证信息并将它调度安排并打开。

       为了能够解析LIST命令的结果,实现用于解析传入数据流并最终将结果写入listEntries property的三个helper方法。所有数据都被处理以后,directoryListingFinishedWithSuccess方法被调用,该方法返回一个数组,包含在FTP服务端上找到的文件。listRemoveDirectory方法如清单8-9所示。

- (void)listRemoteDirectory {
BOOL success;

NSURL * url;

url = [self smartURLForString:_ftpServer];
success = (url != nil);
if ( ! success) {
[self.delegate ftpError:@"invalid url for listRemoteDirectory method"]; 
} else {
if (self.isReceiving){

[self.delegate ftpError:@"receiving in progress"]; 
return ;
}

self.listData = [NSMutableData data];
if (self.listEntries) self.listEntries=nil;
self.listEntries=[[NSMutableArray alloc] init]; _
currentOperation = @"LIST";

self.dataStream = CFBridgingRelease(
CFReadStreamCreateWithFTPURL(NULL,
(__bridge CFURLRef) url)); 
//set credentials
[self.dataStream setProperty: self.ftpUsername
 					forKey:(id)kCFStreamPropertyFTPUserName];
[self.dataStream setProperty: self.ftpPassword 
					forKey:(id)kCFStreamPropertyFTPPassword];
self.dataStream.delegate = self;

	[self performSelector:@selector(scheduleInCurrentThread:)
onThread:[[self class] networkThread] 
withObject:_dataStream 
waitUntilDone:YES];
[self.dataStream open];
 }
}

下面的方法来自苹果公司的例子,并且已经清理过。因为从FTP服务端得到的响应并不是100%标准化的,所以你可以使用苹果公司提供的解析示例,该示例在大多数服务端实现上能工作,并呈现给你一个包含返回的文件信息的数组。解析helper方法如清单8-10所示

#pragma listing helpers
		-  (void)addListEntries:(NSArray *)newEntries 
{ 
[ self.listEntries addObjectsFromArray:newEntries];

[self closeAll];

[self.delegate directoryListingFinishedWithSuccess: self.listEntries]; 

}

//this function is taken over from Apple samples 
		-  (NSDictionary *)entryByReencodingNameInEntry:(NSDictionary *)
entry encoding:(NSStringEncoding)newEncoding 
{
NSDictionary  * result;
NSString  *name;
NSData * nameData;
NSString  *newName;
newName = nil;
// Try to get the name, convert it back to MacRoman, and then reconvert it
// with the preferred encoding.

name = [entry objectForKey:(id) kCFFTPResourceName]; 
if (name != nil) {
nameData = [name 
			dataUsingEncoding:NSMacOSRomanStringEncoding];
 if (nameData != nil) {
newName = [[NSString alloc]

initWithData:nameData encoding:newEncoding];
} 
}
if (newName == nil) {

result = (NSDictionary *) entry;
} else {

NSMutableDictionary * newEntry;
newEntry = [entry mutableCopy];

[newEntry setObject:newName forKey:(id) kCFFTPResourceName];
 result = newEntry;
}
return result; 
}

//also this function is taken over from Apple samples 
- (void)parseListData

{
NSMutableArray * newEntries; 
NSUInteger offset; 
newEntries = [NSMutableArray array];
offset = 0;
do {

CFIndex bytesConsumed;

CFDictionaryRef thisEntry;

thisEntry = NULL;

bytesConsumed = CFFTPCreateParsedResourceListing(NULL, &((const uint8_t *)self.listData.bytes)[offset],
(CFIndex) ([self.listData length] - offset), &thisEntry);
if (bytesConsumed > 0) {

if (thisEntry != NULL) {
NSDictionary * entryToAdd;

entryToAdd = [self entryByReencodingNameInEntry:
(__bridge NSDictionary *) thisEntry 
encoding:NSUTF8StringEncoding];
[newEntries addObject:entryToAdd]; }
// We consume the bytes regardless of whether we get an entry. 
offset += (NSUInteger) bytesConsumed;
}

if (thisEntry != NULL) {
CFRelease(thisEntry); 
}
if (bytesConsumed == 0) {

// We hven't yet got enough data to parse an entry. 
//Wait for more data to arrive
break;

} else if (bytesConsumed < 0) {
// We totally failed to parse the listing. Fail. 
break;
}

} while (YES);
if ([newEntries count] != 0) {

[self addListEntries:newEntries];
}

if (offset != 0) {
[self.listData replaceBytesInRange:NSMakeRange(0, offset) 
								withBytes:NULL length:0];
} 
}

结果数组会提供类似如下的信息:

{
kCFFTPResourceGroup = ftp; 
kCFFTPResourceLink = "";
kCFFTPResourceModDate = "2013-03-22 14:34:00 +0000"; 
kCFFTPResourceMode = 420;

kCFFTPResourceName = "1.jpg";

kCFFTPResourceOwner = ftp;
kCFFTPResourceSize = 5855; 
kCFFTPResourceType = 8;
}
数组中的每个实体包含一个字典,字典中元素含义解释如表8-2所示





上传一个文件

       为上传一个文件到FTP服务端,你可以使用uploadFileWithFilePath:方法,向其传送一个你想上传的文件的fileath。

       你可以使用CFURLCreateCopyAppendingPathComponent在smartURLString后面拼接传入的filePath的lastPathComponent,以形成新的URL。

       uploadStream使用filePathPath创建并打开,那样就能使用NSInputStream读取文件。接着调用CFWriteStreamCreateWithFTPURL函数,使用刚才创建的URL创建commandStream,并设置其认证信息。

       commandStream调度安排到NSRunLoop并打开,再次地,stream:handleEvent:负责处理响应。uploadFileWithFilePath方法如清单8-11所示。

- (void)uploadFileWithFilePath:(NSString *)filePath 
{
BOOL success;

NSURL * url;

url = [self smartURLForString:_ftpServer]; 
success = (url != nil);

if (success) {
url=CFBridgingRelease( CFURLCreateCopyAppendingPathComponent(NULL,( CFURLRef) url,
( CFStringRef) [filePath lastPathComponent], false));
success = (url != nil); }
if ( ! success) {

[self.delegate ftpError:@"invalid url for uploadFileWithFilePath method"];
} else {

if (self.isSending){
[self.delegate ftpError:@"sending in progress"];
return ;
 }
self.uploadStream = [NSInputStream 
							inputStreamWithFileAtPath:filePath];
[self.uploadStream open];

self.commandStream=CFBridgingRelease(CFWriteStreamCreateWithFTPURL(NULL,(__bridge CFURLRef) url));
 //set credentials
[self.commandStream setProperty:_ftpUsername
 								forKey:(id)kCFStreamPropertyFTPUserName];
[self.commandStream setProperty:_ftpPassword
 				forKey:(id)kCFStreamPropertyFTPPassword];
self.commandStream.delegate = self;

[self performSelector:@selector(scheduleInCurrentThread:)
onThread:[[self class] 
networkThread] 
withObject:self.commandStream 
waitUntilDone:YES];
[self.commandStream open]; 
}
}

正如前面提到的,stream:handleWithEvent:正是流通信魔法发生的地方。该实现首先使用switch语句来为各种可能发生的NSStreamEvent提供不同的处理。

 

 

 

从NSStream中读取

       你可以从清单8-12所示的实现中发现,假如eventCode是NSStreamEventHasBytesAvailable时,意味着有可读取的比特,你可以创建一个缓冲区,并从流中读取比特到缓冲区中。一旦所有的比特全都从流中读取完毕,根据当前操作,假如你正在执行listRemoteDirectory,你可以调用parseListData方法来解析结果,或者你正在下载一个文件,你可以调用parseListData方法来将它写到一个文件并存储。

 

 

 

写入NSStream

       当你想要写入一个NSStream,你需要检查事件是不是NSStreamEventHasSpaceAvailable,该事件仅在NSStream仍有可用空间用于写入时被调用。因为流被当前runloop所调度安排,所以当无可用写入空间时,线程不会锁住。现在你可以简单地从缓冲区向流写入比特。

为使用FTPManager,你要用FTP服务端的IP地址来初始化它,并且在viewDidLoad方法里提供可用的用户名和密码。

你实现你所要求的代理方法,创建你所需要的用户界面,在FTPManager实例中调用适当的函数,走你!

正如你已知的,你不应当在主线程运行网络操作。因此最好的方法是使用DISPATCH_QUEUE_PRIORITY_BACKGROUND在dispatch_queue里创建FTPManager,这样你的用户界面就不会被锁住。

一个实现例子如清单8-14所示。

LISTING 8-14: Chapter8/SimpleFTPClient/YDViewController.m 
#import "YDViewController.h"
@interface YDViewController ()
@end
@implementation YDViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t defQueue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(defQueue, ^{
ftpmanager=[[FTPManager alloc] initWithServer:@"YOUR_SERVERNAME"
user:@"YOUR_USERNAME" password:@"YOUR_PASSWORD!"];
ftpmanager.delegate=self;
});
}
 -(IBAction)uploadFile:(id)sender {
[ftpmanager listRemoteDirectory];
}
- (void)ftpDownloadFinishedWithSuccess:(BOOL)success {
if (!success) {
//handle your error
} 
}

-(void)ftpError:(NSString *)err
{
//handle your error
}

-(void)directoryListingFinishedWithSuccess:(NSArray *)arr {
//use the array the way you need it 
}

- (void)ftpUploadFinishedWithSuccess:(BOOL)success {
if (!success) {
//handle your error
} 
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; 
}
@end

你可以将创建的FTPManager类用于你第一章创建的应用框架,这样当你开发一个新应用时候它就可以用了。

 

 

 

写一个复杂的FTP客户端

            使用你创建的FTPManager,你可以进行基本的操作,例如下载上传一个文件,在远程服务端端创建一个目录,取回一个目录的清单,就这些。假如你想对FTP过程有更多控制或者想进行本地操作,那你就需要一个不同的方案,该方案在更低层实现FTP协议。在本例子中你将学到如何写一个复杂的FTP客户端,给你FTP操作和应答处理的完全控制。

            启动Xcode并创建一个Single ViewApplication Project模板的工程,命名为ComplexFTPClient使用如图8-2所示的选项。




作为第一步,再次添加CFNetwork到你的工程。

            创建一个继承自NSObject的新类,命名为YDFTPClient。创建YDFTPClientDelegate协议和公开方法如清单8-15所示。

LISTING 8-15: Chapter8/ComplexFTPClient/YDFTPClient.h
#import <Foundation/Foundation.h> 
@protocol YDFTPClientDelegate <NSObject>
-(void)logginFailed;
-(void)loggedOn;
-(void)serverResponseReceived:(NSString *)lastResponseCode
message:(NSString *)lastResponseMessage; -(void)ftpError:(NSString *)err;
@end
@interface YDFTPClient : NSObject<NSStreamDelegate>
@property (nonatomic, strong) id<YDFTPClientDelegate> delegate; 
@property (readonly) UInt64 numberOfBytesSent;
@property (readonly) UInt64 numberOfBytesReceived;
- (id)initClient;
-(void)sendRAWCommand:(NSString *)command;
-(void)connect;
-(void)disconnect;
@end

YDFTPClient.m包括由它的变量定义的私有接口和不同方法的实现。像前面工程那样,YDFTPClient中的关键逻辑是流。你可以在本章的下载中找到完整的YDFTPClient.m实现。这不是所有FTP命令和应答的100%实现,但是是一个你可以通过实现你需要的命令来扩展的骨骼类(skeleton class)。

            在YDFTPClient私有接口中创建properties,如清单8-16所示。

LISTING 8-16: Private Interface of the YDFTPClient class
@interface YDFTPClient() {
UInt64 numberOfBytesSent;
UInt64 numberOfBytesReceived;
int uploadbytesreadSoFar;
}
@property (readwrite, assign) NSString* dataIPAddress;
@property (readwrite, assign) UInt16 dataPort;
@property (nonatomic, assign, readonly ) uint8_t * buffer;
@property (nonatomic, assign, readwrite) size_t bufferOffset;
@property (nonatomic, assign, readwrite) size_t bufferLimit;
@property (nonatomic,assign) int lastResponseInt;
@property (nonatomic,assign) NSString* lastResponseCode; 
@property (nonatomic,assign) NSString* lastCommandSent; 
@property (nonatomic,assign) NSString* lastResponseMessage;
@property (nonatomic,retain, strong) NSInputStream *inputStream; 
@property (nonatomic, retain,strong) NSOutputStream *outputStream;
@property(nonatomic, retain,strong) NSInputStream *dataInStream; 
@property (nonatomic, retain,strong) NSOutputStream *dataOutStream;
@property (nonatomic,assign) BOOL isConnected;
@property (nonatomic,assign) BOOL loggedOn;
@property (nonatomic,assign) BOOL isDataStreamConfigured; 
@property (nonatomic,assign) BOOL isDataStreamAvailable;

@end

让我们来一步步分解该实现,initClient方法是你的将用于初始化本地变量和properties的自定义初始化器。该方法如清单8-17所示。

LISTING 8-17: The initClient method
-(id)initClient 
{
	if ((self = [super init]))
{
self.isConnected=NO; 
self.dataIPAddress=0; 
self.dataPort=0; 
self.isConnected=NO; 
self.isDataStreamAvailable=NO; 
self.lastCommandSent=@""; 
self.lastResponseCode=@"";
 	self.lastResponseMessage=@""; 
}
return self;
 }

你定义connect和disconnect方法。connect方法仅仅调用initNet-workCommunication:方法,disconnect方法调用logoff:方法,如清单8-18所示。

LISTING 8-18: The connect and disconnect methods
-(void)connect 
{
if (!self.isConnected)
[self initNetworkCommunication];
} 
-(void)disconnect 
{
if (self.isConnected) 
[self logoff];
}

scheduleInCurrentThread:方法和你前面的例子中的完全一样。initNetworkCommunication:方法被connect:方法调用,用于创建inputStream和outputStream,将它们安排调度到当前NSRunLoop,并打开,你可以在清单8-19中看到。

LISTING 8-19: The initNetworkCommunication method
- (void) initNetworkCommunication { 
CFReadStreamRef readStream; 
CFWriteStreamRef writeStream; 
CFStreamCreatePairWithSocketToHost(NULL,
(__bridge CFStringRef)kFTPServer, kFTPPort, &readStream, &writeStream);
self.inputStream = (__bridge_transfer NSInputStream *)readStream; 
self.outputStream = (__bridge_transfer NSOutputStream *)writeStream; 
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self performSelector:@selector(scheduleInCurrentThread:)
onThread:[[self class] networkThread]
 	withObject:self.inputStream
waitUntilDone:YES];
[self performSelector:@selector(scheduleInCurrentThread:)
onThread:[[self class] networkThread] 
withObject:self.outputStream
waitUntilDone:YES]; 
[self.inputStream open];
[self.outputStream open]; 
self.isConnected=YES; 
self.isDataStreamConfigured=NO;
}

stream:handleEvent:方法也在该实现中,是一个关键方法,包含了从流对象读取和写入的控制逻辑,你可以在清单8-20中看到。在NSStreamEventHasBytesAvailable事件中,你在缓冲区中读取来自流的比特,增加用于网络统计的numberOfBytesReceived property的值,发送读取输出到负责处理的messageReceived:方法中。

LISTING 8-20: The stream: handleEvent: method
- (void)stream:(NSStream *)theStream
 handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
break;
case NSStreamEventNone:
break;
case NSStreamEventHasBytesAvailable:
if (theStream == self.inputStream) {
uint8_t buffer[1024];
int len;
while ([self.inputStream hasBytesAvailable]) {
len = [self.inputStream read:buffer maxLength:sizeof(buffer)];
numberOfBytesReceived+=len; 
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer
length:len encoding:NSASCIIStringEncoding]; 
if (output) {
[self messageReceived:output]; 
}
}
}
}
else if (theStream == self.dataInStream) {
uint8_t buffer[8192];//8kB block 
int len;
while ([self.dataInStream hasBytesAvailable]) { 
len = [self.dataInStream read:buffer
maxLength:sizeof(buffer)]; 
					numberOfBytesReceived+=len;
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer
length:len 
encoding:NSASCIIStringEncoding];
if (output) {
[self messageReceived:output];
} 
}
} 
}
break;
case NSStreamEventHasSpaceAvailable:
if (theStream == self.dataOutStream) {
//write your custom code for upload and download
} 
break;
case NSStreamEventErrorOccurred:
[self.delegate ftpError:@"Network stream error occured"]; 
break;
case NSStreamEventEndEncountered: 
break;
} 
}

messageReceived方法是一个简单的、用于追踪最近一次来自服务端的响应,使用一个整形或者一个代码和消息,你可以去RFC协议中查阅应答代码。messageReceived:方法如清单8-21所示。

LISTING 8-21: The messageReceived: method
- (void) messageReceived:(NSString *)message { 
self.lastResponseCode = [message substringToIndex:3]; 
self.lastResponseMessage=message;
int response = [_lastResponseCode intValue]; 
self.lastResponseInt=response;
[self.delegate serverResponseReceived: 
self.lastResponseCode message:_lastResponseMessage];
	switch (response) { 
case 150:
//connection accepted break;
case 200:
[self sendCommand:@"PASV"];
case 220: //server welcome message so wait for username
[self sendUsername];
break; 
case 226:
//transfer OK break;
case 227:
[self acceptDataStreamConfiguration:message]; 
break;
case 230: //server logged in 
self.loggedOn=YES;
[self sendCommand:@"PASV"];
 	[self.delegate loggedOn]; break;
case 331: //server waiting for password 
[self sendPassword];
break;
case 530: //Login or passwod incorrect
[self.delegate logginFailed]; 
self.loggedOn=NO;
break;
default: 
break;
} 
}

代码中应注意到,出于安全目的,大多数客户端使用被动连接模式。因这个原因,当发送PASV命令到FTP服务端时,应答会包含将建立的、用于服务端和客户端数据交换的data socket的IP地址和端口号。

acceptDataStreamConfiguration方法负责使用正则表达式解析上述应答结果。前四组数字代表IP地址,后两组用于创建端口号。所以,例如应答是a.b.c.d.x.y,端口号用如下公式计算:(x * 256) + y。acceptDataStreamconfiguration方法如清单8-22所示。

LISTING 8-22: acceptDataStreamConfiguration method
-(void)acceptDataStreamConfiguration:(NSString*)serverResponse 
{ 	NSString *pattern=
@"([-\\d]+),([-\\d]+),([-\\d]+),([-\\d]+),([-\\d]+),([-\\d]+)"; 
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:pattern options:0
error:&error];
NSTextCheckingResult *match = [regex firstMatchInString: serverResponse
options:0 range:NSMakeRange(0, [serverResponse length])];
self.dataIPAddress = [NSString stringWithFormat:@"%@.%@.%@.%@", 
[serverResponse substringWithRange:[match rangeAtIndex:1]],
 			[serverResponse substringWithRange:[match rangeAtIndex:2]], 
[serverResponse substringWithRange:[match rangeAtIndex:3]],
   [serverResponse substringWithRange:[match rangeAtIndex:4]]];
 self.dataPort = ([[serverResponse substringWithRange:
[match rangeAtIndex:5]] intValue] * 256)+ 
[[serverResponse substringWithRange:[match rangeAtIndex:6]] intValue];
 	self.isDataStreamConfigured=YES;
[self openDataStream];
}

最后,你需要一些生命周期管理的代码在你的实现里,如清单8-23所示。openDataStream创建一个inputStream和outputStream并且使用scheduleInCurrentThread:方法将他们调度安排到当前runLoop。closeDataStream方法恰当地关闭并且移除数据流。

LISTING 8-23: openDataStream, closeDataStream, and logoff methods
-(void)openDataStream 
{
if (self.isDataStreamConfigured && !self.isDataStreamAvailable){ 
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(__bridge CFStringRef)self.dataIPAddress, 
self.dataPort, &readStream, &writeStream);
self.dataInStream = (__bridge_transfer NSInputStream *)readStream;
self.dataOutStream = (__bridge_transfer NSOutputStream *)writeStream;
[self.dataInStream setDelegate:self];
[self.dataOutStream setDelegate:self];
[self performSelector:@selector(scheduleInCurrentThread:) 
onThread:[[self class] networkThread]
withObject:self.dataInStream waitUntilDone:YES];
[self performSelector:@selector(scheduleInCurrentThread:) 
onThread:[[self class] networkThread]
withObject:self.dataOutStream waitUntilDone:YES];
[self.dataInStream open]; 
[self.dataOutStream open]; 
self.isDataStreamAvailable=YES;
}
} 

-(void)closeDataStream
 {
	if (self.dataInStream.streamStatus != NSStreamStatusClosed) {
[self.dataInStream removeFromRunLoop: 
[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode]; 
self.dataInStream.delegate = nil; 
[self.dataInStream close];
}
if (self.dataOutStream.streamStatus != NSStreamStatusClosed) {
[self.dataOutStream removeFromRunLoop: 
[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
			self.dataOutStream.delegate = nil; 
[self.dataOutStream close];
}
}


 -(void)logoff {
[self sendCommand:@"QUIT"];
[self closeDataStream];
if (self.inputStream.streamStatus != NSStreamStatusClosed)
{
[self.inputStream removeFromRunLoop:
[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
self.inputStream.delegate = nil; 
[self.inputStream close];
}
if (_outputStream.streamStatus != NSStreamStatusClosed) 
{
[self.outputStream removeFromRunLoop: 
[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.outputStream.delegate = nil; 
[self.outputStream close];
}
self.isConnected=NO; 
self.isDataStreamAvailable=NO; 
self.isDataStreamConfigured=NO;
}

运行FTP客户端

            为使用你开发的FTP客户端,了解一系列发送的FTP命令和处理相关FTP回应是非常重要的。

            你可以在http://www.w3.org/Protocols/rfc959找到完整的RFC说明文档。

            当流打开,服务端会回应以220应答(220 response)和某种欢迎消息。假定你的FTP服务端不允许匿名连接,第一个期望的命令是USER命令后加用户名。假如用户名是正确的,服务端会回应331,表示等待回应一个密码。你使用PASS命令后加密码来发送密码。

            YDFTPClinet类中的sendUsername和sendPassword方法仅仅是说明发送用户名和密码的简单包装。服务端或回应230登陆成功,或530登录失败。

            在登陆之后,直接向服务端发送一个PASV命令是个好习惯,该命令告诉FTP服务端使用一个被动连接,被动连接比主动连接(一直是同样的IP端口)更安全。一个被动连接从服务端配置的可用端口范围中,为数据流选择和使用一个随机端口。

            你可以将这个YDFTPClient类添加到你第一章创建的应用框架中。

 

 

 

总结

            在本章,你学到两种实现使用流实现FTP客户端的不同方法,使用你所学的技术,你可以:

Ø  创建到FTP客户端的流连接

Ø  从FTP服务端下载或向其上传文件

Ø  发送原生FTP命令到FTP服务端并处理响应



代码下载:

http://media.wiley.com/product_ancillary/33/11186611/DOWNLOAD/ch08.zip

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用FTP(IOS FTP客户端开发教程) 的相关文章

  • xcode“将源编译为”覆盖特定文件

    我有一个项目 必须将 编译源为 值设置为 Objective C 不 根据文件类型 在我的主项目中不起作用 我从框架 特别是 OpenFeint 获得的文件有问题 该文件在编译为 Objective C 时出现编译错误 error poin
  • 如何检测 UISearchBar/UITextField 输入中的暂停?

    我有以下 UISearchbar 代码 void searchBar UISearchBar searchBar textDidChange NSString searchText UIApplication sharedApplicati
  • 如何用Block简化回调逻辑?

    假设我需要与一个提供协议的类进行通信 并在操作完成时调用委托方法 如下所示 protocol SomeObjectDelegate required void stuffDone id anObject void stuffFailed e
  • 如何将字符串从 Applescript 传递到 Objective C

    我正在开发一个应用程序 我需要能够传递一个字符串变量 from 苹果脚本 to 目标C 我已经弄清楚如何从 Objective C 类中的方法运行 Applescript 但我需要能够将 NSString 设置为 Applescript 中
  • AppDelegate 的变量用作全局变量不起作用

    我想使用我的 AppDelegate 来存储任何其他类都可以访问的对象 我已经像这样声明了这个 AppDelegate interface MyAppDelegate UIResponder
  • NSDateFormatter:根据 currentLocale 的日期,不包含年份

    这不会太难吧 我想显示不带年份的日期 例如 8 月 2 日 美国 或 02 08 德国 它也必须适用于许多其他语言环境 到目前为止 我唯一的想法是对年份进行正常格式 然后从生成的字符串中删除年份部分 我认为你需要看一下 NSString d
  • UITextView:内存使用量巨大

    我在 UITextView 中遇到了内存使用过多的问题 我正在将 50Kb ascii 文本文件加载到 NSString 中 并将其分配给应用程序中空 UITextView 组件的 text 属性 这立即使我的内存占用量增加了 100Mb
  • 断点条件错误

    我已经根据条件设置了断点 event name isEqualToString Some Name 这很好用 但是 当我尝试添加另一个带有条件的断点时 part name isEqualToString Some Value With A
  • let/var 如何解决可变性? [复制]

    这个问题在这里已经有答案了 我没有任何问题 我只是想对有关可变性的问题进行一些澄清 在 Objective C 中我们会使用例如NSMutableArray得到一个可变数组和NSArray得到一个不可变的 我对两者的内部运作了解不多 但据我
  • AVPlayer 不播放音频 - iOS 9,目标 - C

    我正在尝试从我的应用程序中的 URL 播放音频 iOS 8 中一切都按预期发生 模拟器和物理设备 对于 iOS 9 它可以在模拟器中运行 但在设备上 音频根本无法播放 出现流媒体 如果我单击播放 进度条还显示音频正在加载并播放 但没有声音
  • 如果加载 dylib,垃圾收集工作队列会崩溃

    我们正在将应用程序从 10 6 移植到 10 8 我正在查看我们在应用程序中加载的 dylib 我面临着非常不寻常的崩溃垃圾收集工作队列并附有以下消息 malloc Thread suspend unable to suspend a th
  • 在回调函数中调用目标c函数

    如何在回调函数中调用目标c函数 回调函数 static OSStatus inputRenderCallback void inRefCon AudioUnitRenderActionFlags ioActionFlags const Au
  • 与 Objective-C 的 VPN 连接

    有没有办法在 iPhone 的 Objective C 中以编程方式建立 VPN 连接 有这方面的好教程吗 有人知道吗 多谢 我认为第三方应用程序无法访问这些 API
  • 使用自动布局、IB 和字体大小时表头视图高度错误

    我正在尝试为我的 uiTableView 创建一个标题视图 不是节标题 我已经有了 我已经在界面生成器中设置了一个 XIB 所有的连接都已连接好并且运行良好 除了桌子没有给它足够的空间 我的问题是表格顶部与表格标题有一点重叠 我的 XIB
  • 如何确定 NSTimeInterval 是否发生在任意 NSDate 期间?

    我有一个 NSTimeInterval 我想知道该时间戳是否位于日期的开始和结束之间 基本上我需要能够做类似的事情 NSDate today NSDate date NSTimeInterval myInterval someInterva
  • 如何解决malloc_error_break?

    我在 iOS 3 0 模拟器上遇到此错误 但在 3 1 3 和 3 2 模拟器上没有遇到此错误 创建符号断点后malloc error break 我在日志中看到了这一点 Session started at 2010 02 13 19 1
  • 沙盒尝试恢复消耗性 IAP

    我一直在尝试在 iOS 上测试一些消耗性 IAP 但遇到了一个奇怪的错误 弹出一条警报 其中包含以下文本 此应用内购买已被购买 它将恢复为 自由的 环境 沙盒 我已经检查过 并且确定我的 IAP 可以在 iTunesConnect 中使用
  • Objective-C:int值无故改变

    Objective C 我需要帮助保留 int 的值 无需我的命令 它就在我身上发生变化 最初的问题是 如何声明和保留 int 这在另一篇文章中得到了满足 Objective C 如何声明和保留 int https stackoverflo
  • iOS 视图控制器内存在被关闭后未释放

    当用户单击按钮时 它会显示一个带有两个视图控制器的新选项卡栏视图控制器 我是这样做的 ACLevelDownloadController dvc ACLevelDownloadController alloc initWithNibName
  • ios 导航 堆栈操作

    我在尝试从 iOS 应用程序操作导航堆栈时遇到问题 或者至少是由于这种操纵而产生的行为 我的情况 我有 3 个 ViewController 控制器a显示多个级别 控制器 b 是游戏视图 控制器 c 是某种分数 显然 我将在控制器 a 中选

随机推荐

  • 毕业设计-基于深度学习的垃圾分类识别方法

    目录 前言 课题背景和意义 实现技术思路 一 目标检测算法对比研究 二 垃圾数据集的制作 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精力 近几年各个
  • 基于java springboot vue实现的校园招聘系统

    基于java springboot vue实现的校园招聘系统 总体分为三端 分别为 管理员 用户 企业 用户端 管理员端 企业端
  • layui table切换html,layui-table对返回的数据进行转变显示的实例

    在使用layui表格时 在ajax请求回来的数据 有时候需要我们处理之后显示 1 比如性别sex这个字段 后台可能返回的是1 或者 2 那我们总不能显示1 和 2 我们需要显示男和女 这里就用到了自定义模板了 if d sex 1 男 el
  • RS485转0_20mA输出模块设计

    文章目录 1 简介 2 功能实现 3 测试 4 开源地址 1 简介 结合以前发的文章 我们知道 模拟量输出有两种 一种是共地型 一种是共源型 今天开源一款rs485隔离的转0 20ma输出模块的设计 我设计模块的原因是为了测试公司的一款模拟
  • 基础算法题——异或(复杂度的小差异)

    异或 题目描述 给定一个长度为 n 初始全为 0 的数列 ai 下标从 1 开始 定义操作模 k 异或 v 为对所有满足 ki 0 mod k 的下标 i 将异或上整数v 即令 ai ai v 给出q次操作 每次操作之后输出序列的异或和 并
  • js插入前后

  • 【2023.07.15】生成模型(三)Score-based Generative Models

    1 main contribution 来自Score based Generative Model的原文 1 提供了一个统一SMLD denoising score matching with langevin dynamics 和DDP
  • MPLS LDP的原理与配置

    一 LDP协议的概述 1 LDP会话 本地会话 LSR之间是直连的 双方使用组播地址224 0 0 2建立会话 远程会话 LSR之间可以是非直连的 双方建立会话是使用单播建立的 缺省是本地会话 2 LDP领接体 只要双方建立了会话之后就建立
  • Flink+Hudi 构架湖仓一体化解决方案

    摘要 本文详细介绍了 Flink Hudi 湖仓一体化方案的原型构建 主要内容为 Hudi 新架构与湖仓一体 最佳实践 Flink on Hudi Flink CDC 2 0 on Hudi Tips FFA 2021 重磅开启 点击 阅读
  • tcp第三次握手ack均是1?

    本人做了tcp连接测试 但是结果和网络中其他人的说法有点不一致 测试使用了命令 tcpdump s1用网卡ens33抓取端口好为80的网络数据包 tcpdump nn i ens33 port 80 s2访问百度 建立3次连接请求数据 cu
  • P1094 [NOIP2007 普及组] 纪念品分组 Python (贪心算法)

    题目地址 P1094 NOIP2007 普及组 纪念品分组 又是一道水题 但CSDN上没有详细Python代码 于是我就来水一贴 对于想要学算法提升能力的同学来说可以刷这套题单 能力全面提升综合题单 读完题目后我们可以快速得出 既然要求最小
  • 青少年CTFmisc-simpleness

    提示弱口令 爆破出hint的密码123456 hint zip里面解出两个文件 hint png hint rar 这个hint rar是伪加密 随便打开一个十六进制的编辑器 这里的24表示已加密 改成20表示未加密 打开hint txt
  • 8B10B编解码的Verilog实现

    此篇是我在学习中做的归纳与总结 其中如果存在版权或知识错误或问题请直接联系我 欢迎留言 PS 本着知识共享的原则 此篇博客可以转载 但请标明出处 目录 0 8B 10B编码 0 0 8B 10B编码原理 0 1 8B 10B编码的FPGA实
  • pycharm调整字母长度分割线为80

    写过 python 的同学都知道 python 代码默认一行的长度不超过 80 个字符 但是 pycharm 默认的分割线在第 120 个字符处 需要作如下修改 设置 File gt Settings gt Code Style gt Ri
  • JetBrains全家桶使用说明

    一 二 三 友情推荐 激活获取地址
  • 泰勒公式和二项式展开定理的共同点

    泰勒公式和二项式展开定理的共同点 对于f x 1 x n 采用泰勒展开法有 f x fk0 0 x 0 0 fk1 0 x 1 1 fk2 0 x 2 2 其中fk0 0 fk1 0 分别代表fk x 的k阶导数 并且传0代替k阶导数中的x
  • 保姆级教程:Linux和Windows下本地化部署Vicuna模型

    目录 文章摘要 一 Vicuna简介 1 Vicuna模型定义 2 Vicuna模型的应用场景 3 Vicuna模型的训练数据 4 Vicuna模型的版本 5 性能评估 二 linux 操作系统下部署 1 环境介绍 2 安装Python3
  • Windows 动态磁盘卷:简单卷、跨区卷 、带区卷 、镜像卷 、RAID5卷 相关配置操作

    Windows Server 2003 提供了新的磁盘管理方式 能够提高磁盘性能和容错能力 将基本磁盘升级为动态磁盘 能够更灵活分配和管理磁盘空间 能够配置各种磁盘阵列提高磁盘能力 动态磁盘与基本磁盘对比 一块基本磁盘只能包含4个分区 它们
  • C语言——malloc与free

    文章目录 1 malloc 1 1 size t 1 2 malloc可申请的字节数 1 2 1 整形常量溢出 1 3 malloc一维数组 1 4 calloc 2 free 1 malloc 在堆区申请一个指定大小 连续的空间并返回空间
  • 使用FTP(IOS FTP客户端开发教程)

    本文翻译自新近Wrox出版社出版的 由Peter van de Put所著的 Professional iOS Programming 该书题材比较新颖 结构合理 是一本不错的IOS开发书籍 本文译自该书第八章 Using FTP 本文开放