5

I'm trying to transfer a file over a IHttpHandler, the code is pretty simple. However when i start a single transfer it uses about 20% of the CPU. If i were to scale this to 20 simultaneous transfers the CPU is very high. Is there a better way I can be doing this to keep the CPU lower? the client code just sends over chunks of the file 64KB at a time.

public void ProcessRequest(HttpContext context)
{
      if (context.Request.Params["secretKey"] == null)
      {

      }
      else
      {
           accessCode = context.Request.Params["secretKey"].ToString();
      }

      if (accessCode == "test")
      {
           string fileName = context.Request.Params["fileName"].ToString();
           byte[] buffer = Convert.FromBase64String(context.Request.Form["data"]);
           string fileGuid = context.Request.Params["smGuid"].ToString();
           string user = context.Request.Params["user"].ToString();

           SaveFile(fileName, buffer, user);
      }
}

public void SaveFile(string fileName, byte[] buffer, string user)
{
      string DirPath = @"E:\Filestorage\" + user + @"\";

      if (!Directory.Exists(DirPath))
      {
          Directory.CreateDirectory(DirPath);
      }

      string FilePath = @"E:\Filestorage\" + user + @"\" + fileName;
      FileStream writer = new FileStream(FilePath, File.Exists(FilePath) ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
      writer.Write(buffer, 0, buffer.Length);
      writer.Close();
}

Here is my client code:

//Set filename from object
                string FileName;
                FileName = System.IO.Path.GetFileName(pubAttFullPath.ToString());

                //Open file
                string file = System.IO.Path.GetFileName(pubAttFullPath.ToString());
                FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
                //Chunk size that will be sent to Server
                int chunkSize = 65536;
                // Unique file name
                string fileName = smGuid.ToString() + "_" + FileName;
                int totalChunks = (int)Math.Ceiling((double)fileStream.Length / chunkSize);
                // Loop through the whole stream and send it chunk by chunk;
                for (int i = 0; i < totalChunks; i++)
                {
                    bool doRecieve = true;
                    int cpt = 0;
                    do
                    {
                        int startIndex = i * chunkSize;
                        int endIndex = (int)(startIndex + chunkSize > fileStream.Length ? fileStream.Length : startIndex + chunkSize);
                        int length = endIndex - startIndex;

                        byte[] bytes = new byte[length];
                        fileStream.Read(bytes, 0, bytes.Length);


                        //Request url, Method=post Length and data.
                        string requestURL = "http://localhost:16935/Transfer.doit";
                        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestURL);
                        // Wait 5 min for answer before close connection.
                        request.Timeout = 300000;
                        request.Method = "POST";
                        request.ContentType = "application/x-www-form-urlencoded";

                        // Chunk(buffer) is converted to Base64 string that will be convert to Bytes on  the handler.
                        string requestParameters = @"fileName=" + fileName + @"&secretKey=test" + @"&currentChunk=" + i + @"&totalChunks=" + totalChunks + @"&smGuid=" + smGuid 
                        + "&user=" + userSID.ToString() +
                        "&data=" +  HttpUtility.UrlEncode(Convert.ToBase64String(bytes));

                        // finally whole request will be converted to bytes that will be transferred to HttpHandler
                        byte[] byteData = Encoding.UTF8.GetBytes(requestParameters);

                        request.ContentLength = byteData.Length;
                        try
                        {
                            Stream writer = request.GetRequestStream();
                            writer.Write(byteData, 0, byteData.Length);
                            writer.Close();
                            // here we will receive the response from HttpHandler
                            StreamReader stIn = new StreamReader(request.GetResponse().GetResponseStream());
                            string strResponse = stIn.ReadToEnd();
                            stIn.Close();
                            doRecieve = true;
                        }
                        catch (WebException webException)
                        {
                            if (webException.Status == WebExceptionStatus.ConnectFailure ||
                                webException.Status == WebExceptionStatus.ConnectionClosed ||
                                webException.Status == WebExceptionStatus.ReceiveFailure ||
                                webException.Status == WebExceptionStatus.SendFailure ||
                                webException.Status == WebExceptionStatus.Timeout)
                            {
                                Thread.Sleep(5000);
                                doRecieve = false;
                                cpt++;
                            }
                            else {
                                // if the exception is not those ones then get out
                                doRecieve = true;
                            }
                        }
                        catch (Exception e)
                        {
                            doRecieve = true;
                        }
                    }
                    // will try to send 3 times the current chunk before quitting
                    // can't try it so try it and give me the feedback
                    while(doRecieve == false && cpt < 3);
                 }
user229044
  • 232,980
  • 40
  • 330
  • 338
Dan C.
  • 559
  • 3
  • 8
  • 26
  • What are you basing your CPU usage statistics on? – CodingGorilla Jun 18 '12 at 20:42
  • Starting a transfer and watching perfmon. I'm the only one using it. – Dan C. Jun 18 '12 at 20:43
  • Is this on a development machine or an actual server? – CodingGorilla Jun 18 '12 at 20:44
  • Its on a local server, it is a Dell R720xd dual 8-core 128GB RAM server. Obviously running vmware, and the web server is 2x vCPU and 4GB RAM. – Dan C. Jun 18 '12 at 20:45
  • Dispose of your `IDisposable` objects and use the `using` statement. This is not a CPU usage problem, but I think it should be noted. – Ed S. Jun 18 '12 at 20:49
  • 1
    If I understand correctly, you have an incoming requests with 64kb chunks at a time (context.Request.Form["data"];), and you submit a bunch of request with the data, appending to the storage file by closing and re-opening the file using FileMode.Append each request until the file is assembled? – Jf Beaulac Jun 18 '12 at 20:49
  • That's a whole lot of small disk writes at a high frequency. You should cache the data until the entire message has been received and then write it out. – Ed S. Jun 18 '12 at 20:51
  • Ed - any chance you can show an example of that? Jf Beaulac - Yes that is correct. There is logic on the client side to re-send a chunk if the server cannot connect or times out momentarily too but yes that's whats going on. – Dan C. Jun 18 '12 at 20:52
  • Ed - Cache in memory then? Some file transfers are 2+GB, i'd assume i'd be trading a cpu problem for a memory one. – Dan C. Jun 18 '12 at 20:53
  • I also tried upping the chunk size from 64KB to 3MB per chunk, the CPU still floats between 15-22% for one transfer. – Dan C. Jun 18 '12 at 21:03
  • Have you tried profiling your code? Red Gate ANTS performance profiler is awesome and has a free trial: http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/index-2 – Ian Newson Jun 18 '12 at 21:09
  • 1
    You don't need to check if the file exists, FileMode.Append will create the file if it doesn't exist: http://msdn.microsoft.com/en-us/library/system.io.filemode – Andrew Morton Jun 18 '12 at 21:14
  • You may be writing in chunks but it appears you are reading in one chunk the size of the file. – paparazzo Jun 18 '12 at 21:41
  • Blam - yes, on the client side it sends each chunk one request at a time and closes the https connection. – Dan C. Jun 18 '12 at 21:48

1 Answers1

1

I haven't tested this theory, but working with FromBase64String may be the cause. I found this case where someone was running out of memory using this method.

You might try FromBase64Transform instead, which is designed to handle a stream of data.


Or if you don't need to use base64 for any reason, check out this solution from Scott Hanselman.

Steve Wortham
  • 21,740
  • 5
  • 68
  • 90
  • Thats interesting, i'll give that a shot. Question though, i'm no base64 expert or anything, but since my transport is SSL, do i even need to encode the byte array in base64? – Dan C. Jun 18 '12 at 21:40
  • I didn't know why you were using base64, actually. But to answer your question, no you won't need to do base64 decoding for a file transferred over SSL. Check this out for a solution (assuming you're using multipart/form-data)... http://www.hanselman.com/blog/ABackToBasicsCaseStudyImplementingHTTPFileUploadWithASPNETMVCIncludingTestsAndMocks.aspx – Steve Wortham Jun 18 '12 at 21:51
  • I tried using this method of converting the byte array to a string (http://stackoverflow.com/questions/472906/net-string-to-byte-array-c-sharp) and decoding it to a byte array on the http handler and it still used 15% cpu (and corrupted the file)... not really sure how else i can transport the byte array in the parameters. – Dan C. Jun 18 '12 at 22:28
  • I added my client code to the original post.. im kind of stumped how to not use base64.. any advise? – Dan C. Jun 18 '12 at 23:56
  • @DanC - Have you looked at the WebClient methods? They're a high-level abstraction and could really simplify all of this for you. Here's the UploadFile() method you could use in your client code... http://msdn.microsoft.com/en-us/library/36s52zhs.aspx – Steve Wortham Jun 19 '12 at 01:13