1 using System;
2 using System.Collections.Generic;
3 using System.Net;
4 using System.IO;
5
6 namespace FTP操作
7 {
8 /// <summary>
9 /// FTP客户端操作类
10 /// </summary>
11 public class FtpClient
12 {
13 #region 构造函数
14 /// <summary>
15 /// 创建FTP工具
16 /// <para>
17 /// 默认不使用SSL,使用二进制传输方式,使用被动模式
18 /// </para>
19 /// </summary>
20 /// <param name="host">主机名称</param>
21 /// <param name="userId">用户名</param>
22 /// <param name="password">密码</param>
23 public FtpClient(string host, string userId, string password)
24 : this(host, userId, password, 21, null, false, true, true)
25 {
26 }
27
28 /// <summary>
29 /// 创建FTP工具
30 /// </summary>
31 /// <param name="host">主机名称</param>
32 /// <param name="userId">用户名</param>
33 /// <param name="password">密码</param>
34 /// <param name="port">端口</param>
35 /// <param name="enableSsl">允许Ssl</param>
36 /// <param name="proxy">代理</param>
37 /// <param name="useBinary">允许二进制</param>
38 /// <param name="usePassive">允许被动模式</param>
39 public FtpClient(string host, string userId, string password, int port, IWebProxy proxy, bool enableSsl, bool useBinary, bool usePassive)
40 {
41 this.userId = userId;
42 this.password = password;
43 if (host.ToLower().StartsWith("ftp://"))
44 {
45 this.host = host;
46 }
47 else
48 {
49 this.host = "ftp://" + host;
50 }
51 this.port = port;
52 this.proxy = proxy;
53 this.enableSsl = enableSsl;
54 this.useBinary = useBinary;
55 this.usePassive = usePassive;
56 }
57 #endregion
58
59 #region 主机
60 private string host = string.Empty;
61 /// <summary>
62 /// 主机
63 /// </summary>
64 public string Host
65 {
66 get
67 {
68 return this.host ?? string.Empty;
69 }
70 }
71 #endregion
72
73 #region 登录用户名
74 private string userId = string.Empty;
75 /// <summary>
76 /// 登录用户名
77 /// </summary>
78 public string UserId
79 {
80 get
81 {
82 return this.userId;
83 }
84 }
85 #endregion
86
87 #region 密码
88 private string password = string.Empty;
89 /// <summary>
90 /// 密码
91 /// </summary>
92 public string Password
93 {
94 get
95 {
96 return this.password;
97 }
98 }
99 #endregion
100
101 #region 代理
102 IWebProxy proxy = null;
103 /// <summary>
104 /// 代理
105 /// </summary>
106 public IWebProxy Proxy
107 {
108 get
109 {
110 return this.proxy;
111 }
112 set
113 {
114 this.proxy = value;
115 }
116 }
117 #endregion
118
119 #region 端口
120 private int port = 21;
121 /// <summary>
122 /// 端口
123 /// </summary>
124 public int Port
125 {
126 get
127 {
128 return port;
129 }
130 set
131 {
132 this.port = value;
133 }
134 }
135 #endregion
136
137 #region 设置是否允许Ssl
138 private bool enableSsl = false;
139 /// <summary>
140 /// EnableSsl
141 /// </summary>
142 public bool EnableSsl
143 {
144 get
145 {
146 return enableSsl;
147 }
148 }
149 #endregion
150
151 #region 使用被动模式
152 private bool usePassive = true;
153 /// <summary>
154 /// 被动模式
155 /// </summary>
156 public bool UsePassive
157 {
158 get
159 {
160 return usePassive;
161 }
162 set
163 {
164 this.usePassive = value;
165 }
166 }
167 #endregion
168
169 #region 二进制方式
170 private bool useBinary = true;
171 /// <summary>
172 /// 二进制方式
173 /// </summary>
174 public bool UseBinary
175 {
176 get
177 {
178 return useBinary;
179 }
180 set
181 {
182 this.useBinary = value;
183 }
184 }
185 #endregion
186
187 #region 远端路径
188 private string remotePath = "/";
189 /// <summary>
190 /// 远端路径
191 /// <para>
192 /// 返回FTP服务器上的当前路径(可以是 / 或 /a/../ 的形式)
193 /// </para>
194 /// </summary>
195 public string RemotePath
196 {
197 get
198 {
199 return remotePath;
200 }
201 set
202 {
203 string result = "/";
204 if (!string.IsNullOrEmpty(value) && value != "/")
205 {
206 result = "/" + value.TrimStart('/').TrimEnd('/') + "/";
207 }
208 this.remotePath = result;
209 }
210 }
211 #endregion
212
213 #region 创建一个FTP连接
214 /// <summary>
215 /// 创建一个FTP请求
216 /// </summary>
217 /// <param name="url">请求地址</param>
218 /// <param name="method">请求方法</param>
219 /// <returns>FTP请求</returns>
220 private FtpWebRequest CreateRequest(string url, string method)
221 {
222 //建立连接
223 FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
224 request.Credentials = new NetworkCredential(this.userId, this.password);
225 request.Proxy = this.proxy;
226 request.KeepAlive = false;//命令执行完毕之后关闭连接
227 request.UseBinary = useBinary;
228 request.UsePassive = usePassive;
229 request.EnableSsl = enableSsl;
230 request.Method = method;
231 return request;
232 }
233 #endregion
234
235 #region 上传一个文件到远端路径下
236 /// <summary>
237 /// 把文件上传到FTP服务器的RemotePath下
238 /// </summary>
239 /// <param name="localFile">本地文件信息</param>
240 /// <param name="remoteFileName">要保存到FTP文件服务器上的名称</param>
241 public bool Upload(FileInfo localFile, string remoteFileName)
242 {
243 bool result = false;
244 if (localFile.Exists)
245 {
246 string url = Host.TrimEnd('/') + RemotePath + remoteFileName;
247 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.UploadFile);
248
249 //上传数据
250 using (Stream rs = request.GetRequestStream())
251 using (FileStream fs = localFile.OpenRead())
252 {
253 byte[] buffer = new byte[4096];//4K
254 int count = fs.Read(buffer, 0, buffer.Length);
255 while (count > 0)
256 {
257 rs.Write(buffer, 0, count);
258 count = fs.Read(buffer, 0, buffer.Length);
259 }
260 fs.Close();
261 result = true;
262 }
263 return result;
264 }
265 throw new Exception(string.Format("本地文件不存在,文件路径:{0}", localFile.FullName));
266 }
267 #endregion
268
269 #region 从FTP服务器上下载文件
270 /// <summary>
271 /// 从当前目录下下载文件
272 /// <para>
273 /// 如果本地文件存在,则从本地文件结束的位置开始下载.
274 /// </para>
275 /// </summary>
276 /// <param name="serverName">服务器上的文件名称</param>
277 /// <param name="localName">本地文件名称</param>
278 /// <returns>返回一个值,指示是否下载成功</returns>
279 public bool Download(string serverName, string localName)
280 {
281 bool result = false;
282 using (FileStream fs = new FileStream(localName, FileMode.OpenOrCreate)) //创建或打开本地文件
283 {
284 //建立连接
285 string url = Host.TrimEnd('/') + RemotePath + serverName;
286 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.DownloadFile);
287 request.ContentOffset = fs.Length;
288 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
289 {
290 fs.Position = fs.Length;
291 byte[] buffer = new byte[4096];//4K
292 int count = response.GetResponseStream().Read(buffer, 0, buffer.Length);
293 while (count > 0)
294 {
295 fs.Write(buffer, 0, count);
296 count = response.GetResponseStream().Read(buffer, 0, buffer.Length);
297 }
298 response.GetResponseStream().Close();
299 }
300 result = true;
301 }
302 return result;
303 }
304 #endregion
305
306 #region 重命名FTP服务器上的文件
307 /// <summary>
308 /// 文件更名
309 /// </summary>
310 /// <param name="oldFileName">原文件名</param>
311 /// <param name="newFileName">新文件名</param>
312 /// <returns>返回一个值,指示更名是否成功</returns>
313 public bool Rename(string oldFileName, string newFileName)
314 {
315 bool result = false;
316 //建立连接
317 string url = Host.TrimEnd('/') + RemotePath + oldFileName;
318 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.Rename);
319 request.RenameTo = newFileName;
320 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
321 {
322 result = true;
323 }
324 return result;
325 }
326 #endregion
327
328 #region 从当前目录下获取文件列表
329 /// <summary>
330 /// 获取当前目录下文件列表
331 /// </summary>
332 /// <returns></returns>
333 public List<string> GetFileList()
334 {
335 List<string> result = new List<string>();
336 //建立连接
337 string url = Host.TrimEnd('/') + RemotePath;
338 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.ListDirectory);
339 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
340 {
341 StreamReader reader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.Default);//中文文件名
342 string line = reader.ReadLine();
343 while (line != null)
344 {
345 result.Add(line);
346 line = reader.ReadLine();
347 }
348 }
349 return result;
350 }
351 #endregion
352
353 #region 从FTP服务器上获取文件和文件夹列表
354 /// <summary>
355 /// 获取详细列表
356 /// </summary>
357 /// <returns></returns>
358 public List<string> GetFileDetails()
359 {
360 List<string> result = new List<string>();
361 //建立连接
362 string url = Host.TrimEnd('/') + RemotePath;
363 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.ListDirectoryDetails);
364 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
365 {
366 StreamReader reader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.Default);//中文文件名
367 string line = reader.ReadLine();
368 while (line != null)
369 {
370 result.Add(line);
371 line = reader.ReadLine();
372 }
373 }
374 return result;
375 }
376 #endregion
377
378 #region 从FTP服务器上删除文件
379 /// <summary>
380 /// 删除FTP服务器上的文件
381 /// </summary>
382 /// <param name="fileName">文件名称</param>
383 /// <returns>返回一个值,指示是否删除成功</returns>
384 public bool DeleteFile(string fileName)
385 {
386 bool result = false;
387 //建立连接
388 string url = Host.TrimEnd('/') + RemotePath + fileName;
389 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.DeleteFile);
390 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
391 {
392 result = true;
393 }
394
395 return result;
396 }
397 #endregion
398
399 #region 在FTP服务器上创建目录
400 /// <summary>
401 /// 在当前目录下创建文件夹
402 /// </summary>
403 /// <param name="dirName">文件夹名称</param>
404 /// <returns>返回一个值,指示是否创建成功</returns>
405 public bool MakeDirectory(string dirName)
406 {
407 bool result = false;
408 //建立连接
409 string url = Host.TrimEnd('/') + RemotePath + dirName;
410 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.MakeDirectory);
411 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
412 {
413 result = true;
414 }
415 return result;
416 }
417 #endregion
418
419 #region 从FTP服务器上删除目录
420 /// <summary>
421 /// 删除文件夹
422 /// </summary>
423 /// <param name="dirName">文件夹名称</param>
424 /// <returns>返回一个值,指示是否删除成功</returns>
425 public bool DeleteDirectory(string dirName)
426 {
427 bool result = false;
428 //建立连接
429 string url = Host.TrimEnd('/') + RemotePath + dirName;
430 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.RemoveDirectory);
431 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
432 {
433 result = true;
434 }
435 return result;
436 }
437 #endregion
438
439 #region 从FTP服务器上获取文件大小
440 /// <summary>
441 /// 获取文件大小
442 /// </summary>
443 /// <param name="fileName"></param>
444 /// <returns></returns>
445 public long GetFileSize(string fileName)
446 {
447 long result = 0;
448 //建立连接
449 string url = Host.TrimEnd('/') + RemotePath + fileName;
450 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.GetFileSize);
451 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
452 {
453 result = response.ContentLength;
454 }
455
456 return result;
457 }
458 #endregion
459
460 #region 给FTP服务器上的文件追加内容
461 /// <summary>
462 /// 给FTP服务器上的文件追加内容
463 /// </summary>
464 /// <param name="localFile">本地文件</param>
465 /// <param name="remoteFileName">FTP服务器上的文件</param>
466 /// <returns>返回一个值,指示是否追加成功</returns>
467 public bool Append(FileInfo localFile, string remoteFileName)
468 {
469 if (localFile.Exists)
470 {
471 using (FileStream fs = new FileStream(localFile.FullName, FileMode.Open))
472 {
473 return Append(fs, remoteFileName);
474 }
475 }
476 throw new Exception(string.Format("本地文件不存在,文件路径:{0}", localFile.FullName));
477 }
478
479 /// <summary>
480 /// 给FTP服务器上的文件追加内容
481 /// </summary>
482 /// <param name="stream">数据流(可通过设置偏移来实现从特定位置开始上传)</param>
483 /// <param name="remoteFileName">FTP服务器上的文件</param>
484 /// <returns>返回一个值,指示是否追加成功</returns>
485 public bool Append(Stream stream, string remoteFileName)
486 {
487 bool result = false;
488 if (stream != null && stream.CanRead)
489 {
490 //建立连接
491 string url = Host.TrimEnd('/') + RemotePath + remoteFileName;
492 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.AppendFile);
493 using (Stream rs = request.GetRequestStream())
494 {
495 //上传数据
496 byte[] buffer = new byte[4096];//4K
497 int count = stream.Read(buffer, 0, buffer.Length);
498 while (count > 0)
499 {
500 rs.Write(buffer, 0, count);
501 count = stream.Read(buffer, 0, buffer.Length);
502 }
503 result = true;
504 }
505 }
506 return result;
507 }
508 #endregion
509
510 #region 获取FTP服务器上的当前路径
511 /// <summary>
512 /// 获取FTP服务器上的当前路径
513 /// </summary>
514 public string CurrentDirectory
515 {
516 get
517 {
518 string result = string.Empty;
519 string url = Host.TrimEnd('/') + RemotePath;
520 FtpWebRequest request = CreateRequest(url, WebRequestMethods.Ftp.PrintWorkingDirectory);
521 using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
522 {
523 string temp = response.StatusDescription;
524 int start = temp.IndexOf('"') + 1;
525 int end = temp.LastIndexOf('"');
526 if (end >= start)
527 {
528 result = temp.Substring(start, end - start);
529 }
530 }
531 return result;
532
533 }
534 }
535 #endregion
536
537 #region 检查当前路径上是否存在某个文件
538 /// <summary>
539 /// 检查文件是否存在
540 /// </summary>
541 /// <param name="fileName">要检查的文件名</param>
542 /// <returns>返回一个值,指示要检查的文件是否存在</returns>
543 public bool CheckFileExist(string fileName)
544 {
545 bool result = false;
546 if (fileName != null && fileName.Trim().Length > 0)
547 {
548 fileName = fileName.Trim();
549 List<string> files = GetFileList();
550 if (files != null && files.Count > 0)
551 {
552 foreach (string file in files)
553 {
554 if (file.ToLower() == fileName.ToLower())
555 {
556 result = true;
557 break;
558 }
559 }
560 }
561 }
562 return result;
563 }
564 #endregion
565
566 }
567 }
568
569 /*
570 FTP全状态码查询词典
571
572 1xx - 肯定的初步答复
573 这些状态代码指示一项操作已经成功开始,但客户端希望在继续操作新命令前得到另一个答复。 • 110 重新启动标记答复。
574 • 120 服务已就绪,在 nnn 分钟后开始。
575 • 125 数据连接已打开,正在开始传输。
576 • 150 文件状态正常,准备打开数据连接。
577
578 2xx - 肯定的完成答复
579 一项操作已经成功完成。客户端可以执行新命令。 • 200 命令确定。
580 • 202 未执行命令,站点上的命令过多。
581 • 211 系统状态,或系统帮助答复。
582 • 212 目录状态。
583 • 213 文件状态。
584 • 214 帮助消息。
585 • 215 NAME 系统类型,其中,NAME 是 Assigned Numbers 文档中所列的正式系统名称。
586 • 220 服务就绪,可以执行新用户的请求。
587 • 221 服务关闭控制连接。如果适当,请注销。
588 • 225 数据连接打开,没有进行中的传输。
589 • 226 关闭数据连接。请求的文件操作已成功(例如,传输文件或放弃文件)。
590 • 227 进入被动模式 (h1,h2,h3,h4,p1,p2)。
591 • 230 用户已登录,继续进行。
592 • 250 请求的文件操作正确,已完成。
593 • 257 已创建“PATHNAME”。
594
595 3xx - 肯定的中间答复
596 该命令已成功,但服务器需要更多来自客户端的信息以完成对请求的处理。 • 331 用户名正确,需要密码。
597 • 332 需要登录帐户。
598 • 350 请求的文件操作正在等待进一步的信息。
599
600 4xx - 瞬态否定的完成答复
601 该命令不成功,但错误是暂时的。如果客户端重试命令,可能会执行成功。 • 421 服务不可用,正在关闭控制连接。如果服务确定它必须关闭,将向任何命令发送这一应答。
602 • 425 无法打开数据连接。
603 • 426 Connection closed; transfer aborted.
604 • 450 未执行请求的文件操作。文件不可用(例如,文件繁忙)。
605 • 451 请求的操作异常终止:正在处理本地错误。
606 • 452 未执行请求的操作。系统存储空间不够。
607
608 5xx - 永久性否定的完成答复
609 该命令不成功,错误是永久性的。如果客户端重试命令,将再次出现同样的错误。 • 500 语法错误,命令无法识别。这可能包括诸如命令行太长之类的错误。
610 • 501 在参数中有语法错误。
611 • 502 未执行命令。
612 • 503 错误的命令序列。
613 • 504 未执行该参数的命令。
614 • 530 未登录。
615 • 532 存储文件需要帐户。
616 • 550 未执行请求的操作。文件不可用(例如,未找到文件,没有访问权限)。
617 • 551 请求的操作异常终止:未知的页面类型。
618 • 552 请求的文件操作异常终止:超出存储分配(对于当前目录或数据集)。
619 • 553 未执行请求的操作。不允许的文件名。
620 常见的 FTP 状态代码及其原因
621 • 150 - FTP 使用两个端口:21 用于发送命令,20 用于发送数据。状态代码 150 表示服务器准备在端口 20 上打开新连接,发送一些数据。
622 • 226 - 命令在端口 20 上打开数据连接以执行操作,如传输文件。该操作成功完成,数据连接已关闭。
623 • 230 - 客户端发送正确的密码后,显示该状态代码。它表示用户已成功登录。
624 • 331 - 客户端发送用户名后,显示该状态代码。无论所提供的用户名是否为系统中的有效帐户,都将显示该状态代码。
625 • 426 - 命令打开数据连接以执行操作,但该操作已被取消,数据连接已关闭。
626 • 530 - 该状态代码表示用户无法登录,因为用户名和密码组合无效。如果使用某个用户帐户登录,可能键入错误的用户名或密码,也可能选择只允许匿名访问。如果使用匿名帐户登录,IIS 的配置可能拒绝匿名访问。
627 • 550 - 命令未被执行,因为指定的文件不可用。例如,要 GET 的文件并不存在,或试图将文件 PUT 到您没有写入权限的目录。
628 */