[转]利用FtpClient类实现文件的上传下载功能

本文详细介绍了 FtpClient 类的功能及使用方法,包括类的构造、属性、接口、私有及公共方法等。同时提供了异常处理类 FtpClientException 和回复类 FtpReply 的定义,以及枚举类型 FtpTransferType 的解释。

该代码源自互联网,并经过修改:

解决了中文文件名会出现乱码的情况;

改善了上传不稳定的问题(但没有从根本上解决,目前只知道在接收Socket时有时会收到一半就退出,结果造成接收数据不完整,经常出现在Dir时。尚未仔细研究。)

FtpClient.cs

    /**//// <summary>
    
/// FtpClient 的摘要说明。
    
/// </summary>

    public class FtpClient : IDisposable
    
...{
        
私有变量#region 私有变量

        
private string _serverAddress;
        
private int _serverPort;
        
private string _remotePath;
        
private string _loginUser;
        
private string _loginPassword;
        
private bool _connected;
        
private FtpTransferType _transferType;
        
private int _blockSize = 512;

        
/**//// <summary>
        
/// 服务器返回的应答信息。
        
/// </summary>

        private FtpReply _reply;

        
/**//// <summary>
        
/// 进行控制连接的socket
        
/// </summary>

        private Socket _socketControl;

        
#endregion


        
// 构造方法
        构造方法#region 构造方法

        
/**//// <summary>
        
/// 构造方法。
        
/// </summary>

        public FtpClient() : this("localhost""anonymous""anonymous@anonymous.net"21)
        
...{
        }


        
/**//// <summary>
        
/// 构造方法。
        
/// </summary>
        
/// <param name="serverAddress"></param>
        
/// <param name="loginUser"></param>
        
/// <param name="loginPassword"></param>
        
/// <param name="serverPort"></param>

        public FtpClient(string serverAddress, string loginUser, string loginPassword, int serverPort)
        
...{
            _serverAddress 
= serverAddress;
            _loginUser 
= loginUser;
            _loginPassword 
= loginPassword;
            _serverPort 
= serverPort;

            
this._remotePath = string.Empty;
        }


        
#endregion


        
// 属性
        BlockSize 接收和发送数据的缓冲区大小#region BlockSize 接收和发送数据的缓冲区大小

        
/**//// <summary>
        
/// 获取或设置接收和发送数据的缓冲区大小,默认为 1024 。
        
/// </summary>

        public int BlockSize
        
...{
            
get
            
...{
                
return this._blockSize;
            }

            
set
            
...{
                
this._blockSize = value;
            }

        }


        
#endregion


        
ServerAddress Ftp 服务器地址#region ServerAddress Ftp 服务器地址

        
/**//// <summary>
        
/// Ftp 服务器地址。默认为 localhost 。
        
/// </summary>

        public string ServerAddress
        
...{
            
get
            
...{
                
return _serverAddress;
            }

            
set
            
...{
                _serverAddress 
= value;
            }

        }


        
#endregion


        
ServerPort Ftp 服务器端口#region ServerPort Ftp 服务器端口

        
/**//// <summary>
        
/// Ftp 服务器端口。默认为 21 。
        
/// </summary>

        public int ServerPort
        
...{
            
get
            
...{
                
return _serverPort;
            }

            
set
            
...{
                _serverPort 
= value;
            }

        }


        
#endregion


        
RemotePath 当前服务器目录#region RemotePath 当前服务器目录

        
/**//// <summary>
        
/// 当前服务器目录。
        
/// </summary>

        public string RemotePath
        
...{
            
get
            
...{
                
return _remotePath;
            }

            
set
            
...{
                _remotePath 
= value;
            }

        }


        
#endregion


        
LoginUser 登录用户账号#region LoginUser 登录用户账号

        
/**//// <summary>
        
/// 登录用户账号。默认为 anonymous 。
        
/// </summary>

        public string LoginUser
        
...{
            
set
            
...{
                _loginUser 
= value;
            }

        }


        
#endregion


        
LoginPassword 用户登录密码#region LoginPassword 用户登录密码

        
/**//// <summary>
        
/// 用户登录密码。默认为 anonymous@anonymous.net 。
        
/// </summary>

        public string LoginPassword
        
...{
            
set
            
...{
                _loginPassword 
= value;
            }

        }


        
#endregion


        
Connected 是否登录#region Connected 是否登录

        
/**//// <summary>
        
/// 是否登录
        
/// </summary>

        public bool Connected
        
...{
            
get
            
...{
                
return _connected;
            }

        }


        
#endregion


        
// 接口
        IDisposable 成员#region IDisposable 成员

        
private bool disposed = false;

        
/**//// <summary>
        
/// <see cref="IDisposable"/> 接口的实现方法。
        
/// </summary>

        void IDisposable.Dispose()
        
...{
            
this.Dispose(true);
            GC.SuppressFinalize(
this);
        }


        
/**//// <summary>
        
/// 释放由 <see cref="FtpClient"/> 使用的非托管资源,并可根据需要处置托管资源。
        
/// </summary>
        
/// <param name="isDisposing">指示当前是否是被 Dispose() 方法调用。</param>

        protected virtual void Dispose(bool isDisposing)
        
...{
            
if(!this.disposed)
            
...{
                
if(isDisposing)
                
...{
                    
// 释放托管的资源
                    if(this._socketControl!= null)
                    
...{
                        
this.Close();
                    }

                }


                
// 释放非托管的资源

                
// 标记资源已经释放
                this.disposed = true;
            }

        }


        
#endregion


        
// 私有方法
        ReadReply#region ReadReply

        
/**//// <summary>
        
/// 将一行应答字符串记录在this._reply.Message和this._strMsg
        
/// 应答码记录在this._reply.Code
        
/// </summary>

        private FtpReply ReadReply()
        
...{
            
this._reply = null;
            
string message = ReadLine();
            
this._reply = message.Length < 3 ? null : new FtpReply(Int32.Parse(message.Substring(03)), message);
            
return this._reply;
        }


        
#endregion


        
CreateDataSocket 建立进行数据连接的socket#region CreateDataSocket 建立进行数据连接的socket

        
/**//// <summary>
        
/// 建立进行数据连接的socket
        
/// </summary>
        
/// <returns>数据连接socket</returns>

        private Socket CreateDataSocket()
        
...{
            SendCommand(
"PASV");

            
if(this._reply.Code != 227)
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }


            
int index1 = this._reply.Message.IndexOf('(');
            
int index2 = this._reply.Message.IndexOf(')');
            
string ipData = this._reply.Message.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 FtpClientException("Malformed PASV result: " + this._reply.Message);
                }

                
if(ch == ',' || i + 1 == len)
                
...{
                    
try
                    
...{
                        parts[partCount
++= Int32.Parse(buf);
                        buf 
= "";
                    }

                    
catch (Exception)
                    
...{
                        
throw new FtpClientException("Malformed PASV result (not supported?): " + this._reply.Message);
                    }

                }

            }


            
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);
            
try
            
...{
                s.Connect(ep);
            }

            
catch(Exception ex)
            
...{
                
throw new FtpClientException(string.Format("Can't connect to remote server {0} .", ipAddress), ex);
            }

            
return s;
        }


        
#endregion


        
CloseSocketConnect 关闭socket连接(用于登录以前)#region CloseSocketConnect 关闭socket连接(用于登录以前)

        
/**//// <summary>
        
/// 关闭socket连接(用于登录以前)
        
/// </summary>

        private void CloseSocketConnect()
        
...{
            
if(this._socketControl!= null)
            
...{
                
this._socketControl.Close();
                
this._socketControl = null;
            }

            _connected 
= false;
        }


        
#endregion


        
ReadLine 读取Socket返回的所有字符串#region ReadLine 读取Socket返回的所有字符串

        
/**//// <summary>
        
/// 读取Socket返回的所有字符串
        
/// </summary>
        
/// <returns>包含应答码的字符串行</returns>

        private string ReadLine()
        
...{
            
string message = string.Empty;

            
byte[] buffer = new byte[this._blockSize];
            
while(true)
            
...{
                Thread.Sleep(
10);

                
int iBytes = this._socketControl.Receive(buffer, buffer.Length, 0);
                message 
+= Encoding.Default.GetString(buffer, 0, iBytes);
                
if(iBytes < buffer.Length)
                
...{
                    
break;
                }

            }


            
string[] mess = StringHelper.Split(StringHelper.TrimEnd(message, true" "), true" ");
            
if(mess.Length > 2)
            
...{
                message 
= mess[mess.Length-2];
                
//seperator[0]是10,换行符是由13和0组成的,分隔后10后面虽没有字符串,
                
//但也会分配为空字符串给后面(也是最后一个)字符串数组,
                
//所以最后一个mess是没用的空字符串
                
//但为什么不直接取mess[0],因为只有最后一行字符串应答码与信息之间有空格
            }

            
else
            
...{
                message 
= mess[0];
            }

            
if(message.Length > 3 && message.Substring(31!= " ")//返回字符串正确的是以应答码(如220开头,后面接一空格,再接问候字符串)
            ...{
                
return ReadLine();
            }


            
return message;
        }


        
#endregion


        
SendCommand 发送命令并获取应答码和最后一行应答字符串#region SendCommand 发送命令并获取应答码和最后一行应答字符串

        
/**//// <summary>
        
/// 发送命令并获取应答码和最后一行应答字符串。
        
/// </summary>
        
/// <param name="command">命令</param>

        public FtpReply SendCommand(string command)
        
...{
            
char[] charList = (command + " ").ToCharArray();
            Byte[] cmdBytes 
= Encoding.Default.GetBytes(charList);
            
this._socketControl.Send(cmdBytes, cmdBytes.Length, 0);
            
return this.ReadReply();
        }


        
#endregion


        
// 公共方法
        Connect 建立连接#region Connect 建立连接

        
/**//// <summary>
        
/// 建立连接
        
/// </summary>

        public void Connect()
        
...{
            IPAddress address 
= null;
            
if(RegexUtility.IsIP(this._serverAddress))
            
...{
                address 
= IPAddress.Parse(this._serverAddress);
            }

            
else
            
...{
                
try
                
...{
                    IPHostEntry host 
= Dns.GetHostByName(this._serverAddress);
                    
if(host.AddressList.Length != 0)
                    
...{
                        address 
= host.AddressList[0];
                    }

                }

                
catch(Exception ex)
                
...{
                    
throw new FtpClientException(string.Format("获取 Ftp 服务器主机地址出错:{0}", ex.Message), ex);
                }

            }


            
if(address == null)
            
...{
                
throw new FtpClientException(string.Format("无法获取远程主机 {0} 的 IP 地址。"this._serverAddress));
            }


            
this._socketControl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ep 
= new IPEndPoint(address, _serverPort);

            
// 链接
            try
            
...{
                
this._socketControl.Connect(ep);
            }

            
catch(Exception ex)
            
...{
                
throw new FtpClientException(string.Format("无法连接到远程服务器 {0} 。", address.ToString()), ex);
            }


            
// 获取应答码
            ReadReply();
            
if(this._reply.Code != 220)
            
...{
                Close();
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }


            
// 登陆
            SendCommand("USER " + _loginUser);
            
if(!(this._reply.Code == 331 || this._reply.Code == 230))
            
...{
                
//关闭连接
                CloseSocketConnect();
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }

            
if(this._reply.Code != 230)
            
...{
                SendCommand(
"PASS " + _loginPassword);
                
if(!(this._reply.Code == 230 || this._reply.Code == 202))
                
...{
                    
//关闭连接
                    CloseSocketConnect();
                    
throw new FtpClientException(this._reply.Message.Substring(4));
                }

            }


            _connected 
= true;

            
// 切换到目录
            ChangeDirectory(_remotePath);
        }


        
#endregion


        
EnsureConnected 确保已经连接到服务器#region EnsureConnected 确保已经连接到服务器

        
/**//// <summary>
        
/// 确保已经连接到服务器。
        
/// </summary>

        protected void EnsureConnected()
        
...{
            
if(!_connected)
            
...{
                Connect();
            }

        }


        
#endregion


        
Close 关闭连接#region Close 关闭连接

        
/**//// <summary>
        
/// 关闭连接
        
/// </summary>

        public void Close()
        
...{
            
if(this._socketControl != null)
            
...{
                SendCommand(
"QUIT");
            }


            CloseSocketConnect();
        }


        
#endregion


        
TransferType 获取或设置传输模式#region TransferType 获取或设置传输模式

        
/**//// <summary>
        
/// 获取或设置传输模式
        
/// </summary>

        public FtpTransferType TransferType
        
...{
            
get
            
...{
                
return this._transferType;
            }

            
set
            
...{
                
if(value != this._transferType)
                
...{
                    
if(value == FtpTransferType.Binary)
                    
...{
                        SendCommand(
"TYPE I");
                    }

                    
else
                    
...{
                        SendCommand(
"TYPE A");
                    }


                    
if(this._reply.Code != 200)
                    
...{
                        
throw new FtpClientException(this._reply.Message.Substring(4));
                    }

                    
else
                    
...{
                        
this._transferType = value;
                    }

                }

            }

        }

    
        
#endregion


        
Dir 获得文件列表#region Dir 获得文件列表

        
/**//// <summary>
        
/// 获得文件列表。
        
/// </summary>
        
/// <returns></returns>

        public string[] Dir()
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            
//建立进行数据连接的socket
            Socket socketData = CreateDataSocket();
   
            
//传送命令
            SendCommand("NLST");

            
//分析应答代码
            if(this._reply.Code == 550)
            
...{
                
return new string[0];
            }


            
if(!(this._reply.Code == 150 || this._reply.Code == 125 || this._reply.Code == 226))
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }


            
//获得结果
            string message = string.Empty;
            
byte[] buffer = new byte[this._blockSize];
            
while(true)
            
...{
                
int iBytes = socketData.Receive(buffer, buffer.Length, 0);
                message 
+= Encoding.Default.GetString(buffer, 0, iBytes);
                
if(iBytes < buffer.Length)
                
...{
                    
break;
                }

            }


            
string[] strsFileList = StringHelper.Split(StringHelper.TrimEnd(message, false" "), false" ");
            socketData.Close();
//数据socket关闭时也会有返回码
            if(this._reply.Code != 226)
            
...{
                ReadReply();
                
if(this._reply.Code != 226)
                
...{
                    
throw new FtpClientException(this._reply.Message.Substring(4));
                }

            }

            
return strsFileList;
        }


        
#endregion


        
GetFileSize 获取文件大小#region GetFileSize 获取文件大小

        
/**//// <summary>
        
/// 获取文件大小
        
/// </summary>
        
/// <param name="fileName">文件名</param>
        
/// <returns>文件大小</returns>

        private long GetFileSize(string fileName)
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            SendCommand(
"SIZE " + fileName);

            
long lSize = 0;
            
if(this._reply.Code == 213)
            
...{
                lSize 
= Int64.Parse(this._reply.Message.Substring(4));
            }

            
else
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }

            
return lSize;
        }


        
#endregion


        
Delete 删除#region Delete 删除

        
/**//// <summary>
        
/// 删除
        
/// </summary>
        
/// <param name="fileName">待删除文件名</param>

        public void Delete(string fileName)
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            SendCommand(
"DELE " + fileName);
            
if(this._reply.Code != 250)
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }

        }


        
#endregion


        
Rename 重命名文件(如果新文件名与已有文件重名,将覆盖已有文件)#region Rename 重命名文件(如果新文件名与已有文件重名,将覆盖已有文件)

        
/**//// <summary>
        
/// 重命名(如果新文件名与已有文件重名,将覆盖已有文件)。
        
/// </summary>
        
/// <param name="oldFileName">旧文件名</param>
        
/// <param name="newFileName">新文件名</param>

        public void Rename(string oldFileName,string newFileName)
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            SendCommand(
"RNFR " + oldFileName);
            
if(this._reply.Code != 350)
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }


            
//  如果新文件名与原有文件重名,将覆盖原有文件
            SendCommand("RNTO " + newFileName);
            
if(this._reply.Code != 250)
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }

        }

        
#endregion


        
Download 下载文件#region Download 下载文件

        
/**//// <summary>
        
/// 下载一个文件
        
/// </summary>
        
/// <param name="remoteFileName">远程服务器上的文件路径名。</param>
        
/// <param name="localFileName">将文件保存到的本地路径名。</param>

        public void DownloadFile(string remoteFileName, string localFileName)
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            
this.TransferType = FtpTransferType.Binary;

            
if(localFileName == string.Empty)
            
...{
                localFileName 
= remoteFileName;
            }

            
string path = Path.GetDirectoryName(localFileName);
            
if(!Directory.Exists(path))
            
...{
                Directory.CreateDirectory(path);
            }


            FileStream output 
= new FileStream(localFileName, FileMode.Create);
            Socket socketData 
= CreateDataSocket();
            SendCommand(
"RETR " + remoteFileName);
            
if(!(this._reply.Code == 150 || this._reply.Code == 125 || this._reply.Code == 226 || this._reply.Code == 250))
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }


            
byte[] buffer = new byte[this._blockSize];
            
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(!(this._reply.Code == 226 || this._reply.Code == 250))
            
...{
                ReadReply();
                
if(!(this._reply.Code == 226 || this._reply.Code == 250))
                
...{
                    
throw new FtpClientException(this._reply.Message.Substring(4));
                }

            }

        }


        
#endregion


        
Upload 上传文件#region Upload 上传文件

        
/**//// <summary>
        
/// 上传一批文件
        
/// </summary>
        
/// <param name="folder">本地目录(不得以结束)</param>
        
/// <param name="fileNameMask">文件名匹配字符(可以包含*和?)</param>

        public void UploadDirectory(string folder,string fileNameMask)
        
...{
            
string[] strFiles = Directory.GetFiles(folder,fileNameMask);
            
foreach(string strFile in strFiles)
            
...{
                
//strFile是完整的文件名(包含路径)
                UploadFile(strFile);
            }

        }


        
/**//// <summary>
        
/// 上传一个文件
        
/// </summary>
        
/// <param name="fileName">本地文件名</param>

        public void UploadFile(string fileName)
        
...{
            FileStream input 
= new FileStream(fileName, FileMode.Open);
            
this.UploadFile(input, Path.GetFileName(fileName));
            input.Close();
        }


        
/**//// <summary>
        
/// 上传一个文件
        
/// </summary>
        
/// <param name="input">流。</param>
        
/// <param name="fileName">要保存为的文件名。</param>

        public void UploadFile(Stream input, string fileName)
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            Socket socketData 
= CreateDataSocket();
            SendCommand(
"STOR " + fileName);
            
if(!(this._reply.Code == 125 || this._reply.Code == 150))
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }


            
int iBytes = 0;
            
byte[] buffer = new byte[this._blockSize];
            
while ((iBytes = input.Read(buffer,0,buffer.Length)) > 0)
            
...{
                socketData.Send(buffer, iBytes, 
0);
            }

            
if(socketData.Connected)
            
...{
                socketData.Close();
            }

            
if(!(this._reply.Code == 226 || this._reply.Code == 250))
            
...{
                ReadReply();
                
if(!(this._reply.Code == 226 || this._reply.Code == 250))
                
...{
                    
throw new FtpClientException(this._reply.Message.Substring(4));
                }

            }

        }


        
#endregion


        
MakeDirectory 创建目录#region MakeDirectory 创建目录

        
/**//// <summary>
        
/// 创建目录
        
/// </summary>
        
/// <param name="directory">目录名</param>

        public void MakeDirectory(string directory)
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            SendCommand(
"MKD " + directory);
            
if(this._reply.Code != 257)
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }

        }


        
#endregion


        
RenameDirectory 删除目录#region RenameDirectory 删除目录

        
/**//// <summary>
        
/// 删除目录
        
/// </summary>
        
/// <param name="directory">目录名</param>

        public void RenameDirectory(string directory)
        
...{
            
// 确保已连接上服务器
            this.EnsureConnected();

            SendCommand(
"RMD " + directory);
            
if(this._reply.Code != 250)
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }

        }


        
#endregion


        
ChangeDirectory 改变当前目录#region ChangeDirectory 改变当前目录

        
/**//// <summary>
        
/// 改变当前目录
        
/// </summary>
        
/// <param name="directory">新的工作目录名</param>

        public void ChangeDirectory(string directory)
        
...{
            
if(directory == null || directory == string.Empty || directory == ".")
            
...{
                
return;
            }


            
// 确保已连接上服务器
            this.EnsureConnected();

            SendCommand(
"CWD " + directory);
            
if(this._reply.Code != 250)
            
...{
                
throw new FtpClientException(this._reply.Message.Substring(4));
            }


            
this._remotePath = directory;
        }


        
#endregion

    }

FtpClientException.cs

    /**//// <summary>
    
/// FtpClientException 的摘要说明。
    
/// </summary>

    [Serializable]
    
public class FtpClientException : Exception
    
...{
        
构造方法#region 构造方法

        
public FtpClientException() : base()
        
...{
        }


        
public FtpClientException(string message) : base(message)
        
...{
        }


        
public FtpClientException(string message, Exception innerException) : base(message, innerException)
        
...{
        }


        
protected FtpClientException(SerializationInfo info, StreamingContext context) : base(info, context)
        
...{
        }


        
public override void GetObjectData(SerializationInfo info, StreamingContext context)
        
...
            
base.GetObjectData (info, context);
        }


        
#endregion

    }

}
FtpReply.cs
    /**//// <summary>
    
/// FtpReply 的摘要说明。
    
/// </summary>

    [Serializable]
    
public class FtpReply
    
...{
        
private int _code;
        
private string _message;

        
public FtpReply() : this(0string.Empty)
        
...{
        }


        
public FtpReply(int code, string message)
        
...{
            
this._code = code;
            
this._message = message;
        }


        
public int Code
        
...{
            
get
            
...{
                
return this._code;
            }

            
set
            
...{
                
this._code = value;
            }

        }


        
public string Message
        
...{
            
get
            
...{
                
return this._message;
            }

            
set
            
...{
                
this._message = value;
            }

        }

    }

FtpTransferType.cs
    /**//// <summary>
    
/// 传输模式。
    
/// </summary>

    public enum FtpTransferType
    
...{
        
/**//// <summary>
        
/// 二进制模式。
        
/// </summary>

        Binary,
        
/**//// <summary>
        
/// ASCII 模式。
        
/// </summary>

        ASCII
    }
;

转载于:https://www.cnblogs.com/JimZhang/archive/2009/07/19/1526477.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值