571
技術社區[雲棲]
FTP上傳文件示例
首先,你需要一個測試環境,我在自己的機器上搭建了一個FTP Server,搭建Server有一些比較優秀的軟件,例如:Crob FTP Server,不過這是一個收費軟件,雖然提供試用版,但要在公網上使用的話,還是買個注冊號吧!
下載地址: Crob FTP Server V3.7.0 Build 196 簡體中文版 https://www.skycn.com/soft/11246.html
中文軟件,大家都是學技術的,怎麼搭建我就不說了。
相當傻瓜式的,上傳代碼隻有寥寥幾行:
- FTPClient fc = new FTPClient();
- fc.RemoteHost = "10.6.133.145"; // 這裏隻能用IP,而不能用機器名。當然,你可以利用工具類通過機器名獲取到IP地址
- fc.RemotePath = "/u01/upload"; // FTP服務器的虛擬目錄
- fc.RemotePort = 21;
- fc.RemoteUser = "admin";
- fc.RemotePass = "123";
- fc.Connect();
- fc.Put("c:/1.txt");
- fc.DisConnect();
這裏要注意的是,虛擬目錄/u01/upload對應的路徑需要有寫文件操作的權限,你可以在屬性中配置賬號的寫權限(我直接共享了該文件夾,並允許everyone賬號完全控製)。
這裏提供FTPClient.cs的源碼,來源一時半會兒找不到了:
- using System;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.IO;
- namespace TimerServer
- {
- /// <summary>
- /// FTPClient 的摘要說明。
- /// </summary>
- public class FTPClient
- {
- #region 構造函數
- /**//// <summary>
- /// 缺省構造函數
- /// </summary>
- public FTPClient()
- {
- strRemoteHost = "";
- strRemotePath = "";
- strRemoteUser = "";
- strRemotePass = "";
- strRemotePort = 21;
- bConnected
- = false;
- }
- /**//// <summary>
- /// 構造函數
- /// </summary>
- /// <param name="remoteHost"></param>
- /// <param name="remotePath"></param>
- /// <param name="remoteUser"></param>
- /// <param name="remotePass"></param>
- /// <param name="remotePort"></param>
- 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 登陸
- /**//// <summary>
- /// FTP服務器IP地址
- /// </summary>
- private string strRemoteHost;
- public string RemoteHost
- {
- get
- {
- return strRemoteHost;
- }
- set
- {
- strRemoteHost = value;
- }
- }
- /**//// <summary>
- /// FTP服務器端口
- /// </summary>
- private int strRemotePort;
- public int RemotePort
- {
- get
- {
- return strRemotePort;
- }
- set
- {
- strRemotePort = value;
- }
- }
- /**//// <summary>
- /// 當前服務器目錄
- /// </summary>
- private string strRemotePath;
- public string RemotePath
- {
- get
- {
- return strRemotePath;
- }
- set
- {
- strRemotePath = value;
- }
- }
- /**//// <summary>
- /// 登錄用戶賬號
- /// </summary>
- private string strRemoteUser;
- public string RemoteUser
- {
- set
- {
- strRemoteUser = value;
- }
- }
- /**//// <summary>
- /// 用戶登錄密碼
- /// </summary>
- private string strRemotePass;
- public string RemotePass
- {
- set
- {
- strRemotePass = value;
- }
- }
- /**//// <summary>
- /// 是否登錄
- /// </summary>
- private Boolean bConnected;
- public bool Connected
- {
- get
- {
- return bConnected;
- }
- }
- #endregion
- #region 鏈接
- /**//// <summary>
- /// 建立連接
- /// </summary>
- public void Connect()
- {
- 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("Couldn't connect to remote server");
- }
- // 獲取應答碼
- 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;
- // 切換到目錄
- ChDir(strRemotePath);
- }
- /**//// <summary>
- /// 關閉連接
- /// </summary>
- public void DisConnect()
- {
- if( socketControl != null )
- {
- SendCommand("QUIT");
- }
- CloseSocketConnect();
- }
- #endregion
- #region 傳輸模式
- /**//// <summary>
- /// 傳輸模式:二進製類型、ASCII類型
- /// </summary>
- public enum TransferType {Binary,ASCII};
- /**//// <summary>
- /// 設置傳輸模式
- /// </summary>
- /// <param name="ttType">傳輸模式</param>
- 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;
- }
- }
- /**//// <summary>
- /// 獲得傳輸模式
- /// </summary>
- /// <returns>傳輸模式</returns>
- public TransferType GetTransferType()
- {
- return trType;
- }
- #endregion
- #region 文件操作
- /**//// <summary>
- /// 獲得文件列表
- /// </summary>
- /// <param name="strMask">文件名的匹配字符串</param>
- /// <returns></returns>
- 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 += ASCII.GetString(buffer, 0, iBytes);
- if(iBytes < buffer.Length)
- {
- break;
- }
- }
- char[] seperator = {'/n'};
- string[] strsFileList = strMsg.Split(seperator);
- socketData.Close();//數據socket關閉時也會有返回碼
- if(iReplyCode != 226)
- {
- ReadReply();
- if(iReplyCode != 226)
- {
- throw new IOException(strReply.Substring(4));
- }
- }
- return strsFileList;
- }
- /**//// <summary>
- /// 獲取文件大小
- /// </summary>
- /// <param name="strFileName">文件名</param>
- /// <returns>文件大小</returns>
- private 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;
- }
- /**//// <summary>
- /// 刪除
- /// </summary>
- /// <param name="strFileName">待刪除文件名</param>
- public void Delete(string strFileName)
- {
- if(!bConnected)
- {
- Connect();
- }
- SendCommand("DELE "+strFileName);
- if(iReplyCode != 250)
- {
- throw new IOException(strReply.Substring(4));
- }
- }
- /**//// <summary>
- /// 重命名(如果新文件名與已有文件重名,將覆蓋已有文件)
- /// </summary>
- /// <param name="strOldFileName">舊文件名</param>
- /// <param name="strNewFileName">新文件名</param>
- 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 上傳和下載
- /**//// <summary>
- /// 下載一批文件
- /// </summary>
- /// <param name="strFileNameMask">文件名的匹配字符串</param>
- /// <param name="strFolder">本地目錄(不得以/結束)</param>
- public void Get(string strFileNameMask,string strFolder)
- {
- if(!bConnected)
- {
- Connect();
- }
- string[] strFiles = Dir(strFileNameMask);
- foreach(string strFile in strFiles)
- {
- if(!strFile.Equals(""))//一般來說strFiles的最後一個元素可能是空字符串
- {
- Get(strFile,strFolder,strFile);
- }
- }
- }
- /**//// <summary>
- /// 下載一個文件
- /// </summary>
- /// <param name="strRemoteFileName">要下載的文件名</param>
- /// <param name="strFolder">本地目錄(不得以/結束)</param>
- /// <param name="strLocalFileName">保存在本地時的文件名</param>
- public void Get(string strRemoteFileName,string strFolder,string strLocalFileName)
- {
- if(!bConnected)
- {
- Connect();
- }
- SetTransferType(TransferType.Binary);
- if (strLocalFileName.Equals(""))
- {
- strLocalFileName = strRemoteFileName;
- }
- if(!File.Exists(strFolder + "//" +strLocalFileName))
- {
- Stream st = File.Create(strFolder + "//" +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));
- }
- }
- }
- /**//// <summary>
- /// 上傳一批文件
- /// </summary>
- /// <param name="strFolder">本地目錄(不得以/結束)</param>
- /// <param name="strFileNameMask">文件名匹配字符(可以包含*和?)</param>
- public void Put(string strFolder,string strFileNameMask)
- {
- string[] strFiles = Directory.GetFiles(strFolder,strFileNameMask);
- foreach(string strFile in strFiles)
- {
- //strFile是完整的文件名(包含路徑)
- Put(strFile);
- }
- }
- /**//// <summary>
- /// 上傳一個文件
- /// </summary>
- /// <param name="strFileName">本地文件名</param>
- public void Put(string strFileName)
- {
- if(!bConnected)
- {
- Connect();
- }
- 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;
- while ((iBytes = input.Read(buffer,0,buffer.Length)) > 0)
- {
- socketData.Send(buffer, iBytes, 0);
- }
- input.Close();
- if (socketData.Connected)
- {
- socketData.Close();
- }
- if(!(iReplyCode == 226 || iReplyCode == 250))
- {
- ReadReply();
- if(!(iReplyCode == 226 || iReplyCode == 250))
- {
- throw new IOException(strReply.Substring(4));
- }
- }
- }
- #endregion
- #region 目錄操作
- /**//// <summary>
- /// 創建目錄
- /// </summary>
- /// <param name="strDirName">目錄名</param>
- public void MkDir(string strDirName)
- {
- if(!bConnected)
- {
- Connect();
- }
- SendCommand("MKD "+strDirName);
- if(iReplyCode != 257)
- {
- throw new IOException(strReply.Substring(4));
- }
- }
- /**//// <summary>
- /// 刪除目錄
- /// </summary>
- /// <param name="strDirName">目錄名</param>
- public void RmDir(string strDirName)
- {
- if(!bConnected)
- {
- Connect();
- }
- SendCommand("RMD "+strDirName);
- if(iReplyCode != 250)
- {
- throw new IOException(strReply.Substring(4));
- }
- }
- /**//// <summary>
- /// 改變目錄
- /// </summary>
- /// <param name="strDirName">新的工作目錄名</param>
- 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 內部變量
- /**//// <summary>
- /// 服務器返回的應答信息(包含應答碼)
- /// </summary>
- private string strMsg;
- /**//// <summary>
- /// 服務器返回的應答信息(包含應答碼)
- /// </summary>
- private string strReply;
- /**//// <summary>
- /// 服務器返回的應答碼
- /// </summary>
- private int iReplyCode;
- /**//// <summary>
- /// 進行控製連接的socket
- /// </summary>
- private Socket socketControl;
- /**//// <summary>
- /// 傳輸模式
- /// </summary>
- private TransferType trType;
- /**//// <summary>
- /// 接收和發送數據的緩衝區
- /// </summary>
- private static int BLOCK_SIZE = 512;
- Byte[] buffer = new Byte[BLOCK_SIZE];
- /**//// <summary>
- /// 編碼方式
- /// </summary>
- Encoding ASCII = Encoding.ASCII;
- #endregion
- #region 內部函數
- /**//// <summary>
- /// 將一行應答字符串記錄在strReply和strMsg
- /// 應答碼記錄在iReplyCode
- /// </summary>
- private void ReadReply()
- {
- strMsg = "";
- strReply = ReadLine();
- iReplyCode = Int32.Parse(strReply.Substring(0,3));
- }
- /**//// <summary>
- /// 建立進行數據連接的socket
- /// </summary>
- /// <returns>數據連接socket</returns>
- 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("Can't connect to remote server");
- }
- return s;
- }
- /**//// <summary>
- /// 關閉socket連接(用於登錄以前)
- /// </summary>
- private void CloseSocketConnect()
- {
- if(socketControl!=null)
- {
- socketControl.Close();
- socketControl = null;
- }
- bConnected = false;
- }
- /**//// <summary>
- /// 讀取Socket返回的所有字符串
- /// </summary>
- /// <returns>包含應答碼的字符串行</returns>
- private string ReadLine()
- {
- while(true)
- {
- int iBytes = socketControl.Receive(buffer, buffer.Length, 0);
- strMsg += ASCII.GetString(buffer, 0, iBytes);
- if(iBytes < buffer.Length)
- {
- break;
- }
- }
- char[] seperator = {'/n'};
- 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;
- }
- /**//// <summary>
- /// 發送命令並獲取應答碼和最後一行應答字符串
- /// </summary>
- /// <param name="strCommand">命令</param>
- private void SendCommand(String strCommand)
- {
- Byte[] cmdBytes =
- Encoding.ASCII.GetBytes((strCommand+"/r/n").ToCharArray());
- socketControl.Send(cmdBytes, cmdBytes.Length, 0);
- ReadReply();
- }
- #endregion
- }
- }
該工具類還提供一些文件操作,注釋也比較豐富,再次向這個類的作者致敬。
-------------------------------------------------
這個類有一種無法處理的情況,如果FTP服務器使用SSL或者SSH2安全協議搭建的Secure FTP Server,就無法鏈接上了。在網上找到一個可行的辦法,見文章:https://blog.csdn.net/venus0314/archive/2006/09/21/1262386.aspx
原理是利用psftp.exe工具來上傳文件,上傳過程和通訊協議都被隱藏了,通過流寫DOS命令來文件的複製與粘貼的操作。我寫過測試類,但沒有成功,不過應該是一個可行的辦法,正在想辦法研究,如果有結果將會在博客中放出來。
psftp.exe比較難找,但還是找的到的。google吧!
-------------------------------------------------
Updated On 2008-10-21
FTPClient類中沒有包含Append的功能,即將本地文件的內容追加到遠程文件上的功能。這裏,我放上來一個通過了測試的Append方法:
- /// <summary>
- /// 文件Append操作
- /// </summary>
- /// <param name="strFileName">被Append的本地文件名,要求與遠程文件名一致</param>
- public void Append(string strFileName)
- {
- if(!bConnected)
- Connect();
- Socket socketData = CreateDataSocket();
- SetTransferType(TransferType.Binary);
- SendCommand("APPE " + Path.GetFileName(strFileName));
- if( !(iReplyCode == 125 || iReplyCode == 150) )
- {
- throw new IOException(strReply.Substring(4));
- }
- FileStream input = new FileStream(strFileName,FileMode.Open);
- int iBytes = 0;
- while ((iBytes = input.Read(buffer,0,buffer.Length)) > 0)
- {
- socketData.Send(buffer, iBytes, 0);
- }
- input.Close();
- if (socketData.Connected)
- {
- socketData.Close();
- }
- if(!(iReplyCode == 226 || iReplyCode == 250))
- {
- ReadReply();
- if(!(iReplyCode == 226 || iReplyCode == 250))
- {
- throw new IOException(strReply.Substring(4));
- }
- }
- }
這裏沒有提供指定FTP遠程服務器上文件名的功能,要求你上傳的本地文件名與遠程數據庫文件名一致。事實上,我做過指定遠程文件名的嚐試,即將命令該為“APPE local-file remote-file”的格式,但我發現,結果是,它把本地文件的內容傳上去後,生成了一個文件名為local-file與remote-file拚接後的新文件,令人費解!!
最後更新:2017-04-02 00:06:45