⑴ 关于如何实现FTP上传或者下载带进度和速率的实现方法
在这里需要说明的是,该方式是通过其他代码进行改进的。 首先我们需要定义一个委托,用来实现传输过程中传递文件的总数,已完成的字节数和速度,方便客户端界面上调用。 public delegate void TransferProcess(long total,long finished,double speed); 调用代码就不举例了 接下来我们建立一个FTPClient类,该类基于socket和FTP协议实现了连接FTP服务,建立目录,上传文件,下载文件等主要方法。结构如下: 需要注意的是,我们需要定一个事件event TransferProcess OnTransferProcess;该事件在实例化FTPClient之后需要调用,这个事件对实现进度条和速率是非常重要的。为了实现速率我们还需要定义个公开的成员startTime(开始时间)。我们现在主要是看一下如何上传的。 /// /// 上传一个文件 /// /// 本地文件名 public void Put(string strFileName) { //连接服务器 if (!bConnected) { Connect(); } UpdateStatus = true; //建立socket连接 Socket socketData = CreateDataSocket(); //向FTP服务器发生存储命令 SendCommand("STOR " + Path.GetFileName(strFileName)); //如何服务器返回的信息不是我们所需要的,就抛出异常 if (!(iReplyCode == 125 || iReplyCode == 150)) { throw new IOException(strReply.Substring(4)); } //建立本地文件的数据流 FileStream input = new FileStream(strFileName, FileMode.Open); int iBytes = 0; long total = input.Length;//该成员主要记录文件的总字节数,注意这里使用长整型,是为了突破只能传输2G左右的文件的限制 long finished = 0;//该成员主要记录已经传输完成的字节数,注意这里使用长整型,是为了突破只能传输2G左右的文件的限制 double speed = 0;//记录传输的速率 while ((iBytes = input.Read(buffer, 0, buffer.Length)) > 0)//循环从本地数据流中读取数据到缓冲区 { //Console.WriteLine(startTime.ToString()); socketData.Send(buffer, iBytes, 0);//将缓冲区的数据发送到FTP服务器 DateTime endTime = DateTime.Now;//每次发送数据的结束时间 TimeSpan ts = endTime - startTime;//计算每次发送数据的时间间隔 finished += iBytes;//计算完成的字节数. Console.WriteLine(ts.Milliseconds); //计算速率,注意finished是字节,所以需要换算冲K字节 if (ts.Milliseconds > 0) { speed = (double)(finished / ts.TotalMilliseconds); speed = Math.Round(speed * 1000 / 1024, 2); } //这里是必不可少的,否则你无法实现进度条 //如果传输进度事件被实例化,而且从本地数据流中读取数据不是空的并完成的字节数也不为空的话,则实现委托. if (OnTransferProcess != null&&iBytes>0&&finished>0) { OnTransferProcess(total, finished,speed); } } UpdateStatus = false; finished = 0; input.Close();//当传输完成之后需要关闭数据流,以便下次访问. if (socketData.Connected) { socketData.Close();//关闭当前的socket } if (!(iReplyCode == 226 || iReplyCode == 250)) { ReadReply(); if (!(iReplyCode == 226 || iReplyCode == 250)) { UpdateStatus = false; throw new IOException(strReply.Substring(4)); } } } 上面代码中注释写得比较详细,这里就不再一一讲解了,关于下载中实现进度条和速率的问题可以参考以上代码进行修改. 完整的代码如下: using System; using System.net; using System.IO; using System.Text; using System.net.Sockets; namespace MMSEncoder { public delegate void TransferProcess(long total,long finished,double speed); /// /// FTP Client /// public class FTPClient { public event TransferProcess OnTransferProcess; public bool UpdateStatus = true; public DateTime startTime; private bool IsAbortConnect = false; #region 构造函数 /// /// 缺省构造函数 /// public FTPClient() { strRemoteHost = ""; strRemotePath = ""; strRemoteUser = ""; strRemotePass = ""; strRemotePort = 21; bConnected = false; } /// /// 构造函数 /// /// FTP服务器IP地址 /// 当前服务器目录 /// 登录用户账号 /// 登录用户密码 /// FTP服务器端口 public FTPClient(string remoteHost, string remotePath, string remoteUser, string remotePass, int remotePort) { strRemoteHost = remoteHost; strRemotePath = remotePath; strRemoteUser = remoteUser; strRemotePass = remotePass; strRemotePort = remotePort; Connect(); } #endregion #region 登陆字段、属性 /// /// FTP服务器IP地址 /// private string strRemoteHost; public string RemoteHost { get { return strRemoteHost; } set { strRemoteHost = value; } } /// /// FTP服务器端口 /// private int strRemotePort; public int RemotePort { get { return strRemotePort; } set { strRemotePort = value; } } /// /// 当前服务器目录 /// private string strRemotePath; public string RemotePath { get { return strRemotePath; } set { strRemotePath = value; } } /// /// 登录用户账号 /// private string strRemoteUser; public string RemoteUser { set { strRemoteUser = value; } } /// /// 用户登录密码 /// private string strRemotePass; public string RemotePass { set { strRemotePass = value; } } /// /// 是否登录 /// private Boolean bConnected; public bool Connected { get { return bConnected; } } #endregion #region 链接 /// /// 建立连接 /// public void Connect() { //if (IsAbortConnect) throw new IOException("用户强制终止了FTP"); socketControl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint ep = new IPEndPoint(IPAddress.Parse(RemoteHost), strRemotePort); // 链接 try { socketControl.Connect(ep); } catch (Exception) { throw new IOException("无法连接到远程服务器!"); } // 获取应答码 ReadReply(); if (iReplyCode != 220) { DisConnect(); throw new IOException(strReply.Substring(4)); } // 登陆 SendCommand("USER " + strRemoteUser); if (!(iReplyCode == 331 || iReplyCode == 230)) { CloseSocketConnect();//关闭连接 throw new IOException(strReply.Substring(4)); } if (iReplyCode != 230) { SendCommand("PASS " + strRemotePass); if (!(iReplyCode == 230 || iReplyCode == 202)) { CloseSocketConnect();//关闭连接 throw new IOException(strReply.Substring(4)); } } bConnected = true; // 切换到初始目录 if (!string.IsNullOrEmpty(strRemotePath)) { ChDir(strRemotePath); } } /// /// 关闭连接 /// public void DisConnect() { if (socketControl != null) { SendCommand("QUIT"); } CloseSocketConnect(); } public void AbortConnect() { if (socketControl != null) { SendCommand("ABOR"); } IsAbortConnect = true; //CloseSocketConnect(); } #endregion #region 传输模式 /// /// 传输模式:二进制类型、ASCII类型 /// public enum TransferType { Binary, ASCII }; /// /// 设置传输模式 /// /// 传输模式 public void SetTransferType(TransferType ttType) { if (ttType == TransferType.Binary) { SendCommand("TYPE I");//binary类型传输 } else { SendCommand("TYPE A");//ASCII类型传输 } if (iReplyCode != 200) { throw new IOException(strReply.Substring(4)); } else { trType = ttType; } } /// /// 获得传输模式 /// /// 传输模式 public TransferType GetTransferType() { return trType; } #endregion #region 文件操作 /// /// 获得文件列表 /// /// 文件名的匹配字符串 /// public string[] Dir(string strMask) { // 建立链接 if (!bConnected) { Connect(); } //建立进行数据连接的socket Socket socketData = CreateDataSocket(); //传送命令 SendCommand("NLST " + strMask); //分析应答代码 if (!(iReplyCode == 150 || iReplyCode == 125 || iReplyCode == 226)) { throw new IOException(strReply.Substring(4)); } //获得结果 strMsg = ""; while (true) { int iBytes = socketData.Receive(buffer, buffer.Length, 0); strMsg += GB2312.GetString(buffer, 0, iBytes); if (iBytes < buffer.Length) { break; } } char[] seperator = { '
' }; string[] strsFileList = strMsg.Split(seperator); socketData.Close();//数据socket关闭时也会有返回码 if (iReplyCode != 226) { ReadReply(); if (iReplyCode != 226) { throw new IOException(strReply.Substring(4)); } } return strsFileList; } /// /// 获取文件大小 /// /// 文件名 /// 文件大小 public long GetFileSize(string strFileName) { if (!bConnected) { Connect(); } SendCommand("SIZE " + Path.GetFileName(strFileName)); long lSize = 0; if (iReplyCode == 213) { lSize = Int64.Parse(strReply.Substring(4)); } else { throw new IOException(strReply.Substring(4)); } return lSize; } /// /// 删除 /// /// 待删除文件名 public void Delete(string strFileName) { if (!bConnected) { Connect(); } SendCommand("DELE " + strFileName); if (iReplyCode != 250) { throw new IOException(strReply.Substring(4)); } } /// /// 重命名(如果新文件名与已有文件重名,将覆盖已有文件) /// /// 旧文件名 /// 新文件名 public void Rename(string strOldFileName, string strNewFileName) { if (!bConnected) { Connect(); } SendCommand("RNFR " + strOldFileName); if (iReplyCode != 350) { throw new IOException(strReply.Substring(4)); } // 如果新文件名与原有文件重名,将覆盖原有文件 SendCommand("RNTO " + strNewFileName); if (iReplyCode != 250) { throw new IOException(strReply.Substring(4)); } } #endregion #region 上传和下载 /// /// 下载一批文件 /// /// 文件名的匹配字符串 /// 本地目录(不得以\结束) public void Get(string strFileNameMask, string strFolder) { if (!bConnected) { Connect(); } string[] strFiles = Dir(strFileNameMask); foreach (string strFile in strFiles) { if (!strFile.Equals(""))//一般来说strFiles的最后一个元素可能是空字符串 { if (strFile.LastIndexOf(".") > -1) { Get(strFile.Replace("\r", ""), strFolder, strFile.Replace("\r", "")); } } } } /// /// 下载一个文件 /// /// 要下载的文件名 /// 本地目录(不得以\结束) /// 保存在本地时的文件名 public void Get(string strRemoteFileName, string strFolder, string strLocalFileName) { if (!bConnected) { Connect(); } SetTransferType(TransferType.Binary); if (strLocalFileName.Equals("")) { strLocalFileName = strRemoteFileName; } if (!File.Exists(strLocalFileName)) { Stream st = File.Create(strLocalFileName); st.Close(); } FileStream output = new FileStream(strFolder + "\\" + strLocalFileName, FileMode.Create); Socket socketData = CreateDataSocket(); SendCommand("RETR " + strRemoteFileName); if (!(iReplyCode == 150 || iReplyCode == 125 || iReplyCode == 226 || iReplyCode == 250)) { throw new IOException(strReply.Substring(4)); } while (true) { int iBytes = socketData.Receive(buffer, buffer.Length, 0); output.Write(buffer, 0, iBytes); if (iBytes <= 0) { break; } } output.Close(); if (socketData.Connected) { socketData.Close(); } if (!(iReplyCode == 226 || iReplyCode == 250)) { ReadReply(); if (!(iReplyCode == 226 || iReplyCode == 250)) { throw new IOException(strReply.Substring(4)); } } } /// /// 上传一批文件 /// /// 本地目录(不得以\结束) /// 文件名匹配字符(可以包含*和?) public void Put(string strFolder, string strFileNameMask) { string[] strFiles = Directory.GetFiles(strFolder, strFileNameMask); foreach (string strFile in strFiles) { //strFile是完整的文件名(包含路径) Put(strFile); } } /// /// 上传一个文件 /// /// 本地文件名 public void Put(string strFileName) { if (!bConnected) { Connect(); } UpdateStatus = true; Socket socketData = CreateDataSocket(); SendCommand("STOR " + Path.GetFileName(strFileName)); if (!(iReplyCode == 125 || iReplyCode == 150)) { throw new IOException(strReply.Substring(4)); } FileStream input = new FileStream(strFileName, FileMode.Open); int iBytes = 0; long total = input.Length; long finished = 0; //DateTime startTime = DateTime.Now; double speed = 0; while ((iBytes = input.Read(buffer, 0, buffer.Length)) > 0) { Console.WriteLine(startTime.ToString()); socketData.Send(buffer, iBytes, 0); DateTime endTime = DateTime.Now; TimeSpan ts = endTime - startTime; finished += iBytes; Console.WriteLine(ts.Milliseconds); if (ts.Milliseconds > 0) { speed = (double)(finished / ts.TotalMilliseconds); speed = Math.Round(speed * 1000 / 1024, 2); } if (OnTransferProcess != null&&iBytes>0&&finished>0) { OnTransferProcess(total, finished,speed); } } UpdateStatus = false; finished = 0; input.Close(); if (socketData.Connected) { socketData.Close(); } if (!(iReplyCode == 226 || iReplyCode == 250)) { ReadReply(); if (!(iReplyCode == 226 || iReplyCode == 250)) { UpdateStatus = false; throw new IOException(strReply.Substring(4)); } } } #endregion #region 目录操作 /// /// 创建目录 /// /// 目录名 public void MkDir(string strDirName) { if (!bConnected) { Connect(); } SendCommand("MKD " + strDirName); if (iReplyCode != 257) { throw new IOException(strReply.Substring(4)); } } /// /// 删除目录 /// /// 目录名 public void RmDir(string strDirName) { if (!bConnected) { Connect(); } SendCommand("RMD " + strDirName); if (iReplyCode != 250) { throw new IOException(strReply.Substring(4)); } } /// /// 改变目录 /// /// 新的工作目录名 public void ChDir(string strDirName) { if (strDirName.Equals(".") || strDirName.Equals("")) { return; } if (!bConnected) { Connect(); } SendCommand("CWD " + strDirName); if (iReplyCode != 250) { throw new IOException(strReply.Substring(4)); } this.strRemotePath = strDirName; } #endregion #region 内部变量 /// /// 服务器返回的应答信息(包含应答码) /// private string strMsg; /// /// 服务器返回的应答信息(包含应答码) /// private string strReply; /// /// 服务器返回的应答码 /// private int iReplyCode; /// /// 进行控制连接的socket /// private Socket socketControl; /// /// 传输模式 /// private TransferType trType; /// /// 接收和发送数据的缓冲区 /// private static int BLOCK_SIZE = Int16.MaxValue; Byte[] buffer = new Byte[BLOCK_SIZE]; /// /// 编码方式(为防止出现中文乱码采用 GB2312编码方式) /// Encoding GB2312 = Encoding.Default ;//Encoding.GetEncoding("gb2312"); #endregion #region 内部函数 /// /// 将一行应答字符串记录在strReply和strMsg /// 应答码记录在iReplyCode /// private void ReadReply() { strMsg = ""; strReply = ReadLine(); iReplyCode = Int32.Parse(strReply.Substring(0, 3)); } /// /// 建立进行数据连接的socket /// /// 数据连接socket private Socket CreateDataSocket() { SendCommand("PASV"); if (iReplyCode != 227) { throw new IOException(strReply.Substring(4)); } int index1 = strReply.IndexOf('('); int index2 = strReply.IndexOf(')'); string ipData = strReply.Substring(index1 + 1, index2 - index1 - 1); int[] parts = new int[6]; int len = ipData.Length; int partCount = 0; string buf = ""; for (int i = 0; i < len && partCount <= 6; i++) { char ch = Char.Parse(ipData.Substring(i, 1)); if (Char.IsDigit(ch)) buf += ch; else if (ch != ',') { throw new IOException("Malformed PASV strReply: " + strReply); } if (ch == ',' || i + 1 == len) { try { parts[partCount++] = Int32.Parse(buf); buf = ""; } catch (Exception) { throw new IOException("Malformed PASV strReply: " + strReply); } } } string ipAddress = parts[0] + "." + parts[1] + "." + parts[2] + "." + parts[3]; int port = (parts[4] << 8) + parts[5]; Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint ep = new IPEndPoint(IPAddress.Parse(ipAddress), port); try { s.Connect(ep); } catch (Exception) { throw new IOException("无法连接服务器"); } return s; } /// /// 关闭socket连接(用于登录以前) /// private void CloseSocketConnect() { if (socketControl != null) { socketControl.Close(); socketControl = null; } bConnected = false; } /// /// 读取Socket返回的所有字符串 /// /// 包含应答码的字符串行 private string ReadLine() { while (true) { int iBytes = socketControl.Receive(buffer, buffer.Length, 0); strMsg += GB2312.GetString(buffer, 0, iBytes); if (iBytes < buffer.Length) { break; } } char[] seperator = { '
' }; string[] mess = strMsg.Split(seperator); if (strMsg.Length > 2) { strMsg = mess[mess.Length - 2]; //seperator[0]是10,换行符是由13和0组成的,分隔后10后面虽没有字符串, //但也会分配为空字符串给后面(也是最后一个)字符串数组, //所以最后一个mess是没用的空字符串 //但为什么不直接取mess[0],因为只有最后一行字符串应答码与信息之间有空格 } else { strMsg = mess[0]; } if (!strMsg.Substring(3, 1).Equals(" "))//返回字符串正确的是以应答码(如220开头,后面接一空格,再接问候字符串) { return ReadLine(); } return strMsg; } /// /// 发送命令并获取应答码和最后一行应答字符串 /// /// 命令 private void SendCommand(String strCommand) { Byte[] cmdBytes = GB2312.GetBytes((strCommand + "\r
").ToCharArray()); socketControl.Send(cmdBytes, cmdBytes.Length, 0); ReadReply(); } #endregion } }
⑵ Python中如何获取FTP服务器上的文件大小
这要看你下载用了哪个ftp库
搜到的代码
files = ftp.nlst()
for f in files:
print f
⑶ FTP问题NLST does not support wildcards
这个MDEL需要指定具体的文件名,并不支持通配符 * 的使用。
NLST does not support wildcards 的意思就是:NLST不支持通配符
⑷ 请解释一下FTP连接的命令及响应
FTP命令
命令 描述
ABOR 中断数据连接程序
ACCT <account> 系统特权帐号
ALLO <bytes> 为服务器上的文件存储器分配字节
APPE <filename> 添加文件到服务器同名文件
CDUP <dir path> 改变服务器上的父目录
CWD <dir path> 改变服务器上的工作目录
DELE <filename> 删除服务器上的指定文件
HELP <command> 返回指定命令信息
LIST <name> 如果是文件名列出文件信息,如果是目录则列出文件列表
MODE <mode> 传输模式(S=流模式,B=块模式,C=压缩模式)
MKD <directory> 在服务器上建立指定目录
NLST <directory> 列出指定目录内容
NOOP 无动作,除了来自服务器上的承认
PASS <password> 系统登录密码
PASV 请求服务器等待数据连接
PORT <address> IP 地址和两字节的端口 ID
PWD 显示当前工作目录
QUIT 从 FTP 服务器上退出登录
REIN 重新初始化登录状态连接
REST <offset> 由特定偏移量重启文件传递
RETR <filename> 从服务器上找回(复制)文件
RMD <directory> 在服务器上删除指定目录
RNFR <old path> 对旧路径重命名
RNTO <new path> 对新路径重命名
SITE <params> 由服务器提供的站点特殊参数
SMNT <pathname> 挂载指定文件结构
STAT <directory> 在当前程序或目录上返回信息
STOR <filename> 储存(复制)文件到服务器上
STOU <filename> 储存文件到服务器名称上
STRU <type> 数据结构(F=文件,R=记录,P=页面)
SYST 返回服务器使用的操作系统
TYPE <data type> 数据类型(A=ASCII,E=EBCDIC,I=binary)
USER <username>> 系统登录的用户名
FTP响应码
响应代码 解释说明
110 新文件指示器上的重启标记
120 服务器准备就绪的时间(分钟数)
125 打开数据连接,开始传输
150 打开连接
200 成功
202 命令没有执行
211 系统状态回复
212 目录状态回复
213 文件状态回复
214 帮助信息回复
215 系统类型回复
220 服务就绪
221 退出网络
225 打开数据连接
226 结束数据连接
227 进入被动模式(IP 地址、ID 端口)
230 登录因特网
250 文件行为完成
257 路径名建立
331 要求密码
332 要求帐号
350 文件行为暂停
421 服务关闭
425 无法打开数据连接
426 结束连接
450 文件不可用
451 遇到本地错误
452 磁盘空间不足
500 无效命令
501 错误参数
502 命令没有执行
503 错误指令序列
504 无效命令参数
530 未登录网络
532 存储文件需要帐号
550 文件不可用
551 不知道的页类型
552 超过存储分配
553 文件名不允许
⑸ python,如何下载FTP上的文件
importftplib,socket
defconnect():
CONST_HOST="xxxx.xxxx.xxx"
CONST_USERNAME="xxxxx"
CONST_PWD="xxxxxxx"
try:
ftp=ftplib.FTP(CONST_HOST)
ftp.login(CONST_USERNAME,CONST_PWD)
returnftp
exceptsocket.error,socket.gaierror:
print("FTPisunavailable,pleasecheckthehost,usernameandpassword!")
sys.exit(0)
defdisconnect(ftp):
ftp.quit()
defdownload(ftp,filename):
#预定义每次写文件的buffer
CONST_BUFFER_SIZE=8192
f=open(filename,"wb").write
try:
ftp.retrbinary("RETR%s"%filename,f,CONST_BUFFER_SIZE)
exceptftplib.error_perm:
returnFalse
returnTrue
deffind(ftp,filename):
ftp_f_list=ftp.nlst()
iffilenameinftp_f_list:
returnTrue
else:
returnFalse
ftp=connect()
#yourfile为你想要下载的文件
iffind(ftp,'yourfile'):
download(ftp,'yourfile')
disconnect(ftp)
⑹ FTP 批量删除服务器文件相关,该怎么解决
功能是想根据FTP服务器上面的指定目录,删除目录下面所有文件,以下是测试代码。
ftpClient.sendServer(DELE README.txt\r\n);
int reply = ftpClient.readServerResponse();
if(reply == 250){//success
System.out.println(File delete success. Filename is README.txt);}走到这里正常,reply返回值是250(有点奇怪,弄这么个你懂的数字),到这里单个文件测试正常。
接下来想要删除目录下所有文件,也就是需要取目录下列表,也就是说要用到nameList这个函数,不知道还有没有别的,我就知道这个,下面为实际想要的代码。
TelnetInputStream list = ftpClient.nameList(fullPath);
ftpClient.sendServer(DELE README.txt\r\n);
int reply = ftpClient.readServerResponse();
if(reply == 250){//success
System.out.println(File delete success. Filename is README.txt);}取完列表后再这里进行删除,下面的reply返回值始终是226,这个很奇怪,不管删除文件存在不存在,都是226,这样就午饭判断删除是否成功了。
请知道的高手帮帮忙,深表谢意!
------解决方案--------------------------------------------------------
FTP reply codes are described in RFC 959.
Generally, reply code 226 or 250 is used after a successful file transfer, after LIST commands, and after NLST commands.
Reply code 250 (but not 226) is used for a broader class of FTP commands, such as RNTO, DELE, MKD, RMD, CWD.
RFC 959 describes the command sequences where a server is allowed to reply with either reply code 226 or reply code 250.
可能是没删除成功,所以226,要删除成功的话,就会250
------解决方案--------------------------------------------------------
好吧 我接分了。
------解决方案--------------------------------------------------------
求代码看看~~~
一个人唱双簧。。。算是接分。。。
⑺ FTP中NLST命令显示中文乱码解决办法
这个没有任何办法解决,请修改所有文件夹为中文。
为什么现在已经没有纯中文内核的操作系统出现,编程软件出现
就是因为中文在计算机里并不是默认字符,一个中文占据2个byte位置。。而英文是一个。
再加上中文编码都是2,3种(GB2312 BIG5等)
所以所有编程 计算机内部语言,几乎都用英文。
⑻ 如何异步读取FTP
一:开发背景
由于需要对多个服务器发布大的数据包,所以自己在LINUX用C语言,开发了一个传送器工具。因为上传时需要支持断点续传,所以自己参考ftp-rfc959和一些文章开发了这个支持断点续传的上传工具。整个工具分两部分开发的:一是支持断点续传的Ftp工具,包含下载和上传功能。二是同时对多个服务器发布不同数据包的传送器工具。本文只是对断点续传的商船和下载作探讨。
二:实现
其实Ftp上传下载的实现很简单,首先Ftp服务器端要支持文件的定位,
然后就是通过建立的Socket用Ftp服务器命令和服务器交互.
有些也可以通过Socket做自己的客户端和服务器端,但是有些服务器是自己能管理控制的,而且原来自己也写过一些这样C/S模式的工具,简单消息的传送接收还不错。
但用来传输文件效率很低,比文件传送协议FTP(File Transfer Protocol)差的太多。 利用Ftp的服务端可以省去服务器端的开发,而且可以向任何开放Ftp服务的服务器上传送文件,可以不考虑对方安装的
是什么的操作系统。
三:FTP
文件传送协议 FTP 只提供文件传送的一些基本的服务,它使用 TCP 可靠的运输服务。FTP 的主要功能是减少或消除在不同操作系统下处理文件的不兼容性。
FTP 使用客户服务器方式。
一个 FTP 服务器进程可同时为多个客户进程提供服务。FTP的服务器进程由两大部分组成:一个主进程,负责接受新的请求;另外有若干个从属进程,负责处理单个请求。
A、通常的方式:
控制连接在整个会话期间一直保持打开,FTP 客户所发出的传送请求通过控制连接发送给控制进程,但控制连接并不用来传送文件,实际用于传输文件的是“数据连接”。
控制进程在接收到 FTP 客户发送来的文件传输请求后就创建一个“数据传送进程”和一个“数据连接”,并将数据连接连接到“数据传送进程”,数据传送进程实际完成文件的传送,在传送完毕后关闭“数据传送连接”并结束运行
当客户进程向服务器进程发出建立连接请求时,要寻找连接服务器进程的熟知端口(21),同时还要告诉服务器进程自己的另一个端口号码,用于建立数据传送连接。接着,服务器进程用自己传送数据的熟知端口(20)与客户进程所提供的端口号码建立数据传送连接。
B、 被动模式:
FTP客户端发出的连接请求,一般通过服务器的21号端口建立控制连接,专门用来传输一些字符串命令和响应信息。控制命令通道一定是由客户端向服务器的连接(默认的端口是21,也可以指定端口,这要看服务器的设置)。
PASV:通过控制通道通过发送PASV 服务器命令到 FTP服务器。请求建立被动模式数据连接通道。 (客户端的命令 passive)
服务器返回连接的信息(227 Entering Passive Mode (70,0,10,62,120,18) )地址和端口。端口=最后第二位乘256再加上最后一位(120*256+18)。(注意端口设为0的情况) 如: *f_port = atoi(port_1) * 256 + atoi(port_2);
服务器端和客户端身份转换,原客户端在本地建立监听,监听来自原服务器远端的连接请求建立数据连接通道。
四、 实现方法:
A、下载:
1、向服务器发送“REST + 本地文件长度”,告诉服务器,客户端要断点下载了。这时服务器还不知道客户端要下载的文件名;
2、向服务器发送"RETR + 文件名",通知服务器要下载的文件名,这时服务器开始定位文件指针读文件并发送数据。
3、客户端定位本地文件指针偏移到文件末尾;
4、两端的准备工作都做完了以后,客户端创建socket,以被动或非被动方式建立数据链接,循环调用recv接收文件数据并追加到本地文件末尾;
B、上传:
1、获取服务器上和本地要上传文件的同名文件大小;
2、向服务器发送“APPE + 文件名”,通知服务器,从数据通道发送给你的数据要附加到这个文件末尾。
3、定位本地文件指针,文件指针偏移到指定位置,这个位置与FTP服务器上文件大小相同的位置。
4、从文件指针处读数据并发送。
C、Ftp服务器命令
我们平时使用的命令,大多是客户端的。服务器端的命令可以参考下面:
命令 描述
ABOR 中断数据连接程序 ACCT <account> 系统特权帐号
ALLO <bytes> 为服务器上的文件存储器分配字节 APPE <filename> 添加文件到服务器同名文件
CDUP <dir path> 改变服务器上的父目录 CWD <dir path> 改变服务器上的工作目录
DELE <filename> 删除服务器上的指定文件 HELP <command> 返回指定命令信息 LIST <name> 如果是文件名列出文件信息,如果是目录则列出文件列表 MODE <mode> 传输模式(S=流模式,B=块模式,C=压缩模式)
MKD <directory> 在服务器上建立指定目录 NLST <directory> 列出指定目录内容
NOOP 无动作,除了来自服务器上的承认 PASS <password> 系统登录密码
PASV 请求服务器等待数据连接 PORT <address> IP 地址和两字节的端口 ID PWD 显示当前工作目录 QUIT 从 FTP 服务器上退出登录
REIN 重新初始化登录状态连接 REST <offset> 由特定偏移量重启文件传递
RETR <filename> 从服务器上找回(复制)文件 RMD <directory> 在服务器上删除指定目录
RNFR <old path> 对旧路径重命名 RNTO <new path> 对新路径重命名
SITE <params> 由服务器提供的站点特殊参数 SMNT <pathname> 挂载指定文件结构
STAT <directory> 在当前程序或目录上返回信息 STOR <filename> 储存(复制)文件到服务器上
STOU <filename> 储存文件到服务器名称上 STRU <type> 数据结构(F=文件,R=记录,P=页面)
SYST 返回服务器使用的操作系统 TYPE <data type> 数据类型(A=ASCII,E=EBCDIC,I=binary)
USER <username>> 系统登录的用户名
D、服务器返回的部分数字代码含义
125 Data connection already open; Transfer starting.
226 Transfer complete.
227 Entering Passive Mode (127,0,0,1,4,18).
230 User xxxxx logged in.
331 Password required for xxxxx.
425 Can’t open data connection.
226 Closing data connection.
200 return a state of TYPE or MODE commond
220 connection state
五、关于Socket
关于Socket编程有很多参考资料,这里不作详细说明
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口
Socket 是一个基本的通信机制Socket把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.
Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
常用的Socket类型有两种:
A、流式Socket(SOCK_STREAM):流式是一种面向连接的Socket,针对于面向连接的TCP服务应用。
B、数据报式Socket(SOCK_DGRAM):数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
Socket的程序是一种C/S结构,分客户端和服务器端。
A、客户端
– 初始化Socket
– 连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了
– 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端
– 客户端读取数据,最后关闭连接,一次交互结束。
B、服务器端
- 初始化Socket
- 端口绑定(bind)
- 监听(listen)端口
- 调用accept阻塞,等待客户端连接
在这里我们使用的就是面向连接的流式Socket,只编写客户端的程序。
六、代码:
下面仅提供一个编译过并在使用中的函数简单的说明其实现原理,
需要有一点C语言和Linux/UNIX的socket编程基础即可理解。
只是建立连接和传输部分的一个函数。当文件传出结束或断开时,
可以调用检查函数看是否成功,字节是否正常等,如果不正常结束,
可以再次循环调用它并定为服务器文件的字节,继续传输。
其他的控制和功能都在其他函数中实现。为移植方便使用标准C语法符合C89标准。
code:
/*****************************************************************
* FileName: uftt_ver5.c *
* Company: algorithmics china lib Co.,Ltd. *
* Author: G.L.Zhang --- zglcl008 *
* Time: [2006-12-20] *
* Description: uninterrupted file transfers tools *
*****************************************************************/
int f_file_trans(const char *ft_addr, int ft_port, const char *ft_usr,const char *ft_pwd, const char *ft_opt, const char *ft_src, char *ft_obj, int ft_flg)
{
int cmd_sock = -1;
int dat_sock = -1;
int stream_sock= -1;
int dat_port = 0;
char dat_buffer[1024*5];
struct sockaddr_in f_server;
struct sockaddr_in f_datasvr;
unsigned char *pasv_ip = NULL;
unsigned char *pasv_port = NULL;
unsigned int len_addr = 0;
long rc_size = 0;
/* long svr_file_size = 0; */
long file_size = 0;
int rc = 0;
if (ft_addr == NULL || ft_usr == NULLft_pwd == NULL ||
ft_opt == NULL || ft_src == NULL || ft_obj == NULL ||
ft_port == 0 || ft_flg < 0) {
err_quit("--- file transfers parameter error");
}
/* get ftp commomd socket */
if ((cmd_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("cmmond socket");
err_quit("--- command sock error");
}
bzero(&f_server, sizeof(f_server));
f_server.sin_family = AF_INET;
f_server.sin_port = htons(ft_port);
f_server.sin_addr.s_addr = inet_addr(ft_addr);
if (connect(cmd_sock, (struct sockaddr *)&f_server, sizeof(f_server)) < 0) {
perror("connect");
err_quit("--- command sock error");
}
rc = uftt_cmd(dat_buffer, cmd_sock, NULL);
if (rc == 220)
printf("-1- %d OK\n", rc);
else
printf("-1- %d ERR\n", rc);
rc = uftt_cmd(dat_buffer, cmd_sock,"USER %s",ft_usr);
if (rc == 331)
printf("-2- %d OK\n", rc);
else
printf("-3- %d ERR\n", rc);
rc = uftt_cmd(dat_buffer, cmd_sock,"PASS %s",ft_pwd);
if (rc == 230)
printf("-3- %d OK\n", rc);
else
printf("-3- %d ERR\n", rc);
rc = uftt_cmd(dat_buffer, cmd_sock, "TYPE I");
if (rc == 200)
printf("-41- %d OK\n", rc);
else
printf("-41- %d ERR\n", rc);
rc = uftt_cmd(dat_buffer, cmd_sock, "MODE S");
if (rc == 200)
printf("-42- %d OK\n", rc);
else
printf("-42- %d ERR\n", rc);
memset(dat_buffer, ''\0'', sizeof(dat_buffer));
rc = uftt_cmd(dat_buffer, cmd_sock, "PASV");
if (rc == 227)
printf("-5- %d OK\n", rc);
else
printf("-5- %d ERR\n", rc);
#ifdef ZGL_DEBUG
printf("-5- %d %s\n", rc, dat_buffer);
#endif
/* get passive port */
dat_port = 0;
if ((rc = get_svr_port(dat_buffer, (int *)&dat_port)) < 0)
err_quit("data stream port error");
#ifdef ZGL_DEBUG
printf("--- pasive port=[%d]\n", dat_port);
#endif
if ((dat_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("data socket");
err_quit("--- data sock error");
}
len_addr = sizeof(f_datasvr);
bzero(&f_datasvr, sizeof(f_datasvr));
rc = getsockname(cmd_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
f_datasvr.sin_port = htons(dat_port);
/* 0=all port or appoint port TEST OK ***
f_datasvr.sin_port = htons(dat_port);
f_datasvr.sin_port = 0;
*/
if (bind(dat_sock,(struct sockaddr *)&f_datasvr,len_addr) == -1)
err_sys("--- data sock bind error");
if (listen(dat_sock,1) == -1)
err_sys("--- data sock bind error");
rc = getsockname(dat_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
pasv_ip = (unsigned char *)&f_datasvr.sin_addr;
pasv_port = (unsigned char *)&f_datasvr.sin_port;
rc = uftt_cmd(dat_buffer, cmd_sock,"PORT %d,%d,%d,%d,%d,%d",pasv_ip[0],
pasv_ip[1], pasv_ip[2], pasv_ip[3], pasv_port[0], pasv_port[1]);
if (rc == 200)
printf("-6- %d OK\n", rc);
else
printf("-6- %d ERR\n", rc);
/* get RETR put STOR append APPE */
if (ft_flg == 0) {
rc = uftt_cmd(dat_buffer, cmd_sock, "STOR %s", ft_obj);
}
else if (ft_flg > 0){
rc = uftt_cmd(dat_buffer, cmd_sock, "APPE %s", ft_obj);
}
if (rc == 150)
printf("-7- %d OK\n", rc);
else
printf("-7- %d ERR\n", rc);
stream_sock = accept(dat_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
if (stream_sock < 0)
err_sys("--- stream socket error");
rc_size = f_put_file(stream_sock, ft_src, ft_obj, ft_flg, file_size);
/* do without ***
rc = uftt_cmd(dat_buffer, dat_sock,"QUIT");
printf("-8- %d \n", rc);
*/
close(stream_sock);
close(dat_sock);
rc = uftt_cmd(dat_buffer, cmd_sock,"QUIT");
if (rc == 226)
printf("-9- %d OK\n", rc);
else
printf("-9- %d ERR\n", rc);
close(cmd_sock);
return rc_size ;
}
⑼ python 检测FTP目录是否为空
ftp相关命令操作
ftp.cwd(pathname) #设置FTP当前操作的路径
ftp.dir() #显示目录下文件信息
ftp.nlst() #获取目录下的文件
ftp.mkd(pathname) #新建远程目录
ftp.pwd() #返回当前所在位置
ftp.rmd(dirname) #删除远程目录
ftp.delete(filename) #删除远程文件
ftp.rename(fromname, toname)#将fromname修改名称为toname。
ftp.storbinaly("STOR filename.txt",file_handel,bufsize) #上传目标文件
ftp.retrbinary("RETR filename.txt",file_handel,bufsize)#下载FTP文件
⑽ VB与FTP编程
建议楼主用winsock连接FTP服务器,发送FTP命令来实现交互,另外说明下,目录的获取与文件的传输是另开端口进行的,以下是命令表
ftp协议命令字(转)
命令 描述
ABOR 中断数据连接程序
ACCT <account> 系统特权帐号
ALLO <bytes> 为服务器上的文件存储器分配字节
APPE <filename> 添加文件到服务器同名文件
CDUP <dir path> 改变服务器上的父目录
CWD <dir path> 改变服务器上的工作目录
DELE <filename> 删除服务器上的指定文件
HELP <command> 返回指定命令信息
LIST <name> 如果是文件名列出文件信息,如果是目录则列出文件列表
MODE <mode> 传输模式(S=流模式,B=块模式,C=压缩模式)
MKD <directory> 在服务器上建立指定目录
NLST <directory> 列出指定目录内容
NOOP 无动作,除了来自服务器上的承认
PASS <password> 系统登录密码
PASV 请求服务器等待数据连接
PORT <address> IP 地址和两字节的端口 ID
PWD 显示当前工作目录
QUIT 从 FTP 服务器上退出登录
REIN 重新初始化登录状态连接
REST <offset> 由特定偏移量重启文件传递
RETR <filename> 从服务器上找回(复制)文件
RMD <directory> 在服务器上删除指定目录
RNFR <old path> 对旧路径重命名
RNTO <new path> 对新路径重命名
SITE <params> 由服务器提供的站点特殊参数
SMNT <pathname> 挂载指定文件结构
STAT <directory> 在当前程序或目录上返回信息
STOR <filename> 储存(复制)文件到服务器上
STOU <filename> 储存文件到服务器名称上
STRU <type> 数据结构(F=文件,R=记录,P=页面)
SYST 返回服务器使用的操作系统
TYPE <data type> 数据类型(A=ASCII,E=EBCDIC,I=binary)
USER <username>> 系统登录的用户名
标准 FTP 信息如下:
响应代码 解释说明
110 新文件指示器上的重启标记
120 服务器准备就绪的时间(分钟数)
125 打开数据连接,开始传输
150 打开连接
200 成功
202 命令没有执行
211 系统状态回复
212 目录状态回复
213 文件状态回复
214 帮助信息回复
215 系统类型回复
220 服务就绪
221 退出网络
225 打开数据连接
226 结束数据连接
227 进入被动模式(IP 地址、ID 端口)
230 登录因特网
250 文件行为完成
257 路径名建立
331 要求密码
332 要求帐号
350 文件行为暂停
421 服务关闭
425 无法打开数据连接
426 结束连接
450 文件不可用
451 遇到本地错误
452 磁盘空间不足
500 无效命令
501 错误参数
502 命令没有执行
503 错误指令序列
504 无效命令参数
530 未登录网络
532 存储文件需要帐号
550 文件不可用
551 不知道的页类型
552 超过存储分配
553 文件名不允许