视觉项目在同时产生批量图片后,客户要求这些图片存底和追溯。以前提供的网盘存图无法满足最新现场的要求,需要增加ftp上传模式。
先看上传的核心代码:
List str = new List();
str.Add(path);
str.Add(DateTime.Now.ToString("yyyyMMdd"));
if (!string.IsNullOrWhiteSpace(station)) { str.Add(station); }
if (!string.IsNullOrWhiteSpace(info)) { str.Add(info); }
path = path + "\\" + DateTime.Now.ToString("yyyyMMdd") + "\\" + station;
string pathAfter = path + "\\" + info;
FtpWebRequest reqFTP;
pathAfter = pathAfter.Replace('\\', '/');
var ftppath = str[0];
if (str != null && str.Count > 0)
{
for (int i = 0; i < str.Count; i++)
{
if (!string.IsNullOrWhiteSpace(str[i]))
{
if (!DirectoryExists(ftppath, userName, pwd))
{
reqFTP = WebRequest.Create(new Uri(ftppath)) as FtpWebRequest;
reqFTP.Credentials = new NetworkCredential(userName, pwd);
reqFTP.Method = WebRequestMethods.Ftp.MakeDirectory;
reqFTP.UseBinary = true;
reqFTP.UsePassive = true;
FtpWebResponse response = (FtpWebResponse)reqFTP.GetResponse();
response.Close();
reqFTP.Abort();
}
if (i + 1 < str.Count)
{
ftppath = ftppath + '/' + str[i + 1];
}
}
}
}
string strImageName = pathAfter + "\\" + imageName + "_" + DateTime.Now.ToString("HH_mm_ss_ffff") + strImageTypes[(int)imageType];
byte[] buffer = imageBuffer;
...
FtpWebRequest request;
strImageName = strImageName.Replace('\\', '/');
request = WebRequest.Create(new Uri(strImageName)) as FtpWebRequest;
request.Method = WebRequestMethods.Ftp.UploadFile;
request.UseBinary = true;
request.UsePassive = true;
request.Credentials = new NetworkCredential(userName, pwd);
using (MemoryStream ms = new MemoryStream())
{
Stream requestStream = request.GetRequestStream();
requestStream.Write(buffer, 0, buffer.Length);
requestStream.Flush();
requestStream.Close();
}
上传图片前需验证当前路径,如果不存在,要创建路径后再写入流文件。验证路径有两种方式:
WebRequestMethods.Ftp.ListDirectory类显示路径清单后和当前路径做比较。
先用WebRequestMethods.Ftp.UploadFile上传测试文件,再WebRequestMethods.Ftp.DeleteFile删除文件。整个操作具有原子性,失败即不存在路径。
我使用的第二种方式,本地测试过程是没有问题的。但没有考虑到现场复杂性,整个流程单次进行能正常运行,流程一个接一个时,这里就报错,导致图片上传老是漏几张。
错误提示:"系统错误"。
Ftp通过网络传输,用流文件的方式上传,特别在验证路径时,通过拆分每一级目录,对每级目录上传和删除的操作,增加网络和IO的消耗,操作FTP服务器频率高导致的偶尔失败。
解决方案:
重试机制
第一次读写FTP失败后,在catch里再次操作。最简单并且能马上解决问题的办法是封装上方循环目录的部分代码,在try和catch里分别调用一次,可减少该问题发生的概率。
异步
msdn代码地址
详情如下:
using System;
using System.Net;
using System.Threading;
using System.IO;
namespace Examples.System.Net
{
public class FtpState
{
private ManualResetEvent wait;
private FtpWebRequest request;
private string fileName;
private Exception operationException = null;
string status;
public FtpState()
{
wait = new ManualResetEvent(false);
}
public ManualResetEvent OperationComplete
{
get {return wait;}
}
public FtpWebRequest Request
{
get {return request;}
set {request = value;}
}
public string FileName
{
get {return fileName;}
set {fileName = value;}
}
public Exception OperationException
{
get {return operationException;}
set {operationException = value;}
}
public string StatusDescription
{
get {return status;}
set {status = value;}
}
}
public class AsynchronousFtpUpLoader
{
// Command line arguments are two strings:
// 1. The url that is the name of the file being uploaded to the server.
// 2. The name of the file on the local machine.
//
public static void Main(string[] args)
{
// Create a Uri instance with the specified URI string.
// If the URI is not correctly formed, the Uri constructor
// will throw an exception.
ManualResetEvent waitObject;
Uri target = new Uri (args[0]);
string fileName = args[1];
FtpState state = new FtpState();
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(target);
request.Method = WebRequestMethods.Ftp.UploadFile;
// This example uses anonymous logon.
// The request is anonymous by default; the credential does not have to be specified.
// The example specifies the credential only to
// control how actions are logged on the server.
request.Credentials = new NetworkCredential ("anonymous","[email protected]");
// Store the request in the object that we pass into the
// asynchronous operations.
state.Request = request;
state.FileName = fileName;
// Get the event to wait on.
waitObject = state.OperationComplete;
// Asynchronously get the stream for the file contents.
request.BeginGetRequestStream(
new AsyncCallback (EndGetStreamCallback),
state
);
// Block the current thread until all operations are complete.
waitObject.WaitOne();
// The operations either completed or threw an exception.
if (state.OperationException != null)
{
throw state.OperationException;
}
else
{
Console.WriteLine("The operation completed - {0}", state.StatusDescription);
}
}
private static void EndGetStreamCallback(IAsyncResult ar)
{
FtpState state = (FtpState) ar.AsyncState;
Stream requestStream = null;
// End the asynchronous call to get the request stream.
try
{
requestStream = state.Request.EndGetRequestStream(ar);
// Copy the file contents to the request stream.
const int bufferLength = 2048;
byte[] buffer = new byte[bufferLength];
int count = 0;
int readBytes = 0;
FileStream stream = File.OpenRead(state.FileName);
do
{
readBytes = stream.Read(buffer, 0, bufferLength);
requestStream.Write(buffer, 0, readBytes);
count += readBytes;
}
while (readBytes != 0);
Console.WriteLine ("Writing {0} bytes to the stream.", count);
// IMPORTANT: Close the request stream before sending the request.
requestStream.Close();
// Asynchronously get the response to the upload request.
state.Request.BeginGetResponse(
new AsyncCallback (EndGetResponseCallback),
state
);
}
// Return exceptions to the main application thread.
catch (Exception e)
{
Console.WriteLine("Could not get the request stream.");
state.OperationException = e;
state.OperationComplete.Set();
return;
}
}
// The EndGetResponseCallback method
// completes a call to BeginGetResponse.
private static void EndGetResponseCallback(IAsyncResult ar)
{
FtpState state = (FtpState) ar.AsyncState;
FtpWebResponse response = null;
try
{
response = (FtpWebResponse) state.Request.EndGetResponse(ar);
response.Close();
state.StatusDescription = response.StatusDescription;
// Signal the main application thread that
// the operation is complete.
state.OperationComplete.Set();
}
// Return exceptions to the main application thread.
catch (Exception e)
{
Console.WriteLine ("Error getting response.");
state.OperationException = e;
state.OperationComplete.Set();
}
}
}
}
总结如下:
先研究技术,在msdn官网查询Ftp有关类的操作。官方其实不建议FtpWebRequest用于新开发,更建议用HttpClient。
充分考虑场景,本地测试没有问题,现场使用时总有意想不到的bug出现。下次在做一个新的功能时,需要模拟现场环境后,测试通过才是第一步。