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






3.15/5 (7投票s)
2004年6月26日
3分钟阅读

68895

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;
}
下面列出的方法是 StartUpload
、SendUploadData
和 FinishUpload
。它们是 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 稳定,但它绝对更便宜。可以在 这里 查看。
另一个需要说明的是,我花了很多时间在这个项目上,不断调整服务存根中的 OnStart
和 OnStop
方法,主要是因为 FTP 服务器代码是多线程的。它在 OnStart
时一直挂起,而当它尝试停止时,它根本不会停止(至少不会立即停止)。因此,我不得不添加 SharpServerListener
类作为成员类,以便能够强制它关闭其客户端线程。
我不能 100% 确定整个项目中的所有代码都编写正确,但我知道它是可行的(至少在我的 PC 上是可行的)。将所有本地和远程用户名和密码明文保存绝对是一个安全隐患。
如果我需要为服务器添加更多增强功能,我会这样做:
- 一个独立的机制来存储、更新和检索安全信息。
- 能够路由其他操作(重命名和删除文件)。
- 一个图形化管理控制台。
- 为服务提供一个安装过程。
- 一种更好的方式供服务器处理传入的命令。
- 一种更好的方式来管理客户端线程(线程池?)。
- 能够将文件“缓冲”到磁盘,稍后发送到远程服务器。