之前写了一个利用FTP进行下载的文章,同FTP下载一样,现在网络上大多数的文件的上传都是通过ASIHttpRequest库以form表单的形式完成的,比较简单,代码逻辑也比较清晰,我就不多说了。而今天要跟大家分享的是在iOS端使用ftp的形式进行上传的方式。
同理也是因为自己公司需要用到FTP进行上传文件,所以写了这篇文章:
同下载一样,也使用到了CFNetWork框架,也是只能做一些FTP基础操作,对于复杂的操作无法完成,详细情况大家可以看我的上一篇文章
在试图控制器WKViewController.h里面添加如下代码:
enum {
kSendBufferSize = 32768
};
@interface WKViewController : UIViewController <UITextFieldDelegate,NSStreamDelegate>
{
uint8_t _buffer[kSendBufferSize];
}
@property (retain, nonatomic) IBOutlet UITextField *fileInput;
@property (retain, nonatomic) IBOutlet UITextField *serverInput;
@property (retain, nonatomic) IBOutlet UITextField *accountInput;
@property (retain, nonatomic) IBOutlet UITextField *passwordInput;
@property (retain, nonatomic) IBOutlet UILabel *status;
- (IBAction)sendAction:(id)sender;//点击上传
- (IBAction)textFieldDoneEditing:(id)sender;//Did End On Exit 事件
@end
其中首先要遵循NSStreamDelegate协议,另外kSendBufferSize为缓冲区的大小设置,可以自己调节,其他的只是一些UI上的控件,大家自己行搭建
然后去到同一个控制器的.m文件里
首先定义相关内部变量与存取方法:
@interface WKViewController ()
//内部变量
@property (nonatomic, readonly) BOOL isSending;
@property (nonatomic, strong) NSOutputStream * networkStream;
@property (nonatomic, strong) NSInputStream * fileStream;
@property (nonatomic, readonly) uint8_t * buffer;
@property (nonatomic, assign) size_t bufferOffset;
@property (nonatomic, assign) size_t bufferLimit;
@end
//存取方法
@implementation WKViewController
@synthesize fileInput = _fileInput;
@synthesize serverInput = _serverInput;
@synthesize status = _status;
@synthesize accountInput = _accountInput;
@synthesize passwordInput = _passwordInput;
- (uint8_t *)buffer
{
return self->_buffer;
}
size_t与uint8_t为c扩展的类型变量,在用于跨语言的时候比较方便,uint8_t为一个字节大小的无符号int类型,size_t在32位与64位机器上代表不同的大小,分别为4个字节与8个字节。
因为buffer被声明为一个数组,所以必须要使用自定义的getter方法,synthesised方法是不会被编译的。
之后我们要点击上传按钮,触发一个上传事件,并且生成networkStream流
#pragma mark 上传事件
- (IBAction)sendAction:(id)sender {
NSURL *url;//ftp服务器地址
NSString *filePath;//图片地址
NSString *account;//账号
NSString *password;//密码
CFWriteStreamRef ftpStream;
//获得输入
url =[NSURL URLWithString:_serverInput.text];
filePath = _fileInput.text;
// filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"png"];
account =_accountInput.text;
password = _passwordInput.text;
//添加后缀(文件名称)
url=CFBridgingRelease(CFURLCreateCopyAppendingPathComponent(NULL, (CFURLRef)url, (CFStringRef)[filePath lastPathComponent], false));
//读取文件,转化为输入流
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
[self.fileStream open];
//为url开启CFFTPStream输出流
ftpStream = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url);
self.networkStream = (__bridge NSOutputStream *) ftpStream;
//设置ftp账号密码
[self.networkStream setProperty:account forKey:(id)kCFStreamPropertyFTPUserName];
[self.networkStream setProperty:password forKey:(id)kCFStreamPropertyFTPPassword];
//设置networkStream流的代理,任何关于networkStream的事件发生都会调用代理方法
self.networkStream.delegate = self;
//设置runloop
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
//完成释放链接
CFRelease(ftpStream);
}
实现NSStreamDelegate的代理方法
#pragma mark 回调方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
//aStream 即为设置为代理的networkStream
switch (eventCode) {
case NSStreamEventOpenCompleted: {
NSLog(@"NSStreamEventOpenCompleted");
} break;
case NSStreamEventHasBytesAvailable: {
NSLog(@"NSStreamEventHasBytesAvailable");
assert(NO); // 在上传的时候不会调用
} break;
case NSStreamEventHasSpaceAvailable: {
NSLog(@"NSStreamEventHasSpaceAvailable");
NSLog(@"bufferOffset is %zd",self.bufferOffset);
NSLog(@"bufferLimit is %zu",self.bufferLimit);
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
//读取文件错误
[self _stopSendWithStatus:@"读取文件错误"];
} else if (bytesRead == 0) {
NSLog(@"UpLoad Success");
//文件读取完成 上传完成
[self _stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
if (self.bufferOffset != self.bufferLimit) {
//写入数据
NSInteger bytesWritten;//bytesWritten为成功写入的数据
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self _stopSendWithStatus:@"网络写入错误"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self _stopSendWithStatus:@"Stream打开错误"];
assert(NO);
} break;
case NSStreamEventEndEncountered: {
// 忽略
} break;
default: {
assert(NO);
} break;
}
}
bytesWritten为实际写入的数据量,虽然缓冲区大小是固定的值,但每次写入并不一定是充满缓冲区,可以看到NSLog的输出值。(上传图片大小为428492字节,约430k)。
首先是NSStreamEventOpenCompleted打开事件完成的回调,然后不断发送NSStreamEventHasSpaceAvailable消息完成整个上传过程。
可以看到在传输的时候,bufferOffset并不是每次都是32768,所以self.bufferOffset!= self.bufferLimit是一个续传上次未完数据的过程,而当self.bufferOffset = self.bufferLimit时,即上次的32768已经传完了,此时将bufferOffset重新设置为0。
最后处理上传的结果:
//结果处理
- (void)_stopSendWithStatus:(NSString *)statusString
{
if (self.networkStream != nil) {
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.networkStream.delegate = nil;
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
[self _sendDidStopWithStatus:statusString];
}
- (void)_sendDidStopWithStatus:(NSString *)statusString
{
if (statusString == nil) {
statusString = @"上传成功";
}
_status.text = statusString;
}
在此方法中关闭链接、循环、设置代理为空