[C# 网络编程系列]专题十一:实现一个基于FTP协议的程序——文件上传下载器
引言:
在这个专题将为大家揭开下FTP这个协议的面纱,其实学习知识和生活中的例子都是很相通的,就拿这个专题来说,要了解FTP协议然后根据FTP协议实现一个文件下载器,就和和追MM是差不多的过程的,相信大家追MM都有自己的经验的,我感觉大部分的过程肯定是——第一步: 先通过工作关系或者朋友关系等认识MM(认识FTP协议,知道FTP协议的是什么) ; 第二步: 当然了解MM有兴趣爱好了(了解FTP协议有哪些命令和工作过程)第三步:如果对方是你的菜的话,那当然要采取追求的了(就好比用了解到的FTP协议来实现一个文件上传下载器)。不过追MM好像对我来说还是比较难的了, 所以还是言归正传了,还是好好的学习我的代码吧,回到本章的内容——FTP的协议。
(注:最近想好好改进下文章的幽默程度,所以文章中会尽量以有趣的生活中的例子来表述网络编程的知识,希望可以让大家在学习知识的同时也可以获得乐趣,如果有什么地方理解不准确的还望大家多多指出。)
一、FTP协议的自我介绍
我们在上学的时候,同学们第一次开学的时候老师一般会组织大学到讲台上进行自我介绍,让同学都互相认识,同样,如果对于没有接触过FTP协议的朋友来说,FTP协议的自我介绍当然也是不可避免的了,这样我们才能进一步去了解FTP协议 “这位同学”了,之后才能和他成为好朋友,或者是好基友了。下面就开始FTP协议同学的自我介绍了, 大家热烈欢迎。
FTP 协议同学: 大家好, 我的名字叫FTP,FTP是文件传输协议(File Transfer Protocol,FTP),我的工作就是负责将文件从一台计算机传输到另外一台计算机,并且我还可以保证传输的可靠性。我的工作流程可以通过下面的一张图来表达:
从图中大家应该可以明白我的工作过程了吧,我的工作过程是典型的C/S模式——我的客户端(在本章实现的文件上传下载器属于客户端)首先发起与我的服务器连接连接,告诉我的服务器说“我现在想和你聊聊天”, 然后我的服务器收到这个请求后给出回答——“聊天,当然可以了,我批准了”,客户端收到这个信息后,就可以服务器之间就建立一条马路或者是通道,然后我的客户端好还想进一步了解下我的服务器,在发出一个说“我想要下载你上面的东西 或者是 我想上传一些文件到你那里,想让你帮我保管下,这样我可以随处都可以从你那里得到我上传的资料的”, 我的服务器收到请求后,如果允许客户端这么做的话就会回答说 “可以啊”(就像我们追女生一样,建立好关系后,当然就要表白了,此时我们就说“我很喜欢你之类的话”,然后等待MM的回答,“可以啊” 这个答案都是我们希望听到的答案的),我的客户端听到后非常开心,马上选择自己需要上传的文件或者想从服务器下载的文件找到,上传或者下载该文件的。 我还要补充一点,在访问我的FTP服务器之前必须登录,这样我的服务器才认识你,才可能会搭理你的,登录时就需要客户端提供一个用户名和密码,提供了正确的用户名和密码后就可以和我的服务器进行聊天 和请求上传或下载我服务器上的文件了; 然而我的某些服务器提供了一种匿名的方式,我的客户端不需要提供用户名和密码就可以进行聊天了,其实匿名的方式和我聊天的本质是:提供服务的公司或机构在我的服务器上建立一个公用的账户,方便那些没有提供用户名和密码的客户端与我聊天。 上面就是我的自我介绍了,谢谢大家。
二、.Net 为实现我的客户端提供了些什么?
可以说微软真是一位雷锋叔叔的,因为在他的.Net类库中提供了很多类库供我们使用,当然为实现我的客户端也提供了一些类的支持的, 现在就看看这位好人帮我们提供了哪些类来对实现一个FTP客户端程序的支持的。
这位好人通过命名空间System.Net下的FtpWebRequest类和FtpWebResponse类提供对实现FTP客户端的支持。
2.1 FtpWebRequest类
该类是WebRequest类的派生类,FTPWebRequest类用于向服务器发出请求,告诉服务器说“我想和你聊天",如果要获得FtpWebRequest的一个实例,则需要使用Create方法来创建实例,对于该类如何使用我在这里也就不一一列出来的, 大家可以查看MSDN的相关文档来了解方法的使用,并且在本专题实现的程序中也会有所介绍的,下面给出MSDN中的一个链接的:
http://msdn.microsoft.com/zh-cn/library/8exfzxft.aspx
2.2 FtpWebResponse类
FTP客户端既然发话了,服务器当然也要有所表示的了, 不要哑巴一样不说话的,总要给个答复的,FtpWebResponse类就负责封装FTP服务器对客户端请求的回答的一个类。FTP客户端通过GetResponse方法来获得FtpWebResponse类的对象的,如果服务器回答说“我们可以聊天的”,这样就说明他们俩就可以互相沟通了,就好比追MM的时候你问MM说“可以给电话号码给我吗?”,然后MM对你也有好感就告诉你一个号码后,得到MM的号码也就和MM建立了沟通的通道了,就好比服务器回答“我们可以聊天的”。之后客户端和服务器就可以进行进一步的沟通(上传文件到服务器或者要求服务器给些文件给客户端),之后的过程就好比你可以通过电话号码和MM进一步的交流,知道MM的有些什么性格和爱好的。下面提供一个MSDN中该类的使用链接,这里我就不一一介绍他的成员了,大家可以到MSDN中查看的,上面每个属性和方法都有一个比较好的解释,并且大家也可以通过下面实现的FTP客户端程序进一步了解该类的使用:
http://msdn.microsoft.com/zh-cn/library/system.net.ftpwebresponse.aspx
三、如何实现一个FTP客户端程序?——看完下面的介绍你就会知道了
通过FTP协议的自我介绍部分大家应该可以明白了FTP协议的工作过程的,然而一个FTP客户端程序就是基于FTP协议的文件上传下载器,通过这个程序大家可以对FTP服务器上的资料进行浏览、上传和下载等操作的。
程序中主要模块的代码:
登录模块:
#region 登录模块的实现 // 登录服务器事件 private void btnlogin_Click(object sender, EventArgs e) { if (tbxServerIp.Text == string.Empty) { MessageBox.Show("请先填写服务器IP地址", "提示"); return; } ftpUristring = "ftp://" + tbxServerIp.Text; networkCredential = new NetworkCredential(tbxUsername.Text, tbxPassword.Text); if (ShowFtpFileAndDirectory() == true) { btnlogin.Enabled = false; btnlogout.Enabled = true; lstbxFtpResources.Enabled = true; lstbxFtpState.Enabled = true; tbxServerIp.Enabled = false; if (chkbxAnonymous.Checked == false) { tbxUsername.Enabled = false; tbxPassword.Enabled = false; chkbxAnonymous.Enabled = false; } else { chkbxAnonymous.Enabled = false; } tbxloginmessage.Text = "登录成功"; btnUpload.Enabled = true; btndownload.Enabled = true; btnDelete.Enabled = true; } else { lstbxFtpState.Enabled = true; tbxloginmessage.Text = "登录失败"; } } // 显示资源列表 private bool ShowFtpFileAndDirectory() { lstbxFtpResources.Items.Clear(); string uri = string.Empty; if (currentDir == "/") { uri = ftpUristring; } else { uri = ftpUristring + currentDir; } string[] urifield = uri.Split(' '); uri = urifield[0]; FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.ListDirectoryDetails); // 获得服务器返回的响应信息 FtpWebResponse response = GetFtpResponse(request); if (response == null) { return false; } lstbxFtpState.Items.Add("连接成功,服务器返回的是:"+response.StatusCode+" "+response.StatusDescription); // 读取网络流数据 Stream stream = response.GetResponseStream(); StreamReader streamReader = new StreamReader(stream,Encoding.Default); lstbxFtpState.Items.Add("获取响应流...."); string s = streamReader.ReadToEnd(); streamReader.Close(); stream.Close(); response.Close(); lstbxFtpState.Items.Add("传输完成"); // 处理并显示文件目录列表 string[] ftpdir = s.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); lstbxFtpResources.Items.Add("↑返回上层目录"); int length = 0; for (int i = 0; i < ftpdir.Length; i++) { if (ftpdir[i].EndsWith(".")) { length = ftpdir[i].Length - 2; break; } } for (int i = 0; i < ftpdir.Length; i++) { s = ftpdir[i]; int index = s.LastIndexOf('\t'); if (index == -1) { if (length < s.Length) { index = length; } else { continue; } } string name = s.Substring(index + 1); if (name == "." || name == "..") { continue; } // 判断是否为目录,在名称前加"目录"来表示 if (s[0] == 'd' || (s.ToLower()).Contains("<dir>")) { string[] namefield = name.Split(' '); int namefieldlength = namefield.Length; string dirname; dirname = namefield[namefieldlength - 1]; // 对齐 dirname = dirname.PadRight(34,' '); name = dirname; // 显示目录 lstbxFtpResources.Items.Add("[目录]" + name); } } for (int i = 0; i < ftpdir.Length; i++) { s = ftpdir[i]; int index = s.LastIndexOf('\t'); if (index == -1) { if (length < s.Length) { index = length; } else { continue; } } string name = s.Substring(index + 1); if (name == "." || name == "..") { continue; } // 判断是否为文件 if (!(s[0] == 'd' || (s.ToLower()).Contains("<dir>"))) { string[] namefield = name.Split(' '); int namefieldlength = namefield.Length; string filename; filename = namefield[namefieldlength - 1]; // 对齐 filename = filename.PadRight(34, ' '); name = filename; // 显示文件 lstbxFtpResources.Items.Add(name); } } return true; } // 注销事件 private void btnlogout_Click(object sender, EventArgs e) { btnlogin.Enabled = true; btnlogout.Enabled = false; tbxServerIp.Enabled = true; tbxServerIp.SelectAll(); tbxServerIp.Focus(); chkbxAnonymous.Enabled = true; if (chkbxAnonymous.Checked == false) { tbxUsername.Enabled = true; tbxPassword.Enabled = true; } tbxloginmessage.Text = "你已经退出了。"; lstbxFtpResources.Items.Clear(); lstbxFtpResources.Enabled = false; lstbxFtpState.Items.Clear(); lstbxFtpState.Enabled = false; btnUpload.Enabled = false; btndownload.Enabled = false; btnDelete.Enabled = false; } #endregion
对FTP服务器操作模块(本程序中实现下载、上传和删除的功能):
#region 对文件的操作模块实现 // 上传文件到服务器事件 private void btnUpload_Click(object sender, EventArgs e) { // 选择要上传的文件 OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.FileName = openFileDialog.FileNames.ToString(); openFileDialog.Filter = "所有文件(*.*)|*.*"; if (openFileDialog.ShowDialog() != DialogResult.OK) { return; } FileInfo fileinfo = new FileInfo(openFileDialog.FileName); try { string uri = GetUriString(fileinfo.Name); FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.UploadFile); request.ContentLength = fileinfo.Length; int buflength = 8196; byte[] buffer = new byte[buflength]; FileStream filestream = fileinfo.OpenRead(); Stream responseStream = request.GetRequestStream(); lstbxFtpState.Items.Add("打开上传流,文件上传中..."); int contenlength = filestream.Read(buffer, 0, buflength); while (contenlength != 0) { responseStream.Write(buffer, 0, contenlength); contenlength = filestream.Read(buffer, 0, buflength); } responseStream.Close(); filestream.Close(); FtpWebResponse response = GetFtpResponse(request); if (response == null) { lstbxFtpState.Items.Add("服务器未响应..."); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; return; } lstbxFtpState.Items.Add("上传完毕,服务器返回:" + response.StatusCode + " " + response.StatusDescription); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show("上传成功!"); // 上传成功后,立即刷新服务器目录列表 ShowFtpFileAndDirectory(); } catch (WebException ex) { lstbxFtpState.Items.Add("上传发生错误,返回信息为:" + ex.Status); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show(ex.Message, "上传失败"); } } private string GetUriString(string filename) { string uri = string.Empty; if (currentDir.EndsWith("/")) { uri = ftpUristring + currentDir + filename; } else { uri = ftpUristring + currentDir + "/" + filename; } return uri; } // 从服务器上下载文件到本地事件 private void btndownload_Click(object sender, EventArgs e) { string fileName = GetSelectedFile(); if (fileName.Length == 0) { MessageBox.Show("请选择要下载的文件!","提示"); return; } // 选择保存文件的位置 SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.FileName = fileName; saveFileDialog.Filter = "所有文件(*.*)|(*.*)"; if (saveFileDialog.ShowDialog() != DialogResult.OK) { return; } string filePath = saveFileDialog.FileName; try { string uri = GetUriString(fileName); FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.DownloadFile); FtpWebResponse response = GetFtpResponse(request); if (response == null) { lstbxFtpState.Items.Add("服务器未响应..."); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; return; } Stream responseStream = response.GetResponseStream(); FileStream filestream = File.Create(filePath); int buflength = 8196; byte[] buffer = new byte[buflength]; int bytesRead =1; lstbxFtpState.Items.Add("打开下载通道,文件下载中..."); while (bytesRead != 0) { bytesRead = responseStream.Read(buffer, 0, buflength); filestream.Write(buffer, 0, bytesRead); } responseStream.Close(); filestream.Close(); lstbxFtpState.Items.Add("下载完毕,服务器返回:" + response.StatusCode + " " + response.StatusDescription); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show("下载完成!"); } catch (WebException ex) { lstbxFtpState.Items.Add("发生错误,返回状态为:" + ex.Status); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show(ex.Message, "下载失败"); } } // 获得选择的文件 // 如果选择的是目录或者是返回上层目录,则返回null private string GetSelectedFile() { string filename = string.Empty; if (!(lstbxFtpResources.SelectedIndex == -1 || lstbxFtpResources.SelectedItem.ToString().Substring(0, 4) == "[目录]")) { string[] namefield = lstbxFtpResources.SelectedItem.ToString().Split(' '); filename = namefield[0]; } return filename; } // 删除服务器文件事件 private void btnDelete_Click(object sender, EventArgs e) { string filename = GetSelectedFile(); if (filename.Length == 0) { MessageBox.Show("请选择要删除的文件!", "提示"); return; } try { string uri = GetUriString(filename); if (MessageBox.Show("确定要删除文件 " + filename + " 吗?", "确认文件删除", MessageBoxButtons.YesNo) == DialogResult.Yes) { FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.DeleteFile); FtpWebResponse response = GetFtpResponse(request); if (response == null) { lstbxFtpState.Items.Add("服务器未响应..."); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; return; } lstbxFtpState.Items.Add("文件删除成功,服务器返回:" + response.StatusCode + " " + response.StatusDescription); ShowFtpFileAndDirectory(); } else { return; } } catch (WebException ex) { lstbxFtpState.Items.Add("发生错误,返回状态为:" + ex.Status); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show(ex.Message, "删除失败"); } } #endregion
由于程序的演示效果需要结合下一专题介绍的FTP服务器,具体的演示效果大家可以查看——专题十二:实现一个简单的FTP服务器,下面就列出程序的主界面截图:
四、小结
这个专题的介绍就到这里的,在下一个专题将和大家介绍下如何实现一个FTP服务器,这样再加上本专题制作的FTP文件上传下载器就可以形成一个完整的软件套件,自己实现FTP文件上传下载器访问自己实现的FTP服务器将会让大家觉得很很有趣的, 想赶快体验下这样的一种乐趣吗?那就赶快下载本专题的源码来亲身体验下吧。通过希望通过本专题让大家对FTP协议不再陌生,并且做Asp.net开发的朋友,文件的上传和下载是一个公共模块的,然后Asp.net中的文件上传和下载只是通过浏览器向HTTP服务器发送HTTP命令,来告诉HTTP服务器说“我想和你对话”,“我想要你上面的某某文件”以及“我想上传一个文件到你的上面去”等等的对话,这个系列完成之后,我也会和大家总结下网络编程的知识的。
最后提供下源码下载地址:https://files.cnblogs.com/zhili/FTPUpDownloader.zip