天天看點

FTP上傳檔案示例

 首先,你需要一個測試環境,我在自己的機器上搭建了一個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拼接後的新檔案,令人費解!!