脑残的FtpWebRequest 2之事故现场重现

鉴于上一篇POST过于抽象以至于很多人无法理解,现在用代码来说话,我们一起来重新回顾一下事故现场的情况。

首先在本机安装FTP软件,我这里使用的Serv-U一个用得非常广泛的Ftp Server,准备好Ftp的目录,我这里使用自己放mp3的目录

Prepare_ftp_dir

其中有一个子目录 2

Prepare_ftp_dir2

我们可以看到两个目录的内容截然不同以方便我们重现事故现场。

第二部我们建立一个Winform项目

Create_project

之后在界面上用三个按钮来实现testCase

Test_Case

 

 

 

第三步是准备的重头戏,我们准备一个Ftp的操作类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
 
namespace TestFtpWebRequest
{
    class FtpHelper
    {
        public static void Upload(string Url, string LocalPath)
        {
            FileInfo f = new FileInfo(LocalPath);
            FtpWebRequest req = (FtpWebRequest)FtpWebRequest.Create(Url + "/" + f.Name);
            req.Credentials = new NetworkCredential("Test", "123456");
            req.Method = WebRequestMethods.Ftp.UploadFile;
            req.UseBinary = true;
            req.ContentLength = f.Length;
            FtpWebResponse rep = (FtpWebResponse)req.GetResponse();
            using (Stream s = req.GetRequestStream())
            {
                using (FileStream fs = f.OpenRead())
                {
                    int readcount = 0;
                    long totalread = 0;
                    byte[] buffer = new byte[1024];
                    while (true)
                    {
                        readcount = fs.Read(buffer, 0, 1024);
                        totalread += readcount;
                        s.Write(buffer, 0, readcount);
                        if (totalread >= f.Length)
                        {
                            break;
                        }
                    }
 
                }
            }
        }
        public static string[] List(string Url)
        {
            List<string> rs = new List<string>();
            FtpWebRequest req = (FtpWebRequest)FtpWebRequest.Create(Url);
            req.Credentials = new NetworkCredential("Test", "123456");
            req.Method = WebRequestMethods.Ftp.ListDirectory;
            FtpWebResponse rep = (FtpWebResponse)req.GetResponse();
            long len = rep.ContentLength;
            using (Stream s = rep.GetResponseStream())
            {
                MemoryStream ms = new MemoryStream();
                int readcount = 0;
                long totalcount = 0;
                byte[] buffer = new byte[1024];
                while (true)
                {
                    readcount = s.Read(buffer, 0, 1024);
                    totalcount += readcount;
                    ms.Write(buffer, 0, readcount);
                    if (totalcount >= len)
                    {
                        break;
                    }
                }
                s.Close();
                rep.Close();
                string str = Encoding.Default.GetString(ms.ToArray());
                StringReader sr = new StringReader(str);
                string line = string.Empty;
                while ((line = sr.ReadLine()) != null)
                {
                    rs.Add(line);
                }
            }
            return rs.ToArray();
        }
    }
}

这里准备了两个操作方法,一个是读取文件列表,一个是上传文件

最后在按钮里写上调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace TestFtpWebRequest
{
    public partial class Form1 : Form
    {
        const string Path1 = @"D:\MyDocument\test.jpg";
        const string Path2 = @"D:\MyDocument\yoxi.jpg";
        const string FtpUrl = "ftp://192.168.212.102";
        const string FtpUrl2 = "ftp://192.168.212.102/2";
 
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button3_Click(object sender, EventArgs e)
        {
            textBox1.Text = string.Empty;
            string[] files = FtpHelper.List(FtpUrl);
 
            textBox1.Text = "file count:" + files.Length + "\r\n";
            foreach (string s in files)
            {
                textBox1.Text += "ftp:" + s + "\r\n";
            }
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            FtpHelper.Upload(FtpUrl2, Path1);
            MessageBox.Show("up ok!");
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            FtpHelper.Upload(FtpUrl2, Path2);
            MessageBox.Show("up ok!");
        }
    }
}

好了,根据两个ftp的方法大家可以看到,从表面上两个方法应该是独立的,不管在什么时候,我如果按列表的按钮都应该返回Ftp根目录的文件列表出来。但是实际的执行结果,我们拭目以待。

首先我们执行程序,点击列表按钮(List with no upload),这个时候我们能够看到返回的确实是根目录的文件

image 

现在我们继续点击 FirstUpload,按钮,猜猜结果怎么样? 报错了:

image

路径明明是对的,为什么报错?大家可以自己想想

接下来我们把顺序换一下。重新执行程序,这次先点击Firstupload,结果上传成功,结果如下

image

好,这个时候我们再点击列表按钮,本来应该显示根目录内容的,结果却显示的:

image 

这下看明白了没?好的,接下来我们调整一下Ftp的设置,现在Ftp账号的设置如下:

image

我们现在把将用户锁定于主目录的勾去掉:

image

 

 

 

 

如此这般,好了,现在我们再次执行程序。

先点击列表,然后再点击上传,看看,没报错,成功了:

image

---------------------------------------------------------

一开始我怀疑是因为KeepAlive保持了链接造成的,因为这个结果非常像是每一个Request都复用了连接,所以我两个方法都加上了KeepAlive=false

然后恢复Serv-U的设置,将锁定用户于主目录的勾选上。

执行程序,点击列表,再点击上传,成功了,貌似没问题了吧。不过不要高兴得太早,这个时候再点击一次列表,看,报错了:

image

到现在为止,我应该对事故的由来,前因后果说得很清楚了。

这里我总结出了几点问题。

1.不管是不是要复用底层的链接,但是在语义上的一致性是需要保证的,一个Request,既然用url :Ftp://ip/创建出来的,那么就应该是操作Ftp的根而不是其他目录,如果这个都不能保证,那么谁还能信任这样的一个工具?更不用说多线程的环境下工作会如何了。

2.为了安全性,大部分ftp都会锁定用户到主目录,一个ftpclient怎么能够依赖于服务端的设置工作?无论是谁做出这种脑残设定都是不对的。

3.KeepAlive禁用后,居然同样的过程执行都会报错,不可理喻。

具体问题处在哪里,等我下次分析了。

posted on   亚历山大同志  阅读(3980)  评论(7编辑  收藏  举报

(评论功能已被禁用)
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述

导航

< 2009年11月 >
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 1 2 3 4 5
6 7 8 9 10 11 12

统计

点击右上角即可分享
微信分享提示