大约在去年的12月份,我们开始着手设计和开发这项功能,而该项功能主要是解决类似于一些帖子附件(图片或文件)访问比较频繁,同时附件的体积又比较大,从而造成对主站服务器访问压力过大的问题。而实现了该项功能之后,在一些合作伙伴的站点上使用了一段时间,发现该功能明显的降低了主站服务器的负载,使其可以节省更多的资源(cpu,内存等) 用于处理用户的其它访问请求。
大约在去年的12月份,我们开始着手设计和开发这项功能,而该项功能主要是解决类似于一些
帖子附件(图片或文件)访问比较频繁,同时附件的体积又比较大,从而造成对主站服务器访问压
力过大的问题。而实现了该项功能之后,在一些合作伙伴的站点上使用了一段时间,发现该功能明
显的降低了主站服务器的负载,使其可以节省更多的资源(cpu,内存等) 用于处理用户的其它访问
请求。
下面就简要介绍一下该功能的一些实现细节, 该项功能所实现的流程如下图所示:
而主要的核心就是采用FTP协议上传附件到远程的服务器上,这样当用户点开网页或进行附件下
载时,就会将链接指向远程的FTP服务上(该服务器要支持HTTP协议访问其资源)。而这个类的原
型链接如下:
http://www.csharphelp.com/archives/archive9.html
本人在其基础上修改了该类在DEBUG模式下上传文件过程中的BUG,同时翻译了其注释内容。大
家可在dicuz.common.dll(discuz!nt 2.1以后的版块)的中找到该类(使用Reflector)。
下面是其核心代码(您可在下个开源版本中获取该类的全部代码):
Code
/**//// <summary>
/// FTP类
/// </summary>
public class FTP
{
变量声明#region 变量声明
/**//// <summary>
/// 服务器连接地址
/// </summary>
public string server;
/**//// <summary>
/// 登陆帐号
/// </summary>
public string user;
/**//// <summary>
/// 登陆口令
/// </summary>
public string pass;
/**//// <summary>
/// 端口号
/// </summary>
public int port;
/**//// <summary>
/// 无响应时间(FTP在指定时间内无响应)
/// </summary>
public int timeout;
/**//// <summary>
/// 服务器错误状态信息
/// </summary>
public string errormessage;
/**//// <summary>
/// 服务器状态返回信息
/// </summary>
private string messages;
/**//// <summary>
/// 服务器的响应信息
/// </summary>
private string responseStr;
/**//// <summary>
/// 链接模式(主动或被动,默认为被动)
/// </summary>
private bool passive_mode;
/**//// <summary>
/// 上传或下载信息字节数
/// </summary>
private long bytes_total;
/**//// <summary>
/// 上传或下载的文件大小
/// </summary>
private long file_size;
/**//// <summary>
/// 主套接字
/// </summary>
private Socket main_sock;
/**//// <summary>
/// 要链接的网络地址终结点
/// </summary>
private IPEndPoint main_ipEndPoint;
/**//// <summary>
/// 侦听套接字
/// </summary>
private Socket listening_sock;
/**//// <summary>
/// 数据套接字
/// </summary>
private Socket data_sock;
/**//// <summary>
/// 要链接的网络数据地址终结点
/// </summary>
private IPEndPoint data_ipEndPoint;
/**//// <summary>
/// 用于上传或下载的文件流对象
/// </summary>
private FileStream file;
/**//// <summary>
/// 与FTP服务器交互的状态值
/// </summary>
private int response;
/**//// <summary>
/// 读取并保存当前命令执行后从FTP服务器端返回的数据信息
/// </summary>
private string bucket;
#endregion
构造函数#region 构造函数
/**//// <summary>
/// 构造函数
/// </summary>
public FTP()
{
server = null;
user = null;
pass = null;
port = 21;
passive_mode = true;
main_sock = null;
main_ipEndPoint = null;
listening_sock = null;
data_sock = null;
data_ipEndPoint = null;
file = null;
bucket = "";
bytes_total = 0;
timeout = 10000; //无响应时间为10秒
messages = "";
errormessage = "";
}
/**//// <summary>
/// 构造函数
/// </summary>
/// <param name="server">服务器IP或名称</param>
/// <param name="user">登陆帐号</param>
/// <param name="pass">登陆口令</param>
public FTP(string server, string user, string pass)
{
this.server = server;
this.user = user;
this.pass = pass;
port = 21;
passive_mode = true;
main_sock = null;
main_ipEndPoint = null;
listening_sock = null;
data_sock = null;
data_ipEndPoint = null;
file = null;
bucket = "";
bytes_total = 0;
timeout = 10000; //无响应时间为10秒
messages = "";
errormessage = "";
}
/**//// <summary>
/// 构造函数
/// </summary>
/// <param name="server">服务器IP或名称</param>
/// <param name="port">端口号</param>
/// <param name="user">登陆帐号</param>
/// <param name="pass">登陆口令</param>
public FTP(string server, int port, string user, string pass)
{
this.server = server;
this.user = user;
this.pass = pass;
this.port = port;
passive_mode = true;
main_sock = null;
main_ipEndPoint = null;
listening_sock = null;
data_sock = null;
data_ipEndPoint = null;
file = null;
bucket = "";
bytes_total = 0;
timeout = 10000; //无响应时间为10秒
messages = "";
errormessage = "";
}
/**//// <summary>
/// 构造函数
/// </summary>
/// <param name="server">服务器IP或名称</param>
/// <param name="port">端口号</param>
/// <param name="user">登陆帐号</param>
/// <param name="pass">登陆口令</param>
/// <param name="mode">链接方式</param>
public FTP(string server, int port, string user, string pass, int mode)
{
this.server = server;
this.user = user;
this.pass = pass;
this.port = port;
passive_mode = mode <= 1 ? true : false;
main_sock = null;
main_ipEndPoint = null;
listening_sock = null;
data_sock = null;
data_ipEndPoint = null;
file = null;
bucket = "";
bytes_total = 0;
this.timeout = 10000; //无响应时间为10秒
messages = "";
errormessage = "";
}
/**//// <summary>
/// 构造函数
/// </summary>
/// <param name="server">服务器IP或名称</param>
/// <param name="port">端口号</param>
/// <param name="user">登陆帐号</param>
/// <param name="pass">登陆口令</param>
/// <param name="mode">链接方式</param>
/// <param name="timeout">无响应时间(限时),单位:秒 (小于或等于0为不受时间限制)</param>
public FTP(string server, int port, string user, string pass, int mode, int timeout_sec)
{
this.server = server;
this.user = user;
this.pass = pass;
this.port = port;
passive_mode = mode <= 1 ? true : false;
main_sock = null;
main_ipEndPoint = null;
listening_sock = null;
data_sock = null;
data_ipEndPoint = null;
file = null;
bucket = "";
bytes_total = 0;
this.timeout = (timeout_sec <= 0) ? int.MaxValue : (timeout_sec * 1000); //无响应时间
messages = "";
errormessage = "";
}
#endregion
属性#region 属性
/**//// <summary>
/// 当前是否已连接
/// </summary>
public bool IsConnected
{
get
{
if (main_sock != null)
return main_sock.Connected;
return false;
}
}
/**//// <summary>
/// 当message缓冲区有数据则返回
/// </summary>
public bool MessagesAvailable
{
get
{
if (messages.Length > 0)
return true;
return false;
}
}
/**//// <summary>
/// 获取服务器状态返回信息, 并清空messages变量
/// </summary>
public string Messages
{
get
{
string tmp = messages;
messages = "";
return tmp;
}
}
/**//// <summary>
/// 最新指令发出后服务器的响应
/// </summary>
public string ResponseString
{
get
{
return responseStr;
}
}
/**//// <summary>
///在一次传输中,发送或接收的字节数
/// </summary>
public long BytesTotal
{
get
{
return bytes_total;
}
}
/**//// <summary>
///被下载或上传的文件大小,当文件大小无效时为0
/// </summary>
public long FileSize
{
get
{
return file_size;
}
}
/**//// <summary>
/// 链接模式:
/// true 被动模式 [默认]
/// false: 主动模式
/// </summary>
public bool PassiveMode
{
get
{
return passive_mode;
}
set
{
passive_mode = value;
}
}
#endregion
操作#region 操作
/**//// <summary>
/// 操作失败
/// </summary>
private void Fail()
{
Disconnect();
errormessage += responseStr;
//throw new Exception(responseStr);
}
private void SetBinaryMode(bool mode)
{
if (mode)
SendCommand("TYPE I");
else
SendCommand("TYPE A");
ReadResponse();
if (response != 200)
Fail();
}
private void SendCommand(string command)
{
Byte[] cmd = Encoding.ASCII.GetBytes((command + "\r\n").ToCharArray());
if (command.Length > 3 && command.Substring(0, 4) == "PASS")
{
messages = "\rPASS xxx";
}
else
{
messages = "\r" + command;
}
try
{
main_sock.Send(cmd, cmd.Length, 0);
}
catch (Exception ex)
{
try
{
Disconnect();
errormessage += ex.Message;
return;
}
catch
{
main_sock.Close();
file.Close();
main_sock = null;
main_ipEndPoint = null;
file = null;
}
}
}
private void FillBucket()
{
Byte[] bytes = new Byte[512];
long bytesgot;
int msecs_passed = 0;
while (main_sock.Available < 1)
{
System.Threading.Thread.Sleep(50);
msecs_passed += 50;
//当等待时间到,则断开链接
if (msecs_passed > timeout)
{
Disconnect();
errormessage += "Timed out waiting on server to respond.";
return;
}
}
while (main_sock.Available > 0)
{
bytesgot = main_sock.Receive(bytes, 512, 0);
bucket += Encoding.ASCII.GetString(bytes, 0, (int)bytesgot);
System.Threading.Thread.Sleep(50);
}
}
private string GetLineFromBucket()
{
int i;
string buf = "";
if ((i = bucket.IndexOf('\n')) < 0)
{
while (i < 0)
{
FillBucket();
i = bucket.IndexOf('\n');
}
}
buf = bucket.Substring(0, i);
bucket = bucket.Substring(i + 1);
return buf;
}
/**//// <summary>
/// 返回服务器端返回信息
/// </summary>
private void ReadResponse()
{
string buf;
messages = "";
while (true)
{
buf = GetLineFromBucket();
if (Regex.Match(buf, "^[0-9]+ ").Success)
{
responseStr = buf;
response = int.Parse(buf.Substring(0, 3));
break;
}
else
messages += Regex.Replace(buf, "^[0-9]+-", "") + "\n";
}
}
/**//// <summary>
/// 打开数据套接字
/// </summary>
private void OpenDataSocket()
{
if (passive_mode)
{
string[] pasv;
string server;
int port;
Connect();
SendCommand("PASV");
ReadResponse();
if (response != 227)
Fail();
try
{
int i1, i2;
i1 = responseStr.IndexOf('(') + 1;
i2 = responseStr.IndexOf(')') - i1;
pasv = responseStr.Substring(i1, i2).Split(',');
}
catch (Exception)
{
Disconnect();
errormessage += "Malformed PASV response: " + responseStr;
return ;
}
if (pasv.Length < 6)
{
Disconnect();
errormessage += "Malformed PASV response: " + responseStr;
return ;
}
server = String.Format("{0}.{1}.{2}.{3}", pasv[0], pasv[1], pasv[2], pasv[3]);
port = (int.Parse(pasv[4]) << 8) + int.Parse(pasv[5]);
try
{
CloseDataSocket();
data_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
#if NET1
data_ipEndPoint = new IPEndPoint(Dns.GetHostByName(server).AddressList[0], port);
#else
data_ipEndPoint = new IPEndPoint(System.Net.Dns.GetHostEntry(server).AddressList[0], port);
#endif
data_sock.Connect(data_ipEndPoint);
}
catch (Exception ex)
{
errormessage += "Failed to connect for data transfer: " + ex.Message;
return ;
}
}
else
{
Connect();
try
{
CloseDataSocket();
listening_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 对于端口,则发送IP地址.下面则提取相应信息
string sLocAddr = main_sock.LocalEndPoint.ToString();
int ix = sLocAddr.IndexOf(':');
if (ix < 0)
{
errormessage += "Failed to parse the local address: " + sLocAddr;
return;
}
string sIPAddr = sLocAddr.Substring(0, ix);
// 系统自动绑定一个端口号(设置 port = 0)
System.Net.IPEndPoint localEP = new IPEndPoint(IPAddress.Parse(sIPAddr), 0);
listening_sock.Bind(localEP);
sLocAddr = listening_sock.LocalEndPoint.ToString();
ix = sLocAddr.IndexOf(':');
if (ix < 0)
{
errormessage += "Failed to parse the local address: " + sLocAddr;
}
int nPort = int.Parse(sLocAddr.Substring(ix + 1));
// 开始侦听链接请求
listening_sock.Listen(1);
string sPortCmd = string.Format("PORT {0},{1},{2}",
sIPAddr.Replace('.', ','),
nPort / 256, nPort % 256);
SendCommand(sPortCmd);
ReadResponse();
if (response != 200)
Fail();
}
catch (Exception ex)
{
errormessage += "Failed to connect for data transfer: " + ex.Message;
return;
}
}
}
private void ConnectDataSocket()
{
if (data_sock != null) // 已链接
return;
try
{
data_sock = listening_sock.Accept(); // Accept is blocking
listening_sock.Close();
listening_sock = null;
if (data_sock == null)
{
throw new Exception("Winsock error: " +
Convert.ToString(System.Runtime.InteropServices.Marshal.GetLastWin32Error()));
}
}
catch (Exception ex)
{
errormessage += "Failed to connect for data transfer: " + ex.Message;
}
}
private void CloseDataSocket()
{
if (data_sock != null)
{
if (data_sock.Connected)
{
data_sock.Close();
}
data_sock = null;
}
data_ipEndPoint = null;
}
/**//// <summary>
/// 关闭所有链接
/// </summary>
public void Disconnect()
{
CloseDataSocket();
if (main_sock != null)
{
if (main_sock.Connected)
{
SendCommand("QUIT");
main_sock.Close();
}
main_sock = null;
}
if (file != null)
file.Close();
main_ipEndPoint = null;
file = null;
}
/**//// <summary>
/// 链接到FTP服务器
/// </summary>
/// <param name="server">要链接的IP地址或主机名</param>
/// <param name="port">端口号</param>
/// <param name="user">登陆帐号</param>
/// <param name="pass">登陆口令</param>
public void Connect(string server, int port, string user, string pass)
{
this.server = server;
this.user = user;
this.pass = pass;
this.port = port;
Connect();
}
/**//// <summary>
/// 链接到FTP服务器
/// </summary>
/// <param name="server">要链接的IP地址或主机名</param>
/// <param name="user">登陆帐号</param>
/// <param name="pass">登陆口令</param>
public void Connect(string server, string user, string pass)
{
this.server = server;
this.user = user;
this.pass = pass;
Connect();
}
/**//// <summary>
/// 链接到FTP服务器
/// </summary>
public bool Connect()
{
if (server == null)
{
errormessage += "No server has been set.\r\n";
}
if (user == null)
{
errormessage += "No server has been set.\r\n";
}
if (main_sock != null)
if (main_sock.Connected)
return true;
try
{
main_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
#if NET1
main_ipEndPoint = new IPEndPoint(Dns.GetHostByName(server).AddressList[0], port);
#else
main_ipEndPoint = new IPEndPoint(System.Net.Dns.GetHostEntry(server).AddressList[0], port);
#endif
main_sock.Connect(main_ipEndPoint);
}
catch (Exception ex)
{
errormessage += ex.Message;
return false;
}
ReadResponse();
if (response != 220)
Fail();
SendCommand("USER " + user);
ReadResponse();
switch (response)
{
case 331:
if (pass == null)
{
Disconnect();
errormessage += "No password has been set.";
return false;
}
SendCommand("PASS " + pass);
ReadResponse();
if (response != 230)
{
Fail();
return false;
}
break;
case 230:
break;
}
return true;
}
/**//// <summary>
/// 获取FTP当前(工作)目录下的文件列表
/// </summary>
/// <returns>返回文件列表数组</returns>
public ArrayList List()
{
Byte[] bytes = new Byte[512];
string file_list = "";
long bytesgot = 0;
int msecs_passed = 0;
ArrayList list = new ArrayList();
Connect();
OpenDataSocket();
SendCommand("LIST");
ReadResponse();
switch (response)
{
case 125:
case 150:
break;
default:
CloseDataSocket();
throw new Exception(responseStr);
}
ConnectDataSocket();
while (data_sock.Available < 1)
{
System.Threading.Thread.Sleep(50);
msecs_passed += 50;
if (msecs_passed > (timeout / 10))
{
break;
}
}
while (data_sock.Available > 0)
{
bytesgot = data_sock.Receive(bytes, bytes.Length, 0);
file_list += Encoding.ASCII.GetString(bytes, 0, (int)bytesgot);
System.Threading.Thread.Sleep(50);
}
CloseDataSocket();
ReadResponse();
if (response != 226)
throw new Exception(responseStr);
foreach (string f in file_list.Split('\n'))
{
if (f.Length > 0 && !Regex.Match(f, "^total").Success)
list.Add(f.Substring(0, f.Length - 1));
}
return list;
}
/**//// <summary>
/// 获取到文件名列表
/// </summary>
/// <returns>返回文件名列表</returns>
public ArrayList ListFiles()
{
ArrayList list = new ArrayList();
foreach (string f in List())
{
if ((f.Length > 0))
{
if ((f[0] != 'd') && (f.ToUpper().IndexOf("<DIR>") < 0))
list.Add(f);
}
}
return list;
}
/**//// <summary>
/// 获取路径列表
/// </summary>
/// <returns>返回路径列表</returns>
public ArrayList ListDirectories()
{
ArrayList list = new ArrayList();
foreach (string f in List())
{
if (f.Length > 0)
{
if ((f[0] == 'd') || (f.ToUpper().IndexOf("<DIR>") >= 0))
list.Add(f);
}
}
return list;
}
/**//// <summary>
/// 获取原始数据信息.
/// </summary>
/// <param name="fileName">远程文件名</param>
/// <returns>返回原始数据信息.</returns>
public string GetFileDateRaw(string fileName)
{
Connect();
SendCommand("MDTM " + fileName);
ReadResponse();
if (response != 213)
{
errormessage += responseStr;
return "";
}
return (this.responseStr.Substring(4));
}
/**//// <summary>
/// 得到文件日期.
/// </summary>
/// <param name="fileName">远程文件名</param>
/// <returns>返回远程文件日期</returns>
public DateTime GetFileDate(string fileName)
{
return ConvertFTPDateToDateTime(GetFileDateRaw(fileName));
}
private DateTime ConvertFTPDateToDateTime(string input)
{
if (input.Length < 14)
throw new ArgumentException("Input Value for ConvertFTPDateToDateTime method was too short.");
//YYYYMMDDhhmmss":
int year = Convert.ToInt16(input.Substring(0, 4));
int month = Convert.ToInt16(input.Substring(4, 2));
int day = Convert.ToInt16(input.Substring(6, 2));
int hour = Convert.ToInt16(input.Substring(8, 2));
int min = Convert.ToInt16(input.Substring(10, 2));
int sec = Convert.ToInt16(input.Substring(12, 2));
return new DateTime(year, month, day, hour, min, sec);
}
/**//// <summary>
/// 获取FTP上的当前(工作)路径
/// </summary>
/// <returns>返回FTP上的当前(工作)路径</returns>
public string GetWorkingDirectory()
{
//PWD - 显示工作路径
Connect();
SendCommand("PWD");
ReadResponse();
if (response != 257)
{
errormessage += responseStr;
}
string pwd;
try
{
pwd = responseStr.Substring(responseStr.IndexOf("\"", 0) + 1);//5);
pwd = pwd.Substring(0, pwd.LastIndexOf("\""));
pwd = pwd.Replace("\"\"", "\""); // 替换带引号的路径信息符号
}
catch (Exception ex)
{
errormessage += ex.Message;
return null;
}
return pwd;
}
/**//// <summary>
/// 跳转服务器上的当前(工作)路径
/// </summary>
/// <param name="path">要跳转的路径</param>
public bool ChangeDir(string path)
{
Connect();
SendCommand("CWD " + path);
ReadResponse();
if (response != 250)
{
errormessage += responseStr;
return false;
}
return true;
}
/**//// <summary>
/// 创建指定的目录
/// </summary>
/// <param name="dir">要创建的目录</param>
public void MakeDir(string dir)
{
Connect();
SendCommand("MKD " + dir);
ReadResponse();
switch (response)
{
case 257:
case 250:
break;
default:
{
errormessage += responseStr;
break;
}
}
}
/**//// <summary>
/// 移除FTP上的指定目录
/// </summary>
/// <param name="dir">要移除的目录</param>
public void RemoveDir(string dir)
{
Connect();
SendCommand("RMD " + dir);
ReadResponse();
if (response != 250)
{
errormessage += responseStr;
return; ;
}
}
/**//// <summary>
/// 移除FTP上的指定文件
/// </summary>
/// <param name="filename">要移除的文件名称</param>
public void RemoveFile(string filename)
{
Connect();
SendCommand("DELE " + filename);
ReadResponse();
if (response != 250)
{
errormessage += responseStr;
}
}
/**//// <summary>
/// 重命名FTP上的文件
/// </summary>
/// <param name="oldfilename">原文件名</param>
/// <param name="newfilename">新文件名</param>
public void RenameFile(string oldfilename, string newfilename)
{
Connect();
SendCommand("RNFR " + oldfilename);
ReadResponse();
if (response != 350)
{
errormessage += responseStr;
}
else
{
SendCommand("RNTO " + newfilename);
ReadResponse();
if (response != 250)
{
errormessage += responseStr;
}
}
}
/**//// <summary>
/// 获得指定文件的大小(如果FTP支持)
/// </summary>
/// <param name="filename">指定的文件</param>
/// <returns>返回指定文件的大小</returns>
public long GetFileSize(string filename)
{
Connect();
SendCommand("SIZE " + filename);
ReadResponse();
if (response != 213)
{
errormessage += responseStr;
}
return Int64.Parse(responseStr.Substring(4));
}
/**//// <summary>
/// 上传指定的文件
/// </summary>
/// <param name="filename">要上传的文件</param>
public bool OpenUpload(string filename)
{
return OpenUpload(filename, filename, false);
}
/**//// <summary>
/// 上传指定的文件
/// </summary>
/// <param name="filename">本地文件名</param>
/// <param name="remotefilename">远程要覆盖的文件名</param>
public bool OpenUpload(string filename, string remotefilename)
{
return OpenUpload(filename, remotefilename, false);
}
/**//// <summary>
/// 上传指定的文件
/// </summary>
/// <param name="filename">本地文件名</param>
/// <param name="resume">如果存在,则尝试恢复</param>
public bool OpenUpload(string filename, bool resume)
{
return OpenUpload(filename, filename, resume);
}
/**//// <summary>
/// 上传指定的文件
/// </summary>
/// <param name="filename">本地文件名</param>
/// <param name="remote_filename">远程要覆盖的文件名</param>
/// <param name="resume">如果存在,则尝试恢复</param>
public bool OpenUpload(string filename, string remote_filename, bool resume)
{
Connect();
SetBinaryMode(true);
OpenDataSocket();
bytes_total = 0;
try
{
file = new FileStream(filename, FileMode.Open);
}
catch (Exception ex)
{
file = null;
errormessage += ex.Message;
return false;
}
file_size = file.Length;
if (resume)
{
long size = GetFileSize(remote_filename);
SendCommand("REST " + size);
ReadResponse();
if (response == 350)
file.Seek(size, SeekOrigin.Begin);
}
SendCommand("STOR " + remote_filename);
ReadResponse();
switch (response)
{
case 125:
case 150:
break;
default:
file.Close();
file = null;
errormessage += responseStr;
return false;
}
ConnectDataSocket();
return true;
}
/**//// <summary>
/// 下载指定文件
/// </summary>
/// <param name="filename">远程文件名称</param>
public void OpenDownload(string filename)
{
OpenDownload(filename, filename, false);
}
/**//// <summary>
/// 下载并恢复指定文件
/// </summary>
/// <param name="filename">远程文件名称</param>
/// <param name="resume">如文件存在,则尝试恢复</param>
public void OpenDownload(string filename, bool resume)
{
OpenDownload(filename, filename, resume);
}
/**//// <summary>
/// 下载指定文件
/// </summary>
/// <param name="filename">远程文件名称</param>
/// <param name="localfilename">本地文件名</param>
public void OpenDownload(string remote_filename, string localfilename)
{
OpenDownload(remote_filename, localfilename, false);
}
/**//// <summary>
/// 打开并下载文件
/// </summary>
/// <param name="remote_filename">远程文件名称</param>
/// <param name="local_filename">本地文件名</param>
/// <param name="resume">如果文件存在则恢复</param>
public void OpenDownload(string remote_filename, string local_filename, bool resume)
{
Connect();
SetBinaryMode(true);
bytes_total = 0;
try
{
file_size = GetFileSize(remote_filename);
}
catch
{
file_size = 0;
}
if (resume && File.Exists(local_filename))
{
try
{
file = new FileStream(local_filename, FileMode.Open);
}
catch (Exception ex)
{
file = null;
throw new Exception(ex.Message);
}
SendCommand("REST " + file.Length);
ReadResponse();
if (response != 350)
throw new Exception(responseStr);
file.Seek(file.Length, SeekOrigin.Begin);
bytes_total = file.Length;
}
else
{
try
{
file = new FileStream(local_filename, FileMode.Create);
}
catch (Exception ex)
{
file = null;
throw new Exception(ex.Message);
}
}
OpenDataSocket();
SendCommand("RETR " + remote_filename);
ReadResponse();
switch (response)
{
case 125:
case 150:
break;
default:
file.Close();
file = null;
errormessage += responseStr;
return;
}
ConnectDataSocket();
return;
}
/**//// <summary>
/// 上传文件(循环调用直到上传完毕)
/// </summary>
/// <returns>发送的字节数</returns>
public long DoUpload()
{
Byte[] bytes = new Byte[512];
long bytes_got;
try
{
bytes_got = file.Read(bytes, 0, bytes.Length);
bytes_total += bytes_got;
data_sock.Send(bytes, (int)bytes_got, 0);
if (bytes_got <= 0)
{
//上传完毕或有错误发生
file.Close();
file = null;
CloseDataSocket();
ReadResponse();
switch (response)
{
case 226:
case 250:
break;
default: //当上传中断时
{
errormessage += responseStr;
return -1;
}
}
SetBinaryMode(false);
}
}
catch (Exception ex)
{
file.Close();
file = null;
CloseDataSocket();
ReadResponse();
SetBinaryMode(false);
//throw ex;
//当上传中断时
errormessage += ex.Message;
return -1;
}
return bytes_got;
}
/**//// <summary>
/// 下载文件(循环调用直到下载完毕)
/// </summary>
/// <returns>接收到的字节点</returns>
public long DoDownload()
{
Byte[] bytes = new Byte[512];
long bytes_got;
try
{
bytes_got = data_sock.Receive(bytes, bytes.Length, 0);
if (bytes_got <= 0)
{
//下载完毕或有错误发生
CloseDataSocket();
file.Close();
file = null;
ReadResponse();
switch (response)
{
case 226:
case 250:
break;
default:
{
errormessage += responseStr;
return -1;
}
}
SetBinaryMode(false);
return bytes_got;
}
file.Write(bytes, 0, (int)bytes_got);
bytes_total += bytes_got;
}
catch (Exception ex)
{
CloseDataSocket();
file.Close();
file = null;
ReadResponse();
SetBinaryMode(false);
//throw ex;
//当下载中断时
errormessage += ex.Message;
return -1;
}
return bytes_got;
}
#endregion
}
有了核心代码,下面就是相关的FTP信息(如服务器站点,端口号,密码等)是如何在我们产品代
码中进行设置并保存呢?
请用Reflectort工具反射文件:discuz.config.dll,其配置类如下(其也采用序列化方式进行保
存):
FTPConfigInfo [FTP配置信息类]
FTPConfigInfoCollection [FTP配置信息类集合]
其中的FTPConfigInfo类的代码如下(我已将注释补充):
/// <summary>
/// FTP配置信息类
/// </summary>
[Serializable]
public class FTPConfigInfo : IConfigInfo
{
#region FTP私有字段
private string m_name; //名称,如forumattach:论坛附件,spaceattach:空间附件,album:相册等
private string m_serveraddress; //服务器地址
private int m_serverport = 25; //服务器端口号
private string m_username; //登陆帐号
private string m_password; //登陆密码
private int m_mode = 1; //链接模式 1:被动 2:主动
private int m_allowupload; //允许FTP上传附件 0:不允许 1:允许
private string m_uploadpath; //上传路径
private int m_timeout; //无响应时间(FTP在指定时间内无响应),单位:秒
private string m_remoteurl; //远程访问 URL
private int m_reservelocalattach = 0; //是否保留本地附件. 0:不保留 1:保留
#endregion
public FTPConfigInfo()
{
}
#region 属性
/// <summary>
/// 名称
/// </summary>
public string Name
{
get { return m_name; }
set { m_name = value; }
}
/// <summary>
/// FTP服务器名称
/// </summary>
public string Serveraddress
{
get { return m_serveraddress; }
set { m_serveraddress = value; }
}
/// <summary>
/// FTP端口号
/// </summary>
public int Serverport
{
get { return m_serverport; }
set { m_serverport = value; }
}
/// <summary>
/// 登陆帐号
/// </summary>
public string Username
{
get { return m_username; }
set { m_username = value; }
}
/// <summary>
/// 登陆密码
/// </summary>
public string Password
{
get { return m_password; }
set { m_password = value; }
}
/// <summary>
/// 链接模式 1:被动 2:主动
/// </summary>
public int Mode
{
get { return m_mode <= 0 ? 1 : m_mode; }
set { m_mode = value <= 0 ? 1 : m_mode; }
}
/// <summary>
/// 允许FTP上传附件
/// </summary>
public int Allowupload
{
get { return m_allowupload; }
set { m_allowupload = value; }
}
/// <summary>
/// 上传路径
/// </summary>
public string Uploadpath
{
get { return m_uploadpath; }
set { m_uploadpath = value; }
}
/// <summary>
/// 无响应时间(FTP在指定时间内无响应),单位:秒
/// </summary>
public int Timeout
{
get { return m_timeout <= 0 ? 10 : m_timeout; }
set { m_timeout = value <= 0 ? 10 : m_timeout; }
}
/// <summary>
/// 远程访问 URL
/// </summary>
public string Remoteurl
{
get { return m_remoteurl; }
set { m_remoteurl = value; }
}
/// <summary>
/// 保留本地附件:0为不保留 1为保留
/// </summary>
public int Reservelocalattach
{
get { return m_reservelocalattach; }
set { m_reservelocalattach = value; }
}
#endregion
}
而上面的FTPConfigInfoCollection类是一个可序列化的集合类(为FTPConfigInfo类实例集合)。
这样做是因为如果当论坛,空间,相册等功能需要远程附件支持时,都需要各自的配置信息,而通过序列
化FTPConfigInfoCollection 便可获取或保存各个功能的相应配置信息(也便于日后扩展),其生成的序
列化信息格式如下(相应的节点信息对应上面的FTPConfigInfo类的属性字段):
<?xml version="1.0"?>
<ArrayOfFTPConfigInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FTPConfigInfo>
<Name>ForumAttach</Name>
<Serveraddress>ftpserver</Serveraddress>
<Serverport>21</Serverport>
<Username>username</Username>
<Password>password</Password>
<Mode>1</Mode>
<Allowupload>0</Allowupload>
<Uploadpath>test</Uploadpath>
<Timeout>10</Timeout>
<Remoteurl>http://localhost/forumattach</Remoteurl>
<Reservelocalattach>0</Reservelocalattach>
</FTPConfigInfo>
<FTPConfigInfo>
<Name>SpaceAttach</Name>
<Serveraddress>ftpserver</Serveraddress>
<Serverport>21</Serverport>
<Username>username</Username>
<Password>password</Password>
<Mode>1</Mode>
<Allowupload>0</Allowupload>
<Uploadpath>test</Uploadpath>
<Timeout>10</Timeout>
<Remoteurl>http://localhost/spaceattach</Remoteurl>
<Reservelocalattach>0</Reservelocalattach>
</FTPConfigInfo>
<FTPConfigInfo>
<Name>AlbumAttach</Name>
<Serveraddress>ftpserver</Serveraddress>
<Serverport>21</Serverport>
<Username>username</Username>
<Password>password</Password>
<Mode>1</Mode>
<Allowupload>0</Allowupload>
<Uploadpath>test</Uploadpath>
<Timeout>10</Timeout>
<Remoteurl>http://localhost/albumattach</Remoteurl>
<Reservelocalattach>0</Reservelocalattach>
</FTPConfigInfo>
</ArrayOfFTPConfigInfo>
而该序列化配置文件位于discuz.web项目的config\ftp.config下。
当然上面所说的只是相应的配置类,而为了便于前台开发相应功能,我对上面所说的FTP和配置类又进行了一次
类封装,并将类命名为FTPs,放在了discuz.forum下,大家可以用Reflector获得其代码,我在这里将注释补充如下:
/// <summary>
/// FTP操作类
/// </summary>
public class FTPs
{
#region 声明上传信息(静态)对象
private static FTPConfigInfo m_forumattach;
private static FTPConfigInfo m_spaceattach;
private static FTPConfigInfo m_albumattach;
private static FTPConfigInfo m_mallattach;
#endregion
private static string m_configfilepath = Utils.GetMapPath(BaseConfigs.GetForumPath + "config/ftp.config");
/// <summary>
/// 程序刚加载时ftp.config文件修改时间
/// </summary>
private static DateTime m_fileoldchange;
/// <summary>
/// 最近ftp.config文件修改时间
/// </summary>
private static DateTime m_filenewchange;
private static object lockhelper = new object();
/// <summary>
/// FTP信息枚举类型
/// </summary>
public enum FTPUploadEnum
{
ForumAttach = 1, //论坛附件
SpaceAttach = 2, //空间附件
AlbumAttach = 3, //相册附件
MallAttach = 4 //商场附件
}
/// <summary>
/// 静态构造函数(用于初始化对象和变量)
/// </summary>
static FTPs()
{
SetFtpConfigInfo();
m_fileoldchange = System.IO.File.GetLastWriteTime(m_configfilepath);
}
/// <summary>
/// FTP配置文件监视方法
/// </summary>
private static void FtpFileMonitor()
{
//获取文件最近修改时间
m_filenewchange = System.IO.File.GetLastWriteTime(m_configfilepath);
//当ftp.config修改时间发生变化时
if (m_fileoldchange != m_filenewchange)
{
lock (lockhelper)
{
if (m_fileoldchange != m_filenewchange)
{
//当文件发生修改(时间变化)则重新设置相关FTP信息对象
SetFtpConfigInfo();
m_fileoldchange = m_filenewchange;
}
}
}
}
/// <summary>
/// 设置FTP对象信息
/// </summary>
private static void SetFtpConfigInfo()
{
FTPConfigInfoCollection ftpconfiginfocollection =
(FTPConfigInfoCollection)SerializationHelper.Load(typeof(FTPConfigInfoCollection), m_configfilepath);
FTPConfigInfoCollection.FTPConfigInfoCollectionEnumerator fcice = ftpconfiginfocollection.GetEnumerator();
//遍历集合并设置相应的FTP信息(静态)对象
while (fcice.MoveNext())
{
if (fcice.Current.Name == "ForumAttach")
{
m_forumattach = fcice.Current;
continue;
}
if (fcice.Current.Name == "SpaceAttach")
{
m_spaceattach = fcice.Current;
continue;
}
if (fcice.Current.Name == "AlbumAttach")
{
m_albumattach = fcice.Current;
continue;
}
if (fcice.Current.Name == "MallAttach")
{
m_mallattach = fcice.Current;
continue;
}
}
}
/// <summary>
/// 论坛附件FTP信息
/// </summary>
public static FTPConfigInfo GetForumAttachInfo
{
get
{
FtpFileMonitor();
return m_forumattach;
}
}
/// <summary>
/// 空间附件FTP信息
/// </summary>
public static FTPConfigInfo GetSpaceAttachInfo
{
get
{
FtpFileMonitor();
return m_spaceattach;
}
}
/// <summary>
/// 相册附件FTP信息
/// </summary>
public static FTPConfigInfo GetAlbumAttachInfo
{
get
{
FtpFileMonitor();
return m_albumattach;
}
}
/// <summary>
/// 相册附件FTP信息
/// </summary>
public static FTPConfigInfo GetMallAttachInfo
{
get
{
FtpFileMonitor();
return m_mallattach;
}
}
#region 异步FTP上传文件
private delegate bool delegateUpLoadFile(string path, string file, FTPUploadEnum ftpuploadname);
//异步FTP上传文件代理
private delegateUpLoadFile upload_aysncallback;
public void AsyncUpLoadFile(string path, string file, FTPUploadEnum ftpuploadname)
{
upload_aysncallback = new delegateUpLoadFile(UpLoadFile);
upload_aysncallback.BeginInvoke(path, file, ftpuploadname, null, null);
}
#endregion
/// <summary>
/// 普通FTP上传文件
/// </summary>
/// <param name="file">要FTP上传的文件</param>
/// <returns>上传是否成功</returns>
public bool UpLoadFile(string path, string file, FTPUploadEnum ftpuploadname)
{
FTP ftpupload = new FTP();
//转换路径分割符为"/"
path = path.Replace("""", "/");
path = path.StartsWith("/") ? path : "/" +path ;
//删除file参数文件
bool delfile = true;
//根据上传名称确定上传的FTP服务器
switch (ftpuploadname)
{
//论坛附件
case FTPUploadEnum.ForumAttach:
{
ftpupload = new FTP(m_forumattach.Serveraddress, m_forumattach.Serverport,
m_forumattach.Username, m_forumattach.Password, m_forumattach.Timeout);
path = m_forumattach.Uploadpath + path;
delfile = (m_forumattach.Reservelocalattach == 1) ? false : true;
break;
}
//空间附件
case FTPUploadEnum.SpaceAttach:
{
ftpupload = new FTP(m_spaceattach.Serveraddress, m_spaceattach.Serverport,
m_spaceattach.Username, m_spaceattach.Password, m_spaceattach.Timeout);
path = m_spaceattach.Uploadpath + path;
delfile = (m_spaceattach.Reservelocalattach == 1) ? false : true;
break;
}
//相册附件
case FTPUploadEnum.AlbumAttach:
{
ftpupload = new FTP(m_albumattach.Serveraddress, m_albumattach.Serverport,
m_albumattach.Username, m_albumattach.Password, m_albumattach.Timeout);
path = m_albumattach.Uploadpath + path;
delfile = (m_albumattach.Reservelocalattach == 1) ? false : true;
break;
}
//商城附件
case FTPUploadEnum.MallAttach:
{
ftpupload = new FTP(m_mallattach.Serveraddress, m_mallattach.Serverport,
m_mallattach.Username, m_mallattach.Password, m_mallattach.Timeout);
path = m_mallattach.Uploadpath + path;
delfile = (m_mallattach.Reservelocalattach == 1) ? false : true;
break;
}
}
//切换到指定路径下,如果目录不存在,将创建
if (!ftpupload.ChangeDir(path))
{
//ftpupload.MakeDir(path);
foreach (string pathstr in path.Split('/'))
{
if (pathstr.Trim() != "")
{
ftpupload.MakeDir(pathstr);
ftpupload.ChangeDir(pathstr);
}
}
}
ftpupload.Connect();
if (!ftpupload.IsConnected)
{
return false;
}
int perc = 0;
//绑定要上传的文件
if (!ftpupload.OpenUpload(file, System.IO.Path.GetFileName(file)))
{
ftpupload.Disconnect();
return false;
}
//开始进行上传
while (ftpupload.DoUpload() > 0)
{
perc = (int)(((ftpupload.BytesTotal) * 100) / ftpupload.FileSize);
}
ftpupload.Disconnect();
//(如存在)删除指定目录下的文件
if (delfile && Utils.FileExists(file))
{
System.IO.File.Delete(file);
}
if (perc >= 100)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// FTP连接测试
/// </summary>
/// <param name="Serveraddress">FTP服务器地址</param>
/// <param name="Serverport">FTP端口</param>
/// <param name="Username">用户名</param>
/// <param name="Password">密码</param>
/// <param name="Timeout">超时时间(秒)</param>
/// <param name="uploadpath">附件保存路径</param>
/// <param name="message">返回信息</param>
/// <returns>是否可用</returns>
public bool TestConnect(string Serveraddress, int Serverport, string Username, string Password,
int Timeout, string uploadpath, ref string message)
{
FTP ftpupload = new FTP(Serveraddress, Serverport, Username, Password, Timeout);
bool isvalid = ftpupload.Connect();
if (!isvalid)
{
message = ftpupload.errormessage;
return isvalid;
}
//切换到指定路径下,如果目录不存在,将创建
if (!ftpupload.ChangeDir(uploadpath))
{
ftpupload.MakeDir(uploadpath);
if (!ftpupload.ChangeDir(uploadpath))
{
message += ftpupload.errormessage;
isvalid = false;
}
}
return isvalid;
}
}
现在万事俱备,我们要将新增的的远程附件功能的调用代码放到原有的附件上传代码中了,这里以"ForumUtils.cs"
文件(位于discuz.forum)为例, 通过Reflector, 查看该类下的SaveRequestFiles方法中的如下代码段(已添加注释):
public static AttachmentInfo[] SaveRequestFiles(int forumid, int MaxAllowFileCount, int MaxSizePerDay,
int MaxFileSize, int TodayUploadedSize, string AllowFileType, int watermarkstatus,
GeneralConfigInfo config, string filekey)
{
//当支持FTP上传附件时,使用FTP上传远程附件
if (FTPs.GetForumAttachInfo.Allowupload == 1)
{
FTPs ftps = new FTPs();
//当不保留本地附件模式时,在上传完成之后删除本地tempfilename文件
if (FTPs.GetForumAttachInfo.Reservelocalattach == 0)
{
ftps.UpLoadFile(newfilename.Substring(0, newfilename.LastIndexOf("""")),
UploadDir + tempfilename, FTPs.FTPUploadEnum.ForumAttach);
}
else
{
ftps.UpLoadFile(newfilename.Substring(0, newfilename.LastIndexOf("""")),
UploadDir + newfilename, FTPs.FTPUploadEnum.ForumAttach);
}
}
}
当然这里只是列举了一处改动,其实还有几处不小的变化也是相似的代码实现,比如空间或相册中进行图片等
附件上传时。
而这些功能的设置可以到"后台管理"中找到,如下图所示:
好了,今天的内容就先到这里了,感兴趣的朋友可以在回复中或用EMAIL的方法与我联系。
老外的FTP类源码下载链接,请点击这里:)
作者:代震军 (daizhj)
博客:http://daizhj.cnblogs.com