Обслуживание больших файлов с помощью С# HttpListener

Я пытаюсь использовать HttpListener для обслуживания статических файлов, и это хорошо работает с небольшими файлами. Когда размер файлов увеличивается (тестируется с файлами 350 и 600 МБ), сервер задыхается с одним из следующих исключений:

HttpListenerException: операция ввода-вывода была прервана из-за выхода потока или запроса приложения или:
HttpListenerException: период таймаута семафора истек.

Что нужно изменить, чтобы избавиться от исключений и позволить ему работать стабильно/надежно (и быстро)?

Вот некоторая дальнейшая разработка: это в основном следующий вопрос этого более раннего вопроса. Код слегка расширен, чтобы показать эффект. Запись контента находится в цикле с (мы надеемся, разумными) размерами блоков, в моем случае 64 КБ, но изменение значения не помогло, кроме скорости (см. Упомянутый более старый вопрос).


using( FileStream fs = File.OpenRead( @"C:\test\largefile.exe" ) ) {

    //response is HttpListenerContext.Response...
    response.ContentLength64 = fs.Length;
    response.SendChunked = false;
    response.ContentType = System.Net.Mime.MediaTypeNames.Application.Octet;
    response.AddHeader( "Content-disposition", "attachment; filename=largefile.EXE" );

    byte[] buffer = new byte[ 64 * 1024 ];
    int read;
    using( BinaryWriter bw = new BinaryWriter( response.OutputStream ) ) {
        while( ( read = fs.Read( buffer, 0, buffer.Length ) ) > 0 ) {
            Thread.Sleep( 200 ); //take this out and it will not run
            bw.Write( buffer, 0, read );
            bw.Flush(); //seems to have no effect
        }

        bw.Close();
    }

    response.StatusCode = ( int )HttpStatusCode.OK;
    response.StatusDescription = "OK";
    response.OutputStream.Close();
}

Я пытаюсь загрузить в браузере, а также в программе С# с использованием HttpWebRequest, это не имеет никакого значения.

Основываясь на моих исследованиях, я полагаю, что HttpListener на самом деле не способен очищать содержимое до клиента или, по крайней мере, делает это в своем собственном темпе. Я также оставил BinaryWriter и написал прямо в поток - никакой разницы. Ввел BufferedStream вокруг базового потока - никакой разницы. Забавно, если в цикле введен поток Thread.Sleep(200) или немного больше, он работает на моем ящике. Однако я сомневаюсь, что он достаточно стабилен для реального решения. Этот вопрос создает впечатление, что нет никакого шанса, чтобы он работал правильно (помимо перехода на IIS/ASP.NET, к которому я бы прибегал, но, скорее всего, остался в стороне если возможно).

Ответ 1

Вы не показали нам другую критическую часть, как вы инициализировали HttpListener. Поэтому я попробовал ваш код с приведенным ниже и работал

HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8080/");
listener.Start();
Task.Factory.StartNew(() =>
{
    while (true)
    {
        HttpListenerContext context = listener.GetContext();
        Task.Factory.StartNew((ctx) =>
        {
            WriteFile((HttpListenerContext)ctx, @"C:\LargeFile.zip");
        }, context,TaskCreationOptions.LongRunning);
    }
},TaskCreationOptions.LongRunning);

WriteFile - это ваш код, в котором Thread.Sleep( 200 ); удален.

Если вы хотите увидеть его полный код.


void WriteFile(HttpListenerContext ctx, string path)
{
    var response = ctx.Response;
    using (FileStream fs = File.OpenRead(path))
    {
        string filename = Path.GetFileName(path);
        //response is HttpListenerContext.Response...
        response.ContentLength64 = fs.Length;
        response.SendChunked = false;
        response.ContentType = System.Net.Mime.MediaTypeNames.Application.Octet;
        response.AddHeader("Content-disposition", "attachment; filename=" + filename);

        byte[] buffer = new byte[64 * 1024];
        int read;
        using (BinaryWriter bw = new BinaryWriter(response.OutputStream))
        {
            while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
            {
                bw.Write(buffer, 0, read);
                bw.Flush(); //seems to have no effect
            }

            bw.Close();
        }

        response.StatusCode = (int)HttpStatusCode.OK;
        response.StatusDescription = "OK";
        response.OutputStream.Close();
    }
}