65.9K
CodeProject 正在变化。 阅读更多。
Home

基于 FTP 的复制(使用 Sharp FTP Server)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.15/5 (7投票s)

2004年6月26日

3分钟阅读

viewsIcon

68895

downloadIcon

1323

本文通过 Sharp FTP 服务器的示例,讲解如何实现基于 FTP 的复制。

引言

本文演示了如何创建一个使用 FTP 作为传输机制的文件复制过程。Sharp FTP 服务器及其独特的复制功能的开发,对我来说是一次学习 C# 的探索之旅。最终,我学到了一些关于 Windows 服务、XML 解析、多线程和垃圾回收的知识。

背景

Sharp FTP 服务器是一个 C# Windows 服务,负责处理 FTP 服务器应具备的所有基本功能。服务服务器部分的代码基于一个名为 Light FTP Server 的 FTP 服务器实现。客户端代码(用于将文件推送到其他服务器)基于 Jaimon Matthew 编写的 FtpClient 代码。

我编写的主要部分围绕创建服务和实现基于 FTP 的复制。复制的工作方式是,当接收到 PUT 请求(FTP 规范中的 STOR)时,将其重定向/复制到其他 FTP 服务器。服务器通过查看传入的客户端连接将要去的目录,并在“路由器”配置文件 XML 文件中查找可能的备用目标来执行此操作。如果没有找到任何条目,它会将文件存储在客户端告诉服务器存放的位置。否则,它会打开到备用目标的 FTP 连接,并将上传流中的数据写入到其他传出的上传流中。

使用代码

以下代码片段说明了我复制逻辑的大部分内容。第一个片段显示了 FtpServerControl 类中的 UploadFile 方法。其主要目的是将传入的文件写入磁盘。但我增加了一个步骤,让该方法检查路由配置文件(在主 config.xml 文件中定义),以获取传入目录和用户名,从而查看文件是否真的需要去其他地方。如果需要,则调用 RouteTransfer 方法。

private bool UploadFile(string file, FtpType fileType)
{
    Socket socket;
    XmlDocument mapdoc = new XmlDocument();
    XmlNode root;
    XmlNodeList remoteDirs;
    String fileDir;

    if(file == "")
    {
        return false;
    }

    //get config
    mapdoc.Load(m_routerConfigFile);
    root = mapdoc.DocumentElement;

    if (!(file.IndexOf("/")>= 0))
    {
        fileDir = m_currentDirectory + "/";
        file = fileDir + file;
    }
    else
    {
        fileDir = file.Substring(0,file.LastIndexOf("/")) + "/";
    }

    if (!(AllowAccessToDirectory(fileDir)))
        return false;

    file = file.Replace("/","\\");

    try 
    {
        remoteDirs = root.SelectNodes("directory[@user='" +
            m_clientAuthenticationToken.Username + 
            "' and @dirname='" + fileDir + "']/remotedir");

        if(remoteDirs.Count > 0)
            return RouteTransfer(file, remoteDirs);
    }
    catch (Exception ex)
    {
        m_logOutput.WriteDebugOutput("No routing found for " 
                                           + file + "\n" + ex);
    }

    try
    {  
        socket = GetClientConnection();

        if ((fileType == FtpType.Binary) || (fileType == FtpType.Image))
        {
            BinaryWriter writer = 
              new BinaryWriter(new StreamWriter(file, false).BaseStream);
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytes;

            while ((bytes = socket.Receive(buffer))>0)
            {
                writer.Write(buffer, 0, bytes);
            }

            writer.Close();
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            return true;
        }
        else
        {
            StreamWriter writer = new StreamWriter(file, false);
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytes;

            while ((bytes = socket.Receive(buffer))>0)
            {
                writer.Write(System.Text.Encoding.ASCII.GetChars(buffer, 
                                                                0, bytes));
            }

            writer.Close();
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            return true;
        }
    }
    catch (Exception ex)
    {
        m_logOutput.WriteDebugOutput("UploadFile failed on file " 
                                          + file + "\n" + ex.Message);
        m_logOutput.WriteErrorOutput(m_clientAuthenticationToken.Username, 
          "UploadFile failed on file " + file);
        return false;
    }
}

RouteTransfer 方法接收传入的文件以及文件应发送到的服务器列表。然后,它使用 FtpClientControl 类建立到远程服务器的连接。一旦所有 FtpClientControl 类都设置好(并存储在 ArrayList 中),复制就开始了。RouteTransfer 方法遍历传入的数据,然后遍历 FtpClientControl 类的数组,将该数据发送到其他服务器。

private bool RouteTransfer(string file, XmlNodeList remoteDirs)
{        
    Socket socket;
    byte[] buffer = new byte[BUFFER_SIZE];
    int bytes;
    ArrayList remoteConns = new ArrayList();;

    //Get the real filename
    if(file.IndexOf("\\") >= 0)
    {
        file = file.Substring(file.LastIndexOf("\\") + 1);
    }

    //Loop through each remote node and connect to the remote server
    foreach(XmlNode remoteDirectory in remoteDirs)
    {
        XmlAttributeCollection attrColl = remoteDirectory.Attributes;

        string server = "";
        string username = "";
        string password = "";
        int timeoutSeconds = 10;
        int port = 21;

        server = attrColl["server"].Value;
        username = attrColl["user"].Value;
        password = attrColl["pass"].Value;
        timeoutSeconds = 
          System.Convert.ToInt32((attrColl["timeoutSeconds"].Value));
        port = System.Convert.ToInt32((attrColl["port"].Value));

        if(server != "" && username != "" && password != "")
        {
            FtpClientControl ftpClient = 
              new FtpClientControl(server, username, 
              password, timeoutSeconds, port);

            ftpClient.RemotePath = remoteDirectory.InnerText;
            ftpClient.Login();
            ftpClient.StartUpload(file);
            remoteConns.Add(ftpClient);
        }
        else
        {
            m_logOutput.WriteErrorOutput(m_clientAuthenticationToken.Username, 
              "Missing a server, username, or password for the " + 
              remoteDirectory.Value + " remotedirectory.");

            return false;
        }
    }

    socket = GetClientConnection();

    //read data from client and send data elsewhere
    while ((bytes = socket.Receive(buffer)) > 0)
    {
        foreach(FtpClientControl ftc in remoteConns)
        {
            ftc.SendUploadData(buffer, bytes);
        }
    }

    //When finished, close remote client conns
    foreach(FtpClientControl ftc in remoteConns)
    {
        ftc.FinishUpload();
    }

    socket.Shutdown(SocketShutdown.Both);
    socket.Close();

    return true;
}

下面列出的方法是 StartUploadSendUploadDataFinishUpload。它们是 FtpClientControl 类中的方法,由 FtpServerControl 类中的 RouteTransfer 方法使用,用于将传入的上传复制到其他服务器。

public void StartUpload(string fileName)
{
    if ( !m_loggedin ) Login();

    m_transferSocket = createDataSocket();

    sendCommand("STOR " + fileName);

    if ( m_resultCode != 125 && m_resultCode != 150 ) 
        throw new FtpException(m_result.Substring(4));

    Debug.WriteLine( "Uploading file " + fileName + 
              " to " + m_remotePath, "FtpClient" );
}

public void SendUploadData(byte[] buffer, int bytes)
{
    m_transferSocket.Send(buffer, bytes, 0);
}

public void FinishUpload()
{
    if (m_transferSocket.Connected)
    {
        m_transferSocket.Close();
    }
    readResponse();

    if( m_resultCode != 226 && m_resultCode != 250 ) 
        throw new FtpException(m_result.Substring(4));

    m_transferSocket = null;
}

关注点

我想首先指出的是,我使用了 SharpDevelop 开发环境来开发这个程序。虽然 SharpDevelop 不如 VS.NET 稳定,但它绝对更便宜。可以在 这里 查看。

另一个需要说明的是,我花了很多时间在这个项目上,不断调整服务存根中的 OnStartOnStop 方法,主要是因为 FTP 服务器代码是多线程的。它在 OnStart 时一直挂起,而当它尝试停止时,它根本不会停止(至少不会立即停止)。因此,我不得不添加 SharpServerListener 类作为成员类,以便能够强制它关闭其客户端线程。

我不能 100% 确定整个项目中的所有代码都编写正确,但我知道它是可行的(至少在我的 PC 上是可行的)。将所有本地和远程用户名和密码明文保存绝对是一个安全隐患。

如果我需要为服务器添加更多增强功能,我会这样做:

  1. 一个独立的机制来存储、更新和检索安全信息。
  2. 能够路由其他操作(重命名和删除文件)。
  3. 一个图形化管理控制台。
  4. 为服务提供一个安装过程。
  5. 一种更好的方式供服务器处理传入的命令。
  6. 一种更好的方式来管理客户端线程(线程池?)。
  7. 能够将文件“缓冲”到磁盘,稍后发送到远程服务器。
© . All rights reserved.