using System;
using System.Net;
using System.IO;
using System.Text;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading;
namespace yang.ftp
{
/// <summary>
/// Base FTP exception class.
/// </summary>
public class FTPException : Exception
{
protected FTPException()
{
}
public FTPException(string Message)
: base(Message)
{
}
public FTPException(string Message, Exception innerException)
: base(Message, innerException)
{
}
}
public class FTPReplyParseException : FTPException
{
private string replyText;
public string ReplyText
{
get { return replyText; }
}
public FTPReplyParseException(string replyText)
: base("Invalid server reply: " + replyText)
{
this.replyText = replyText;
}
}
public class FTPProtocolException : FTPException
{
FTPReply reply;
public FTPReply Reply
{
get { return reply; }
}
public FTPProtocolException(FTPReply reply)
: base("Invalid FTP protocol reply: " + reply.ToString())
{
this.reply = reply;
}
}
/// <summary>
/// Exception indicating that a command or set of commands have been cancelled by the caller, via a callback method or event.
/// </summary>
public class FTPOperationCancelledException : FTPException
{
public FTPOperationCancelledException(string Message)
: base(Message)
{
}
}
/// <summary>
/// FTP exception generated by a command with a return code >= 400, as stated in RFC 959.
/// </summary>
public class FTPCommandException : FTPException
{
int errorCode;
public int ErrorCode
{
get { return errorCode; }
}
public FTPCommandException(string Message)
: base(Message)
{
}
public FTPCommandException(string Message, Exception innerException)
: base(Message, innerException)
{
}
public FTPCommandException(FTPReply reply)
: base(reply.Message)
{
this.errorCode = reply.Code;
}
}
/// <summary>
/// FTP exception related to the SSL/TLS support
/// </summary>
public class FTPSslException : FTPException
{
public FTPSslException(string Message)
: base(Message)
{
}
public FTPSslException(string Message, Exception innerException)
: base(Message, innerException)
{
}
}
/// <summary>
/// The SSL/TLS support requested or required for a connection.
/// </summary>
[Flags]
public enum ESSLSupportMode
{
/// <summary>
/// No SSL/TLS support. Used for standard FTP connections.
/// </summary>
ClearText = 0,
/// <summary>
/// Requests a SSL/TLS connection during authentication.
/// Authentication is performed using <see cref="ClearText"/> if SSL/TLS is not supported by the server.
/// Reverts to <see cref="ClearText"/> after authetication if the CCC command is supported by the server.
/// </summary>
CredentialsRequested = 1,
/// <summary>
/// Requires a SSL/TLS connection during authentication.
/// Reverts to <see cref="ClearText"/> after authetication if the CCC command is supported by the server.
/// </summary>
CredentialsRequired = 2 | CredentialsRequested,
/// <summary>
/// Requests a SSL/TLS connection on the control channel.
/// </summary>
/// <remarks>
/// Acts like <see cref="CredentialsRequested"/> but does not revert to <see cref="ClearText"/> after authentication.
/// </remarks>
ControlChannelRequested = 4 | CredentialsRequested,
/// <summary>
/// Requires a SSL/TLS connection on the control channel.
/// </summary>
/// <remarks>
/// Acts like <see cref="CredentialsRequired"/> but does not revert to <see cref="ClearText"/> after authentication.
/// </remarks>
ControlChannelRequired = CredentialsRequired | ControlChannelRequested,
/// <summary>
/// Requests a SSL/TLS connection on the data channel, implies <see cref="CredentialsRequested"/>.
/// Data transfers are not encrypted is not supported by the server.
/// </summary>
DataChannelRequested = 8 | CredentialsRequested,
/// <summary>
/// Requires a SSL/TLS connection on the data channel, implies <see cref="CredentialsRequired"/>.
/// </summary>
DataChannelRequired = 16 | DataChannelRequested | CredentialsRequired,
/// <summary>
/// Requests a SSL/TLS connection on both control and data channels, implies <see cref="ControlChannelRequested"/> and <see cref="DataChannelRequested"/>.
/// Control channel commands and data transfers are not encrypted is not supported by the server.
/// </summary>
ControlAndDataChannelsRequested = ControlChannelRequested | DataChannelRequested,
/// <summary>
/// Requires a SSL/TLS connection on both control and data channels, implies <see cref="ControlChannelRequired"/> and <see cref="DataChannelRequired"/>.
/// </summary>
ControlAndDataChannelsRequired = ControlChannelRequired | DataChannelRequired,
/// <summary>
/// An alias for <see cref="ControlAndDataChannelsRequired"/>
/// </summary>
All = ControlAndDataChannelsRequired,
/// <summary>
/// Implicit SSL/TLS, not supported by RFC 4217. Both control channel and data channel are always encrypted.
/// </summary>
Implicit = 32 | ControlAndDataChannelsRequired
}
/// <summary>
/// Possible actions occurring during a file transfer.
/// </summary>
public enum ETransferActions
{
LocalDirectoryCreated, RemoteDirectoryCreated,
FileUploaded, FileUploadingStatus,
FileDownloaded, FileDownloadingStatus
}
/// <summary>
/// File pattern style used in <see cref="FTPSClient.GetFiles"/> and <see cref="FTPSClient.PutFiles"/>.
/// </summary>
public enum EPatternStyle
{
/// <summary>
/// Interpret as is.
/// </summary>
Verbatim,
/// <summary>
/// Interpret as wildcard, where <c>*</c> means 0 or more chars having any value and <c>?</c> means one char having any value.
/// </summary>
Wildcard,
/// <summary>
/// Interpret as a regular expression.
/// </summary>
Regex
}
/// <summary>
/// Trasfer mode used in connections
/// </summary>
public enum EDataConnectionMode
{
Active,
Passive
}
/// <summary>
/// Encapsulates the SSL/TLS algorithms connection information.
/// </summary>
public class SslInfo
{
SslProtocols sslProtocol;
CipherAlgorithmType cipherAlgorithm;
int cipherStrength;
HashAlgorithmType hashAlgorithm;
int hashStrength;
ExchangeAlgorithmType keyExchangeAlgorithm;
int keyExchangeStrength;
public SslProtocols SslProtocol
{
get { return sslProtocol; }
set { sslProtocol = value; }
}
public CipherAlgorithmType CipherAlgorithm
{
get { return cipherAlgorithm; }
set { cipherAlgorithm = value; }
}
public int CipherStrength
{
get { return cipherStrength; }
set { cipherStrength = value; }
}
public HashAlgorithmType HashAlgorithm
{
get { return hashAlgorithm; }
set { hashAlgorithm = value; }
}
public int HashStrength
{
get { return hashStrength; }
set { hashStrength = value; }
}
public ExchangeAlgorithmType KeyExchangeAlgorithm
{
get { return keyExchangeAlgorithm; }
set { keyExchangeAlgorithm = value; }
}
public int KeyExchangeStrength
{
get { return keyExchangeStrength; }
set { keyExchangeStrength = value; }
}
public override string ToString()
{
return SslProtocol.ToString() + ", " +
CipherAlgorithm.ToString() + " (" + cipherStrength.ToString() + " bit), " +
KeyExchangeAlgorithm.ToString() + " (" + keyExchangeStrength.ToString() + " bit), " +
HashAlgorithm.ToString() + " (" + hashStrength.ToString() + " bit)";
}
}
public class LogCommandEventArgs : EventArgs
{
public LogCommandEventArgs(string commandText)
: base()
{
this.CommandText = commandText;
}
public string CommandText { get; private set; }
}
public class LogServerReplyEventArgs : EventArgs
{
public LogServerReplyEventArgs(FTPReply serverReply)
: base()
{
this.ServerReply = serverReply;
}
public FTPReply ServerReply { get; private set; }
}
public delegate void LogCommandEventHandler(object sender, LogCommandEventArgs args);
public delegate void LogServerReplyEventHandler(object sender, LogServerReplyEventArgs args);
/// <summary>
/// FTP Client
/// </summary>
public class FTPClient
{
Encoding encoding = Encoding.GetEncoding("gb2312") ;
public event System.EventHandler Datareceived;
public event System.EventHandler Filereceived;
public event System.EventHandler Filesend;
public event System.EventHandler Datasend;
private long receivedbyte;
public long Receivedbyte
{
get { return receivedbyte; }
set { receivedbyte = value; }
}
#region 构造函数
/// <summary>
/// 缺省构造函数
/// </summary>
public FTPClient()
{
strRemoteHost = "";
strRemotePath = "";
strRemoteUser = "";
strRemotePass = "";
strRemotePort = 2222;
bConnected = false;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="remoteHost"></param>
/// <param name="remotePath"></param>
/// <param name="remoteUser"></param>
/// <param name="remotePass"></param>
/// <param name="remotePort"></param>
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 登陆
/// <summary>
/// FTP服务器IP地址
/// </summary>
private string strRemoteHost;
public string RemoteHost
{
get
{
return strRemoteHost;
}
set
{
strRemoteHost = value;
}
}
/// <summary>
/// FTP服务器端口
/// </summary>
private int strRemotePort;
public int RemotePort
{
get
{
return strRemotePort;
}
set
{
strRemotePort = value;
}
}
/// <summary>
/// 当前服务器目录
/// </summary>
private string strRemotePath;
public string RemotePath
{
get
{
return strRemotePath;
}
set
{
strRemotePath = value;
}
}
/// <summary>
/// 登录用户账号
/// </summary>
private string strRemoteUser;
public string RemoteUser
{
set
{
strRemoteUser = value;
}
}
/// <summary>
/// 用户登录密码
/// </summary>
private string strRemotePass;
public string RemotePass
{
set
{
strRemotePass = value;
}
}
/// <summary>
/// 是否登录
/// </summary>
private Boolean bConnected;
public bool Connected
{
get
{
return bConnected;
}
}
#endregion
public string t()
{
return "ddss";
}
#region Private Enums
enum EProtCode { C, S, E, P }
enum EAuthMechanism { TLS }
enum ERepType { A, E, I, L }
#endregion
#region Private Fields
TcpClient ctrlClient = null;
StreamReader ctrlSr;
StreamWriter ctrlSw;
SslStream ctrlSslStream;
TcpClient dataClient = null;
SslStream dataSslStream;
EDataConnectionMode dataConnectionMode = EDataConnectionMode.Passive;
/// <summary>
/// <c>true</c> to ignore the address returned by PASV
/// </summary>
bool useCtrlEndPointAddressForData = true;
bool waitingCompletionReply = false;
//string hostname;
//const string anonUsername = "anonymous";
//const string anonPassword = "anonymous@FTPSClient.org"; // dummy password
//const string clntName = "AlexFTPS";
const ESSLSupportMode defaultSSLSupportMode = ESSLSupportMode.CredentialsRequired | ESSLSupportMode.DataChannelRequested;
ESSLSupportMode sslSupportRequestedMode;
ESSLSupportMode sslSupportCurrentMode;
X509Certificate sslServerCert;
X509Certificate sslClientCert;
SslInfo sslInfo;
/// <summary>
/// 0 means no check
/// </summary>
int sslMinKeyExchangeAlgStrength = 0;
int sslMinCipherAlgStrength = 0;
int sslMinHashAlgStrength = 0;
bool sslCheckCertRevocation = true;
RemoteCertificateValidationCallback userValidateServerCertificate;
int timeout = 120000; //ms
//IList<string> features = null;
//ETransferMode transferMode = ETransferMode.ASCII;
//ETextEncoding textEncoding = ETextEncoding.ASCII;
//string welcomeMessage = null;
//string bannerMessage = null;
//Stack<string> currDirStack = new Stack<string>();
//TcpListener activeDataConnListener;
//Thread keepAliveThread = null;
//volatile bool keepAlive = true;
//int keepAliveTimeout = 20000; // ms
#endregion
#region Public Events
public event LogCommandEventHandler LogCommand;
public event LogServerReplyEventHandler LogServerReply;
#endregion
private void SetupCtrlStreamReaderAndWriter(Stream s)
{
if (ctrlSw != null)
ctrlSw.Flush();
// SreamWriter's doc states that the default encoding is UTF8 without BOM.
// Mono 2.0 seems to have a BOM anyway. Create it explicitly as a workaround
//Encoding encoding = new UTF8Encoding(false);
// encoding = Encoding.GetEncoding("gb2312");
ctrlSr = new StreamReader(s, encoding);
ctrlSw = new StreamWriter(s, encoding);
ctrlSw.NewLine = "\r\n";
}
/// <summary>
/// Copies the protocol information form the given stream.
/// </summary>
/// <param name="sslStream"></param>
private void SetSslInfo(SslStream sslStream)
{
sslInfo = new SslInfo()
{
SslProtocol = sslStream.SslProtocol,
CipherAlgorithm = sslStream.CipherAlgorithm,
CipherStrength = sslStream.CipherStrength,
HashAlgorithm = sslStream.HashAlgorithm,
HashStrength = sslStream.HashStrength,
KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm,
KeyExchangeStrength = sslStream.KeyExchangeStrength
};
}
private SslStream CreateSSlStream(Stream s, bool leaveInnerStreamOpen)
{
SslStream sslStream = new SslStream(s, leaveInnerStreamOpen,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null //new LocalCertificateSelectionCallback(ValidateClientCertificate)
);
sslStream.ReadTimeout = timeout;
sslStream.WriteTimeout = timeout;
X509CertificateCollection clientCertColl = new X509CertificateCollection();
if (sslClientCert != null)
clientCertColl.Add(sslClientCert);
//sslStream.AuthenticateAsClient(hostname);
sslStream.AuthenticateAsClient(RemoteHost, clientCertColl, SslProtocols.Default, sslCheckCertRevocation);
CheckSslAlgorithmsStrength(sslStream);
return sslStream;
}
private void CheckSslAlgorithmsStrength(SslStream sslStream)
{
// Check algorithms length
if (sslMinKeyExchangeAlgStrength > 0 && sslStream.KeyExchangeStrength < sslMinKeyExchangeAlgStrength)
throw new FTPSslException("The SSL/TSL key exchange algorithm strength does not fulfill the requirements: " + sslStream.KeyExchangeStrength.ToString());
if (sslMinCipherAlgStrength > 0 && sslStream.CipherStrength < sslMinCipherAlgStrength)
throw new FTPSslException("The SSL/TSL cipher algorithm strength does not fulfill the requirements: " + sslStream.CipherStrength.ToString());
if (sslMinHashAlgStrength > 0 && sslStream.HashStrength < sslMinHashAlgStrength)
throw new FTPSslException("The SSL/TSL hash algorithm strength does not fulfill the requirements: " + sslStream.HashStrength.ToString());
}
private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
bool certOk = true;
// Validate only the first time or if the certificate changes
if (this.sslServerCert == null || !sslServerCert.Equals(certificate))
{
if (userValidateServerCertificate != null)
certOk = userValidateServerCertificate(this, certificate, chain, sslPolicyErrors);
else if (sslPolicyErrors != SslPolicyErrors.None)
certOk = false;
if (certOk)
this.sslServerCert = new X509Certificate(certificate.Export(X509ContentType.Cert));
}
return certOk;
}
#region 链接
/// <summary>
/// 建立连接
/// </summary>
public void Connect()
{
//DisConnect();
socketControl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ep = new IPEndPoint(IPAddress.Parse(RemoteHost), strRemotePort);
// 链接
try
{
socketControl.Connect(ep);
}
catch (Exception)
{
throw new IOException("不能连接到FTP服务器");
}
this.userValidateServerCertificate = new RemoteCertificateValidationCallback((a, b, c, d) => { return true; });
this.sslClientCert = null;
Stream s = new NetworkStream(socketControl);
s.ReadTimeout = timeout;
s.WriteTimeout = timeout;
// SreamWriter's doc states that the default encoding is UTF8 without BOM.
// Mono 2.0 seems to have a BOM anyway. Create it explicitly as a workaround
//Encoding encoding = new UTF8Encoding(false);
//encoding = Encoding.GetEncoding("gb2312");
ctrlSr = new StreamReader(s, encoding);
ctrlSw = new StreamWriter(s, encoding);
ctrlSw.NewLine = "\r\n";
ctrlSslStream = CreateSSlStream(s, true);
SetupCtrlStreamReaderAndWriter(ctrlSslStream);
SetSslInfo(ctrlSslStream);
// Wait fot server message
string bannerMessage = GetReply().Message;
获取应答码
//ReadReply();
//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;
//PBSZ 0
//PROT P
//SendCommand("PBSZ 0");
//SendCommand("PROT P");
//SendCommand("CLNT FTPS");
//SendCommand("OPTS UTF8 ON");
//SendCommand("TYPE I");
// 切换到目录
ChDir(strRemotePath);
}
[MethodImpl(MethodImplOptions.Synchronized)]
private FTPReply GetReply()
{
try
{
FTPReply reply = new FTPReply();
bool replyDone = false;
do
{
string replyLine = ctrlSr.ReadLine();
Match m = Regex.Match(replyLine, @"^([0-9]{3})([\s\-])(.*)$");
if (m.Success)
{
int code = int.Parse(m.Groups[1].Value);
string messageLine = m.Groups[3].Value;
replyDone = (m.Groups[2].Value == " ");
if (reply.Code == 0)
{
reply.Code = code;
reply.Message = messageLine;
}
else // Multiline message
{
if (reply.Code != code)
throw new FTPReplyParseException(replyLine);
reply.Message += "\r\n" + messageLine;
}
}
else // Multiline message
{
if (reply.Code == 0)
throw new FTPReplyParseException(replyLine);
reply.Message += "\r\n" + replyLine.TrimStart();
}
}
while (!replyDone);
waitingCompletionReply = (reply.Code < 200);
if (LogServerReply != null)
LogServerReply(this, new LogServerReplyEventArgs(reply));
if (reply.Code >= 400)
throw new FTPCommandException(reply);
return reply;
}
catch (Exception)
{
waitingCompletionReply = false;
throw;
}
}
/// <summary>
/// 关闭连接
/// </summary>
public void DisConnect()
{
if (socketControl != null)
{
SendCommand("QUIT");
}
CloseSocketConnect();
}
#endregion
#region 传输模式
/// <summary>
/// 传输模式:二进制类型、ASCII类型
/// </summary>
public enum TransferType { Binary, ASCII };
/// <summary>
/// 设置传输模式
/// </summary>
/// <param name="ttType">传输模式</param>
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;
}
}
/// <summary>
/// 获得传输模式
/// </summary>
/// <returns>传输模式</returns>
public TransferType GetTransferType()
{
return trType;
}
#endregion
#region 文件操作
/// <summary>
/// 获得文件列表
/// </summary>
/// <param name="strMask">文件名的匹配字符串</param>
/// <returns></returns>
public string[] Dir(string strMask)
{
// 建立链接
if (!bConnected)
{
Connect();
}
//建立进行数据连接的socket
Socket socketData = CreateDataSocket();
//传送命令
SendCommand("NLST " + strMask);
//分析应答代码
if (!(iReplyCode == 150 || iReplyCode == 125 || iReplyCode == 226))
{
throw new IOException(strReply.Substring(4));
}
Stream s = new NetworkStream(socketData);
s = CreateSSlStream(s, false);
s.ReadTimeout = timeout;
s.WriteTimeout = timeout;
//获得结果
strMsg = "";
//while (true)
//{
// int iBytes = socketData.Receive(buffer, buffer.Length, 0);
// strMsg += Encoding.Default.GetString(buffer, 0, iBytes);
// if (iBytes < buffer.Length)
// {
// break;
// }
//}
StringBuilder data = new StringBuilder();
byte[] buf = new byte[1024];
int n = 0;
do
{
n = s.Read(buf, 0, buf.Length);
data.Append(encoding.GetString(buf, 0, n));
}
while (n != 0);
strMsg = data.ToString();
char[] seperator = { '\n' };
string[] strsFileList = strMsg.Split(seperator);
socketData.Close();//数据socket关闭时也会有返回码
if (iReplyCode != 226)
{
ReadReply();
if (iReplyCode != 226)
{
throw new IOException(strReply.Substring(4));
}
}
return strsFileList;
}
/// <summary>
/// 获取文件大小
/// </summary>
/// <param name="strFileName">文件名</param>
/// <returns>文件大小</returns>
public long GetFileSize(string strFileName)
{
if (!bConnected)
{
Connect();
}
SendCommand("SIZE " + Path.GetFileName(strFileName));
long lSize = 0;
if (iReplyCode == 213)
{
lSize = Int64.Parse(strReply.Substring(4));
}
else
{
throw new IOException(strReply.Substring(4));
}
return lSize;
}
/// <summary>
/// 删除
/// </summary>
/// <param name="strFileName">待删除文件名</param>
public void Delete(string strFileName)
{
if (!bConnected)
{
Connect();
}
SendCommand("DELE " + strFileName);
if (iReplyCode != 250)
{
throw new IOException(strReply.Substring(4));
}
}
/// <summary>
/// 重命名(如果新文件名与已有文件重名,将覆盖已有文件)
/// </summary>
/// <param name="strOldFileName">旧文件名</param>
/// <param name="strNewFileName">新文件名</param>
public void Rename(string strOldFileName, string strNewFileName)
{
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));
}
}
#endregion
#region 上传和下载
/// <summary>
/// 下载一批文件
/// </summary>
/// <param name="strFileNameMask">文件名的匹配字符串</param>
/// <param name="strFolder">本地目录(不得以\结束)</param>
public void Get(string strFileNameMask, string strFolder)
{
if (!bConnected)
{
Connect();
}
string[] strFiles = Dir(strFileNameMask);
foreach (string strFile in strFiles)
{
if (!strFile.Equals(""))//一般来说strFiles的最后一个元素可能是空字符串
{
Get(strFile, strFolder, strFile);
}
}
}
/// <summary>
/// 下载一个文件
/// </summary>
/// <param name="strRemoteFileName">要下载的文件名</param>
/// <param name="strFolder">本地目录(不得以\结束)</param>
/// <param name="strLocalFileName">保存在本地时的文件名</param>
public void Get(string strRemoteFileName, string strFolder, string strLocalFileName)
{
if (!bConnected)
{
try
{
Connect();
}
catch (Exception oe)
{
throw oe;
}
}
SetTransferType(TransferType.Binary);
if (strLocalFileName.Equals(""))
{
strLocalFileName = strRemoteFileName;
}
if (!File.Exists(strLocalFileName))
{
Stream st = File.Create(strLocalFileName);
st.Close();
}
FileStream output = new
FileStream(strFolder + "\\" + strLocalFileName, FileMode.Create);
Socket socketData = CreateDataSocket();
SendCommand("RETR " + strRemoteFileName);
if (!(iReplyCode == 150 || iReplyCode == 125
|| iReplyCode == 226 || iReplyCode == 250))
{
throw new IOException(strReply.Substring(4));
}
receivedbyte = 0;
Stream s = new NetworkStream(socketData);
s = CreateSSlStream(s, false);
s.ReadTimeout = timeout;
s.WriteTimeout = timeout;
while (true)
{
int iBytes = s.Read(buffer, 0, buffer.Length);
//int iBytes = socketData.Receive(buffer, buffer.Length, 0);
output.Write(buffer, 0, iBytes);
//yang 加入收到数据
receivedbyte += iBytes;
//yang 加入事件收到数据
if (Datareceived != null)
Datareceived(null, null);
if (iBytes <= 0)
{
break;
}
}
output.Close();
if (socketData.Connected)
{
socketData.Close();
}
if (!(iReplyCode == 226 || iReplyCode == 250))
{
ReadReply();
if (!(iReplyCode == 226 || iReplyCode == 250))
{
throw new IOException(strReply.Substring(4));
}
}
//yang 加入数据接收完成
if (Filereceived != null)
Filereceived(null, null);
}
/// <summary>
/// 上传一批文件
/// </summary>
/// <param name="strFolder">本地目录(不得以\结束)</param>
/// <param name="strFileNameMask">文件名匹配字符(可以包含*和?)</param>
public void Put(string strFolder, string strFileNameMask)
{
string[] strFiles = Directory.GetFiles(strFolder, strFileNameMask);
foreach (string strFile in strFiles)
{
//strFile是完整的文件名(包含路径)
Put(strFile);
}
}
/// <summary>
/// 上传一个文件
/// </summary>
/// <param name="strFileName">本地文件名</param>
public void Put(string strFileName)
{
if (!bConnected)
{
Connect();
}
Socket socketData = CreateDataSocket();
SendCommand("STOR " + Path.GetFileName(strFileName));
if (!(iReplyCode == 125 || iReplyCode == 150))
{
throw new IOException(strReply.Substring(4));
}
//FileStream input = new
//FileStream(strFileName, FileMode.Open);
int iBytes = 0;
receivedbyte = 0;
//FTPS
Stream s = new NetworkStream(socketData);
s = CreateSSlStream(s, false);
s.ReadTimeout = timeout;
s.WriteTimeout = timeout;
using (FileStream fs = File.OpenRead(strFileName))
{
byte[] buf = new byte[1024];
int n = 0;
do
{
n = fs.Read(buf, 0, buf.Length);
if (n > 0)
{
s.Write(buf, 0, n);
}
if (Datasend != null)
Datasend(null, null);
}
while (n > 0);
fs.Close();
}
s.Close();
//while ((iBytes = input.Read(buffer, 0, buffer.Length)) > 0)
//{
// //FTPS
// s.Write(buffer, 0, buffer.Length);
// //socketData.Send(buffer, iBytes, 0);
// //yang 加入上传数据
// receivedbyte += iBytes;
// //yang 加入事件上传数据
// if (Datasend != null)
// Datasend(null, null);
//}
//input.Close();
if (socketData.Connected)
{
socketData.Close();
}
if (!(iReplyCode == 226 || iReplyCode == 250))
{
ReadReply();
if (!(iReplyCode == 226 || iReplyCode == 250))
{
throw new IOException(strReply.Substring(4));
}
}
//yang 加入数据上传完成
if (Filesend != null)
Filesend(null, null);
}
#endregion
#region 目录操作
/// <summary>
/// 创建目录
/// </summary>
/// <param name="strDirName">目录名</param>
public void MkDir(string strDirName)
{
if (!bConnected)
{
Connect();
}
SendCommand("MKD " + strDirName);
if (iReplyCode != 257)
{
throw new IOException(strReply.Substring(4));
}
}
/// <summary>
/// 删除目录
/// </summary>
/// <param name="strDirName">目录名</param>
public void RmDir(string strDirName)
{
if (!bConnected)
{
Connect();
}
SendCommand("RMD " + strDirName);
if (iReplyCode != 250)
{
throw new IOException(strReply.Substring(4));
}
}
/// <summary>
/// 改变目录
/// </summary>
/// <param name="strDirName">新的工作目录名</param>
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;
}
#endregion
#region 内部变量
/// <summary>
/// 服务器返回的应答信息(包含应答码)
/// </summary>
private string strMsg;
/// <summary>
/// 服务器返回的应答信息(包含应答码)
/// </summary>
private string strReply;
/// <summary>
/// 服务器返回的应答码
/// </summary>
private int iReplyCode;
/// <summary>
/// 进行控制连接的socket
/// </summary>
private Socket socketControl;
/// <summary>
/// 传输模式
/// </summary>
private TransferType trType;
/// <summary>
/// 接收和发送数据的缓冲区
/// </summary>
public static int BLOCK_SIZE = 512;
Byte[] buffer = new Byte[BLOCK_SIZE];
/// <summary>
/// 编码方式
/// </summary>
Encoding ASCII = Encoding.ASCII;
#endregion
#region 内部函数
/// <summary>
/// 将一行应答字符串记录在strReply和strMsg
/// 应答码记录在iReplyCode
/// </summary>
private void ReadReply()
{
strMsg = "";
strReply = ctrlSr.ReadLine();// ReadLine();
iReplyCode = Int32.Parse(strReply.Substring(0, 3));
}
/// <summary>
/// 建立进行数据连接的socket
/// </summary>
/// <returns>数据连接socket</returns>
private Socket CreateDataSocket()
{
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];
Socket s = new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ep = new
IPEndPoint(IPAddress.Parse(RemoteHost), port);
try
{
s.Connect(ep);
}
catch (Exception)
{
throw new IOException("不能连接到FTP服务器");
}
return s;
}
/// <summary>
/// 关闭socket连接(用于登录以前)
/// </summary>
private void CloseSocketConnect()
{
if (socketControl != null)
{
socketControl.Close();
socketControl = null;
}
bConnected = false;
}
/ <summary>
/ 读取Socket返回的所有字符串
/ </summary>
/ <returns>包含应答码的字符串行</returns>
//private string ReadLine()
//{
// while (true)
// {
// int iBytes = socketControl.Receive(buffer, buffer.Length, 0);
// strMsg += ASCII.GetString(buffer, 0, iBytes);
// if (iBytes < buffer.Length)
// {
// break;
// }
// }
// char[] seperator = { '\n' };
// string[] mess = strMsg.Split(seperator);
// if (strMsg.Length > 2)
// {
// strMsg = mess[mess.Length - 2];
// //seperator[0]是10,换行符是由13和0组成的,分隔后10后面虽没有字符串,
// //但也会分配为空字符串给后面(也是最后一个)字符串数组,
// //所以最后一个mess是没用的空字符串
// //但为什么不直接取mess[0],因为只有最后一行字符串应答码与信息之间有空格
// //ggggggggggggg
// }
// else
// {
// strMsg = mess[0];
// }
// if (!strMsg.Substring(3, 1).Equals(" "))//返回字符串正确的是以应答码(如220开头,后面接一空格,再接问候字符串)
// {
// return ReadLine();
// }
// return strMsg;
//}
/// <summary>
/// 发送命令并获取应答码和最后一行应答字符串
/// </summary>
/// <param name="strCommand">命令</param>
private void SendCommand(String strCommand)
{
ctrlSw.WriteLine(strCommand);
ctrlSw.Flush();
ReadReply();
//Byte[] cmdBytes =
//Encoding.Default.GetBytes((strCommand + "\r\n").ToCharArray());
//socketControl.Send(cmdBytes, cmdBytes.Length, 0);
//ReadReply();
}
#endregion
}
public class FTPReply
{
private int code;
private string message;
public int Code
{
get { return code; }
set { code = value; }
}
public string Message
{
get { return message; }
set { message = value; }
}
public override string ToString()
{
return string.Format("{0} {1}", Code, Message);
}
}
}