首先,你需要一個測試環境,我在自己的機器上搭建了一個ftp server,搭建server有一些比較優秀的軟體,例如:crob ftp server,不過這是一個收費軟體,雖然提供試用版,但要在公網上使用的話,還是買個注冊号吧!
下載下傳位址: crob ftp server v3.7.0 build 196 簡體中文版 http://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>
/// 構造函數
/// <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 登陸
/// ftp伺服器ip位址
private string strremotehost;
public string remotehost
get
return strremotehost;
set
strremotehost = value;
/// ftp伺服器端口
private int strremoteport;
public int remoteport
return strremoteport;
strremoteport = value;
/// 目前伺服器目錄
private string strremotepath;
public string remotepath
return strremotepath;
strremotepath = value;
/// 登入使用者賬号
private string strremoteuser;
public string remoteuser
strremoteuser = value;
/// 使用者登入密碼
private string strremotepass;
public string remotepass
strremotepass = value;
/// 是否登入
private boolean bconnected;
public bool connected
return bconnected;
#region 連結
/// 建立連接配接
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();//關閉連接配接
if( ireplycode != 230 )
sendcommand("pass "+strremotepass);
if( !(ireplycode == 230 || ireplycode == 202) )
closesocketconnect();//關閉連接配接
throw new ioexception(strreply.substring(4));
bconnected = true;
// 切換到目錄
chdir(strremotepath);
/// 關閉連接配接
public void disconnect()
if( socketcontrol != null )
sendcommand("quit");
closesocketconnect();
#region 傳輸模式
/// 傳輸模式:二進制類型、ascii類型
public enum transfertype {binary,ascii};
/// 設定傳輸模式
/// <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)
trtype = tttype;
/// 獲得傳輸模式
/// <returns>傳輸模式</returns>
public transfertype gettransfertype()
return trtype;
#endregion
#region 檔案操作
/// 獲得檔案清單
/// <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))
//獲得結果
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)
return strsfilelist;
/// 擷取檔案大小
/// <param name="strfilename">檔案名</param>
/// <returns>檔案大小</returns>
private long getfilesize(string strfilename)
sendcommand("size " + path.getfilename(strfilename));
long lsize=0;
if(ireplycode == 213)
lsize = int64.parse(strreply.substring(4));
return lsize;
/// 删除
/// <param name="strfilename">待删除檔案名</param>
public void delete(string strfilename)
sendcommand("dele "+strfilename);
if(ireplycode != 250)
/// 重命名(如果新檔案名與已有檔案重名,将覆寫已有檔案)
/// <param name="stroldfilename">舊檔案名</param>
/// <param name="strnewfilename">新檔案名</param>
public void rename(string stroldfilename,string strnewfilename)
sendcommand("rnfr "+stroldfilename);
if(ireplycode != 350)
// 如果新檔案名與原有檔案重名,将覆寫原有檔案
sendcommand("rnto "+strnewfilename);
#region 上傳和下載下傳
/// 下載下傳一批檔案
/// <param name="strfilenamemask">檔案名的比對字元串</param>
/// <param name="strfolder">本地目錄(不得以/結束)</param>
public void get(string strfilenamemask,string strfolder)
string[] strfiles = dir(strfilenamemask);
foreach(string strfile in strfiles)
if(!strfile.equals(""))//一般來說strfiles的最後一個元素可能是空字元串
{
get(strfile,strfolder,strfile);
}
/// 下載下傳一個檔案
/// <param name="strremotefilename">要下載下傳的檔案名</param>
/// <param name="strlocalfilename">儲存在本地時的檔案名</param>
public void get(string strremotefilename,string strfolder,string strlocalfilename)
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))
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));
/// 上傳一批檔案
/// <param name="strfilenamemask">檔案名比對字元(可以包含*和?)</param>
public void put(string strfolder,string strfilenamemask)
string[] strfiles = directory.getfiles(strfolder,strfilenamemask);
//strfile是完整的檔案名(包含路徑)
put(strfile);
/// 上傳一個檔案
/// <param name="strfilename">本地檔案名</param>
public void put(string strfilename)
sendcommand("stor "+path.getfilename(strfilename));
if( !(ireplycode == 125 || ireplycode == 150) )
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();
#region 目錄操作
/// 建立目錄
/// <param name="strdirname">目錄名</param>
public void mkdir(string strdirname)
sendcommand("mkd "+strdirname);
if(ireplycode != 257)
/// 删除目錄
public void rmdir(string strdirname)
sendcommand("rmd "+strdirname);
/// 改變目錄
/// <param name="strdirname">新的工作目錄名</param>
public void chdir(string strdirname)
if(strdirname.equals(".") || strdirname.equals(""))
return;
sendcommand("cwd "+strdirname);
this.strremotepath = strdirname;
#region 内部變量
/// 伺服器傳回的應答資訊(包含應答碼)
private string strmsg;
private string strreply;
/// 伺服器傳回的應答碼
private int ireplycode;
/// 進行控制連接配接的socket
private socket socketcontrol;
/// 傳輸模式
private transfertype trtype;
/// 接收和發送資料的緩沖區
private static int block_size = 512;
byte[] buffer = new byte[block_size];
/// 編碼方式
encoding ascii = encoding.ascii;
#region 内部函數
/// 将一行應答字元串記錄在strreply和strmsg
/// 應答碼記錄在ireplycode
private void readreply()
strmsg = "";
strreply = readline();
ireplycode = int32.parse(strreply.substring(0,3));
/// 建立進行資料連接配接的socket
/// <returns>資料連接配接socket</returns>
private socket createdatasocket()
sendcommand("pasv");
if(ireplycode != 227)
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);
s.connect(ep);
throw new ioexception("can't connect to remote server");
return s;
/// 關閉socket連接配接(用于登入以前)
private void closesocketconnect()
if(socketcontrol!=null)
socketcontrol.close();
socketcontrol = null;
bconnected = false;
/// 讀取socket傳回的所有字元串
/// <returns>包含應答碼的字元串行</returns>
private string readline()
int ibytes = socketcontrol.receive(buffer, buffer.length, 0);
strmsg += ascii.getstring(buffer, 0, ibytes);
if(ibytes < buffer.length)
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],因為隻有最後一行字元串應答碼與資訊之間有空格
strmsg = mess[0];
if(!strmsg.substring(3,1).equals(" "))//傳回字元串正确的是以應答碼(如220開頭,後面接一空格,再接問候字元串)
return readline();
return strmsg;
/// 發送指令并擷取應答碼和最後一行應答字元串
/// <param name="strcommand">指令</param>
private void sendcommand(string strcommand)
byte[] cmdbytes =
encoding.ascii.getbytes((strcommand+"/r/n").tochararray());
socketcontrol.send(cmdbytes, cmdbytes.length, 0);
}
}
該工具類還提供一些檔案操作,注釋也比較豐富,再次向這個類的作者緻敬。
-------------------------------------------------
這個類有一種無法處理的情況,如果ftp伺服器使用ssl或者ssh2安全協定搭建的secure ftp server,就無法連結上了。在網上找到一個可行的辦法,見文章:http://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)
settransfertype(transfertype.binary);
sendcommand("appe " + path.getfilename(strfilename));
if( !(ireplycode == 125 || ireplycode == 150) )
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))
這裡沒有提供指定ftp遠端伺服器上檔案名的功能,要求你上傳的本地檔案名與遠端資料庫檔案名一緻。事實上,我做過指定遠端檔案名的嘗試,即将指令該為“appe local-file remote-file”的格式,但我發現,結果是,它把本地檔案的内容傳上去後,生成了一個檔案名為local-file與remote-file拼接後的新檔案,令人費解!!