Kelp-Space 是記錄一些生活雜事的Blog
如有任何程式設計的問題歡迎到 飛特技術論壇 討論

2008-08-18

使用Socket完成FTP檔案存取

為了使用.net framework 2.0完成FTP檔案傳輸,真是花了不少時間。
這隻程式用了Barakbbn.Freeware.Net.Sockets。
使用時總共要加入兩個.cs檔(Barakbbn.Freeware.Net.SocketsKelp.Opensource.FTPClient)。
相關說明有打在註解上,如果還是不清楚的話可以留言發問。
Barakbbn.Freeware.Net.Sockets相關文章

Kelp.Opensource.FTPClient 複製程式碼(Copy to clipboard)

using System;
using System.Net;
using System.IO;
using System.Text;
using Barakbbn.Freeware.Net.Sockets;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  本程式需要使用額外的socket Barakbbn.Freeware.Net.Sockets
//
//  在程式碼上方加入 using Kelp.Opensource.FTPClient;
//
//
//  P.S.下達ftp.Gep()後不會等抓完檔案才執行ftp.DisConnect()
//      檔案上傳及下載是透過事件觸發的
//      "//檔案下載完成"搜尋此字串可以找到下載完成程式位置  事件為GetFileComplete
//      "//檔案上傳完成"搜尋此字串可以找到上傳完成程式位置  事件為PutFileComplete
//
//
//  FTPClient ftp = new FTPClient();    //宣告
//
//  void Connect()                               //連線
//  void DisConnect()                            //關閉連線
//  void SetTransferType(TransferType ttType)    //設定傳輸模式
//  TransferType GetTransferType()               //取得傳輸模式
//  string[] Dir(string strMask)                 //取得文件列表  (文件名稱~可用*)
//  void Delete(string strFileName)              //刪除
//  void Rename(string strOldFileName, string strNewFileName)    //Rename(如果要改的名稱已存在將會覆蓋)
//  void Get(string strRemoteFileName, string strLocalFileName)  //下載文件
//  void Put(string strFileName)                 //上傳文件
//  void MkDir(string strDirName)                //建立目錄
//  void RmDir(string strDirName)                //刪除目錄
//  void ChDir(string strDirName)                //改變目錄
//
//  來源:打奈特(.net)
//  修改:Kelp
//
//  修改記錄
//      2008/08/16
//          因為要使用.net framework2.0來完成FTP傳輸因此到處尋找利用socket進行FTP的程式碼
//
//      2008/08/18
//          原程式因為不斷掃描socket的緩衝區造成錯誤
//          將System.Net.Socket換成Barakbbn.Freeware.Net.Sockets
//
//      2008/08/19
//          修正不穩定因素
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*  下載範例程式
    string[] sdata;
  
    FTPClient ftp = new FTPClient();    宣告
    ftp.GetFileComplete += new EventHandler<LocalGFileAddressData>(ftp_GetFileComplete);
    ftp.RemoteHost = "www.test.com";    設定HOST
    ftp.RemoteUser = "XXXXX";           設定使用者帳號
    ftp.RemotePass = "XXXXX";           設定密碼
    ftp.Connect();                      連線
    ftp.ChDir("/www/");                 切換目錄
    sdata = ftp.Dir("");                取得目前位置內的資料夾及檔案名稱
    ftp.Get("1.jpg", "D:\\1.jpg");      下載1.jpg另存為F:\\1.jpg
    ftp.DisConnect();                   結束連線
   
    private void ftp_GetFileComplete(object sender, LocalGFileAddressData e)
    {
        MessageBox.Show("Finish " + e.LocalFileAddress);
    }
*/
/*  上傳範例程式
    string[] sdata;

    FTPClient ftp = new FTPClient();
    ftp.PutFileComplete += new EventHandler<LocalPFileAddressData>(ftp_PutFileComplete);
    ftp.RemoteHost = "www.test.com";    設定HOST
    ftp.RemoteUser = "XXXXX";           設定使用者帳號
    ftp.RemotePass = "XXXXX";           設定密碼
    ftp.Connect();                      連線
    ftp.Put("F:\\2.jpg");               上傳檔案
       
    private void ftp_PutFileComplete(object sender, LocalPFileAddressData e)
    {
        MessageBox.Show("Finish " + e.LocalFileAddress);
    }
*/

namespace Kelp.Opensource.FTPClient
{
    public class FTPClient
    {
        //////////////////////////記憶體宣告/////////////////////////////////////////////////////////////////////////////
        #region Field

        private bool Received = false;      //當基礎連接socket回應結束後為true
        private bool DataReceived = false;  //當資料連接socket回應結束後為true
        private string strMsg;              //伺服器回應的訊息
        private string strReply;            //伺服器回應的答應碼
        private int iReplyCode;             //伺服器回應的答應碼
        private TcpClient socketControl;    //socket物件
        private TransferType trType;        //傳輸模式
        byte[] buffer = new byte[0];

        Encoding ASCII = Encoding.GetEncoding("big5");  //編碼方式
        public enum TransferType { Binary, ASCII };     //傳輸模式

        bool Downloading = false;
        bool Uploading = false;
        string SOutputAddress = "";         //檔案下載路徑
        FileStream output;                  //檔案下載串流
        string SInputAddress = "";          //檔案上傳路徑
        FileStream input;                   //檔案上傳串流
        int iBytes = 0;                     //檔案上傳指標

        TcpClient socketGetData;
        TcpClient socketSendData;
        #endregion
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //////////////////////////Client/////////////////////////////////////////////////////////////////////////////////
        #region FTPClient
        public FTPClient()
        {
            strRemoteHost = "";
            strRemotePath = "";
            strRemoteUser = "";
            strRemotePass = "";
            strRemotePort = 21;
            bConnected = false;
        }

        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 Loging Property
        private string strRemoteHost;
        public string RemoteHost
        {
            get
            {
                return strRemoteHost;
            }
            set
            {
                strRemoteHost = value;
            }
        }

        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
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //////////////////////////事件///////////////////////////////////////////////////////////////////////////////////
        public virtual event EventHandler<LocalGFileAddressData> GetFileComplete;
        public virtual event EventHandler<LocalPFileAddressData> PutFileComplete;
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //////////////////////////Class函數//////////////////////////////////////////////////////////////////////////////
        public void Connect()                               //連線
        {
            socketControl = new TcpClient();
            socketControl.Key = "Bace";     //設定Key為基礎連接socket
            WireTcpClient(socketControl);   //設定socket對應事件

            try
            {
                Received = false;
                socketControl.Connect(RemoteHost, Convert.ToInt32(strRemotePort));
            }
            catch (Exception)
            {
                throw new IOException("Couldn't connect to remote server");
            }
            // 取得回應
            while (!Received) ;
            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);
        }

        public void DisConnect()                            //關閉連線
        {
            if (socketControl != null)
            {
                SendCommand("QUIT");
            }
            if (socketSendData != null)
                socketSendData.Close();
            if (socketSendData != null)
                socketSendData.Close();
            CloseSocketConnect();
        }

        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;
        }

        public string[] Dir(string strMask)                 //取得文件列表  (文件名稱~可用*)
        {
            if (!bConnected)    // 先確定是否已連線
                Connect();

            DataReceived = false;
            TcpClient socketData = CreateDataSocket();  //建立並行數據連接的socket
            socketData.Key = "Data";
            SendCommand("NLST " + strMask);             //傳送指令
            //分析回應碼
            if (!(iReplyCode == 150 || iReplyCode == 125 || iReplyCode == 226))
            {
                throw new IOException(strReply.Substring(4));
            }
            //取得結果
            while (!DataReceived) ;     //等待傳輸結束
            while (true)
            {
                int iBytes = socketData.Receive(buffer, 0, buffer.Length);
                if (iBytes < 0)
                    break;
                strMsg += ASCII.GetString(buffer, 0, iBytes);
            }
            char[] seperator = { '\n' };
            string[] strsFileList = strMsg.Split(seperator);
            for (int ILoop = 0; ILoop < strsFileList.GetUpperBound(0); ILoop++)
            {
                strsFileList[ILoop] = strsFileList[ILoop].TrimEnd();
            }

            socketData.Close();
            return strsFileList;
        }

        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)    //Rename(如果要改的名稱已存在將會覆蓋)
        {
            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));
            }
        }

        public void Get(string strRemoteFileName, string strLocalFileName)  //下載文件
        {
            if (!bConnected)
                Connect();
            SetTransferType(TransferType.Binary);
            if (strLocalFileName.Equals(""))
            {
                strLocalFileName = strRemoteFileName;
            }
            if (!File.Exists(strLocalFileName))
            {
                Stream st = File.Create(strLocalFileName);
                st.Close();
            }
            SOutputAddress = strLocalFileName;
            Downloading = true;
            socketGetData = CreateDataSocket();
            socketGetData.Key = "GetFile";

            output = new FileStream(SOutputAddress, FileMode.Append);
            SendCommand("RETR " + strRemoteFileName);
            if (!(iReplyCode == 150 || iReplyCode == 125 || iReplyCode == 226 || iReplyCode == 250))
            {
                throw new IOException(strReply.Substring(4));
            }
        }

        public void Put(string strFileName)                 //上傳文件
        {
            if (!bConnected)
                Connect();
            socketSendData = CreateDataSocket();
            socketSendData.Key = "SendFile";
            SendCommand("STOR " + Path.GetFileName(strFileName));
            if (!(iReplyCode == 125 || iReplyCode == 150))
            {
                throw new IOException(strReply.Substring(4));
            }
            SInputAddress = strFileName;
            Uploading = true;
            input = new FileStream(strFileName, FileMode.Open);

            iBytes = input.Read(buffer, 0, buffer.Length);
            socketSendData.Send(buffer, 0, iBytes);
        }

        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;
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //////////////////////////內部函數///////////////////////////////////////////////////////////////////////////////
        private TcpClient CreateDataSocket()        //建立並行數據連接的Socket
        {
            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];
            TcpClient s = new TcpClient();
            WireTcpClient(s);
            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;
        }

        private void CloseSocketConnect()           //關閉Socket的連線(用在登入以前)
        {
            if (socketControl != null)
            {
                socketControl.Close();
                socketControl = null;
            }
            bConnected = false;
        }

        private void SendCommand(String strCommand) //傳送命令並取得回應碼和最後一行的回應字串
        {
            Encoding e = Encoding.GetEncoding("big5");
            Byte[] cmdBytes = e.GetBytes((strCommand + "\r\n").ToCharArray());
            Received = false;
            socketControl.Send(cmdBytes, 0, cmdBytes.Length);
            if (strCommand != "QUIT")
                while (!Received) ;
        }

        void WireTcpClient(TcpClient tcpClient)     //建立Socket事件
        {
            tcpClient.Error += new EventHandler<SocketErrorEventArgs>(socket_Error);
            tcpClient.SendCompleted += new EventHandler(socket_SendCompleted);
            tcpClient.Disconnected += new EventHandler<SocketDisconnectedEventArgs>(socket_Disconnected);
            tcpClient.DataAvailable += new EventHandler(socket_DataAvailable);
            tcpClient.DataArrived += new EventHandler<SocketDataArrivedEventArgs>(socket_DataArrived);
            tcpClient.Connected += new EventHandler(socket_Connected);
            tcpClient.DataLost += new EventHandler<SocketDataArrivedEventArgs>(socket_DataLost);
            tcpClient.SendProgressChanged += new EventHandler<SocketDataSendProgressChangedEventArgs>(socket_SendProgressChanged);
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //////////////////////////Socket事件/////////////////////////////////////////////////////////////////////////////
        void socket_DataLost(object sender, SocketDataArrivedEventArgs e)
        {

        }
        void socket_DataArrived(object sender, SocketDataArrivedEventArgs e)
        {
            TcpClient Socket = sender as TcpClient;
            int iBytes;

            switch (Socket.Key)
            {
                case "Bace":
                    try
                    {
                        buffer = (byte[])ReDim(buffer, e.ReceivedSize);
                        iBytes = socketControl.Receive(buffer, 0, buffer.Length);
                        strReply = ASCII.GetString(buffer, 0, iBytes);
                        iReplyCode = Int32.Parse(strReply.Substring(0, 3));
                    }
                    catch
                    {
                        break;
                    }

                    if (iReplyCode == 226)
                    {
                        if (Downloading)
                        {
                            //檔案下載完成
                            Downloading = false;
                            output.Close();
                            LocalGFileAddressData g = new LocalGFileAddressData(SOutputAddress);
                            GetFileComplete(socketGetData, g);
                        }
                        else if (Uploading)
                        {
                            //檔案上傳完成
                            Uploading = false;
                            input.Close();
                            LocalPFileAddressData h = new LocalPFileAddressData(SInputAddress);
                            PutFileComplete(socketSendData, h);
                        }
                    }
                   
                    Received = true;
                    break;
                case "Data":
                    DataReceived = true;
                    break;
                case "GetFile":
                    while (true)
                    {
                        iBytes = Socket.Receive(buffer, 0, buffer.Length);
                        if (iBytes < 0)
                        {
                            break;
                        }
                        output.Write(buffer, 0, iBytes);
                    }
                    break;
            }
        }
        void socket_Connected(object sender, EventArgs e)
        {

        }
        void socket_DataAvailable(object sender, EventArgs e)
        {
           
        }
        void socket_Disconnected(object sender, SocketDisconnectedEventArgs e)
        {
            TcpClient Socket = sender as TcpClient;
            switch (Socket.Key)
            {
                case "GetFile":
                    if (Socket.IsConnected)
                    {
                        Received = false;
                        Socket.Close();
                    }
                    break;
            }
        }
        void socket_SendCompleted(object sender, EventArgs e)
        {
            TcpClient Socket = sender as TcpClient;
            switch (Socket.Key)
            {
                case "SendFile":
                    iBytes = input.Read(buffer, 0, buffer.Length);
                    if (iBytes > 0)
                        Socket.Send(buffer, 0, iBytes);
                    else
                    {
                        if (Socket.IsConnected)
                        {
                            Received = false;
                            Socket.Close();
                        }
                    }
                    break;
            }
        }
        void socket_Error(object sender, SocketErrorEventArgs e)
        {

        }
        void socket_SendProgressChanged(object sender, SocketDataSendProgressChangedEventArgs e)
        {

        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //////////////////////////通用副程式區域/////////////////////////////////////////////////////////////////////////
        public static Array ReDim(Array origArray, Int32 desiredSize)   //重新宣告記憶體
        {
            Type t = origArray.GetType().GetElementType();
            Array newArray = Array.CreateInstance(t, desiredSize);
            Array.Copy(origArray, 0, newArray, 0, Math.Min(origArray.Length, desiredSize));
            return newArray;
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    }
    public class LocalGFileAddressData : EventArgs
    {
        private string sLocalFileAddress;

        public LocalGFileAddressData(string sLocalFileAddress)
        {
            this.sLocalFileAddress = sLocalFileAddress;
        }
        public string LocalFileAddress
        {
            get { return this.sLocalFileAddress; }
        }
    }
    public class LocalPFileAddressData : EventArgs
    {
        private string sLocalFileAddress;

        public LocalPFileAddressData(string sLocalFileAddress)
        {
            this.sLocalFileAddress = sLocalFileAddress;
        }
        public string LocalFileAddress
        {
            get { return this.sLocalFileAddress; }
        }
    }
}

2 則留言:

匿名 提到...

請問:
我用你的Sample Code 去測試的結果,檔案大小(Size)不一致,例如,原檔案是120K,結果我抓下來的只有10K,不知是哪裡出錯了,還是我需要設定修什麼才可以正確抓取檔案呢?!

Kelp 提到...

請到飛特技術論壇討論,這隻程式是舊版的。

張貼留言