设为首页 收藏本站
查看: 1820|回复: 0

[经验分享] 基于IOS的FTP详解(一)获取列表

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-11-6 10:56:09 | 显示全部楼层 |阅读模式
  项目中有用到ftp对远程文件进行管理,这里整理一下,主要是通过CFNetwork来实现的。
  首先对ftp进行一个小得概述,后面会通过抓包工具进行详细的讲述请求过程:
  FTP地址格式:


  无密码:
  ftp://<服务器地址>
  有密码:
  ftp://<login>:<password>@<ftpserveraddress>


  ftp采用两个TCP连接来传输一个文件,一个是控制连接,一个是数据连接,如下图所示:
   DSC0000.jpg


  通常Unix实现的ftp客户和服务器使用流方式传输
  我们通过终端登录到FTP服务端以后会返回如下信息:
  
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.


  下面我们开始具体的实现:
  

Downloading a directory listing via FTP is slightly different from downloading or uploading a file. This is because the incoming data has to be parsed. First, set up a read stream to get the directory listing. This should be done
as it was for downloading a file: create the stream, register a callback function, schedule the stream with the run loop (if necessary, set up user name, password and proxy information), and finally open the stream. In the following example you do not need
both a read and a write stream when retrieving the directory listing, because the incoming data is going to the screen rather than a file.


In the callback function, watch for the kCFStreamEventHasBytesAvailable event.

通过实现来进行讲解:



-(void)start
{
self.listData = [[NSMutableData alloc]init];
NSURL *url = [NSURL URLWithString:directoryStr];
readStream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef)url);
CFStreamClientContext clientContext;
clientContext.version = 0;
clientContext.info = CFBridgingRetain(self) ;//注解1
clientContext.retain = nil;
clientContext.release = nil;
clientContext.copyDescription = nil;
if (CFReadStreamSetClient (readStream,
kCFStreamEventOpenCompleted |
kCFStreamEventHasBytesAvailable |
kCFStreamEventCanAcceptBytes |
kCFStreamEventErrorOccurred |
kCFStreamEventEndEncountered,
mySocketReadCallBack,   //注解2
&clientContext ) )
{
NSLog(@&quot;Set read callBack Succeeded&quot;);
CFReadStreamScheduleWithRunLoop(readStream,    //注解3
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes);
}
else
{
NSLog(@&quot;Set read callBack Failed&quot;);
}
BOOL success = CFReadStreamOpen(readStream); //注解4
if (!success) {
printf(&quot;stream open fail\n&quot;);
return;
}
}
  





注解1:因为这里是用c来实现的,ARC下面必须用CFBridgingRetain来将引用计数加1,否则会出现僵尸对象;

注解2:对前面一个参数设置的标示事件发生的时候设置的回调函数,后面那个参数是上下文,将你关心的对象传给这个回调函数;

注解3:将输入流加入到当前的运行时循环,这样在所关心的时间发生的时候,就会触发回调;
注解4:打开输入流,这里相当于打开一个TCP连接。





#define BUFSIZE 32768
void mySocketReadCallBack (CFReadStreamRef stream, CFStreamEventType event, void *myPtr)
{
JUNFTPListRequest* request = (__bridge JUNFTPListRequest *)myPtr;
switch(event)
{
case kCFStreamEventHasBytesAvailable:
{
UInt8 recvBuffer[BUFSIZE];
CFIndex bytesRead = CFReadStreamRead(stream, recvBuffer, BUFSIZE);
printf(&quot;bytesRead:%ld\n&quot;,bytesRead);
if (bytesRead > 0)
{
[request.listData appendBytes:recvBuffer length:bytesRead];
}
else if(bytesRead == 0)
{
[request parseListData];
[request stop];
}
else
{
request.failBlock();
}
}
break;
case kCFStreamEventErrorOccurred:
{
CFStreamError error = CFReadStreamGetError(stream);
printf(&quot;kCFStreamEventErrorOccurred-%d\n&quot;,error.error);
[request stop];
request.failBlock();
}
break;
case kCFStreamEventEndEncountered:
printf(&quot;request finished\n&quot;);
[request stop];
break;
default:
break;
}
}


  
当kCFStreamEventHasBytesAvailable事件发生的时候表示套接字有可读的数据到达,因此我们调用CFReadStreamRead方法将数据读到缓冲,当我们读到bytesRead == 0的时候就表示对方文件或者数据已经传送完成,关闭当前连接,因为TCP是字节流,没有任何记录边界,关闭连接的时候会发送一个FIN分节来表示文件结束符(end-of-file)因此我们就可以解析数据了,这里是基于TCP的短连接。


这里要注意最后我们要释放掉通过c语言创建的对象,ARC不负责这个,还要将指针指向空,否则会留下一个野指针;

-(void)stop
{
CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFReadStreamClose(readStream);
CFRelease(readStream);
readStream = nil;
}





After the data has been read to a buffer, set up a loop to parse the data. The data that is parsed is not necessarily the entire directory listing; it could (and probably will) be chunks of the listing. Create the loop to parse the
data using the functionCFFTPCreateParsedResourceListing,
which should be passed the buffer of data, the size of the buffer, and a dictionary reference. It returns the number of bytes parsed. As long as this value is greater than zero, continue to loop. The dictionary thatCFFTPCreateParsedResourceListing creates
contains all the directory listing information.


It is possible for CFFTPCreateParsedResourceListing to return a positive value, but not create a parse dictionary. For example, if the end of the listing contains information
that cannot be parsed, CFFTPCreateParsedResourceListing will return a positive value to tell the caller that data has been consumed. However, CFFTPCreateParsedResourceListing will
not create a parse dictionary since it could not understand the data.




-(void)parseListData
{
CFIndex         bytesConsumed;
CFIndex         totalBytesConsumed = 0;
CFDictionaryRef parsedDict;
NSMutableArray *array = [[NSMutableArray alloc]init];
do
{
bytesConsumed = CFFTPCreateParsedResourceListing(NULL,
&((const uint8_t *)self.listData.bytes)[totalBytesConsumed],
self.listData.length-totalBytesConsumed,
&parsedDict);
if (bytesConsumed > 0)
{
if (parsedDict != NULL) {
[array addObject:(__bridge id)(parsedDict)];
CFRelease(parsedDict);
}
totalBytesConsumed += bytesConsumed;
}
else if (bytesConsumed == 0)
{
break;
}
else if (bytesConsumed == -1)
{
fprintf(stderr, &quot;CFFTPCreateParsedResourceListing parse failure\n&quot;);
self.failBlock();
return;
}
} while (1);
self.finishedBlock([NSArray arrayWithArray:array]);
}


获取列表和下载文件不同,因为文件列表需要解析,它有固定的&#26684;式,这里通过CFFTPCreateParsedResourceListing方法在循环中将列表解析并将每一条数据存放到字典里,每一项数据&#26684;式如下:  



{


        kCFFTPResourceGroup = ftp;


        kCFFTPResourceLink = &quot;&quot;;


        kCFFTPResourceModDate = &quot;2013-12-24 16:00:00 &#43;0000&quot;;


        kCFFTPResourceMode = 511;


        kCFFTPResourceName = &quot;\U00c8\U00e6\U00f6\U00c1\U00e9\U2022 - \U00c8\U00a9\U00a8\U00c2\U00d6\U221e\U00cb\U00e4\U00b1.mp3&quot;;


        kCFFTPResourceOwner = ftp;


        kCFFTPResourceSize = 5250346;


        kCFFTPResourceType = 8;


}






关于kCFFTPResourceType的定义在sys/dirent.h下其中

#define DT_DIR4

代表目录,那么我们可以根据这个来继续读取下一级列表
  这里有个乱码的问题要解决,就是kCFFTPResourceName 获取的文件名,方法如下:
  

        NSString *      name;
NSData *        nameData;
name = [dic objectForKey:(id)kCFFTPResourceName];
NSLog(@&quot;string = %@&quot;,name);
nameData = [name dataUsingEncoding:NSMacOSRomanStringEncoding];
if (nameData != nil) {
name = [[NSString alloc] initWithData:nameData encoding:NSUTF8StringEncoding];
}
NSLog(@&quot;string1 = %@&quot;,name);  
  



由于好奇我连续获取两个列表,然后用抓包工具查看了一下数据:   DSC0001.jpg


   DSC0002.jpg


  


  发现一个问题就是,服务器的21端口只负责控制命令,就像上面图中显示的,发送第一个列表请求的时候首先客户端51664和服务端的21端口通过三次握手建立连接,然后是依次用户名,密码的命令,而每次客户端发送一个控制命令LIST的时候客户端首先会给21端口发送LIST控制命令,然后建立一个新的连接,观察发现端口号在变化,在新连接上面传输数据,数据传输完成以后服务端会关闭连接,下次有数据传输的时候会开启新的连接,51664这个端口和服务器21端口一直保持连接状态,直到我关掉app,51664才会发送FIN标志给服务端。




  demo地址:http://download.iyunv.com/detail/junjun150013652/7629007
  


  参考:
  TCP/IP协议详解


  《CFNetwork Programming Guide:Working with FTP Servers》

版权声明:本文为博主原创文章,未经博主允许不得转载。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-135813-1-1.html 上篇帖子: 实验三 FTP协议分析 下篇帖子: asp.net 实现FTP上传
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表