我正在尝试使用 C#
中的 HttpListener
创建一个简单的 Web 服务器并提供文件下载。我发现传输速率非常差,尤其是与从共享复制相同文件相比。 HttpListener
知道这一点吗?可以采取哪些措施来改进它?
以下是我针对该问题所做的研究的一些附加信息。本地连接时下载速率会提高很多,但在这种情况下复制文件几乎是立即完成的,因此很难测量差异率。然而,当远程连接时(LAN
环境,计算机彼此相邻),传输时间大约是从共享复制简单文件的时间的 25 倍。可用的网络带宽似乎并未用于加快速度。
我发现了一些关于 HttpListener
的其他问题和讨论,它们似乎表明了类似的问题,请参见此处:
HttpListener vs native performance
HttpListener Performance Optimization (但这与下载无关)
MSDN docs还声明 HttpListener
基于 http.sys
,它允许带宽限制。难道这里发生了一些不需要的带宽限制或者我的代码有问题吗?在我测试过的计算机(Windows 7 和 Windows 2008 R2)上,不存在 IIS。
在我的示例中,我正在启动一个 HttpListener
,如下所示:
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:80/");
listener.Start();
这是我的简单文件下载的代码:
HttpListenerResponse response = null;
try {
HttpListenerContext context = listener.GetContext();
response = context.Response;
using( FileStream fs = File.OpenRead( @"c:\downloadsample\testfile.pdf" ) ) {
byte[] buffer = new byte[ 32768 ];
int read;
while( ( read = fs.Read( buffer, 0, buffer.Length ) ) > 0 ) {
response.OutputStream.Write( buffer, 0, read );
}
}
} finally {
if( response != null )
response.Close();
}
(编辑:修复了一些链接...)
最佳答案
总体
运行的两个测试(提供文件的 C# HttpListener 和 smb 文件复制测试)包含太多变量,无法就 HttpListener 与 native 代码的性能得出任何有用的结论。
在这种情况下,所有其他代码都应该被怀疑导致性能问题,并且应该从测试用例中删除。
不幸的是,问题的文件服务实现不是最佳的,因为它从文件中读取一个 block 到托管字节数组中,然后阻止将该 block 写入内核的调用。它将文件的字节复制到托管数组中,然后从托管数组中复制出来(在此过程中不添加任何值)。使用 .Net 4.5,您可以在文件流和输出流之间调用 CopyToAsync,这将使您摆脱弄清楚如何并行执行此操作的麻烦。
结论
下面的测试表明,HttpListener 发送回字节的速度与 IIS Express 8.0 返回文件的速度一样快。在这篇文章中,我在虚拟机上的服务器的 802.11n 网络上对此进行了测试,并且使用 HttpListener 和 IIS Express 仍然达到了 100+ Mbps。
原始帖子中唯一需要更改的是它如何读取文件并将其转发回客户端。
如果您想通过 HTTP 提供文件服务,您可能应该使用现有的 Web 服务器来处理 HTTP 方面的事务以及文件打开/缓存/中继。您会发现很难击败现有的 Web 服务器,特别是当您将 gzip 动态响应引入图片中时(在这种情况下,幼稚的方法会在发送任何响应之前意外地 gzip 整个响应,这浪费了本可以用于发送字节)。
更好的测试来隔离 HttpListener 的性能
我创建了一个返回 10 MB 整数字符串(启动时生成一次)的测试,以允许测试 HttpListener 在预先给出整个 block 时返回数据的速度(这类似于它在您使用 CopyToAsync)。
测试设置
客户端计算机:MacBook Air 2013 年中,1.7 GHz Core i7 服务器计算机:iMac 2011 年中,3.4 GHz Core i7 - Windows 8.1,托管于 VMWare Fusion 6.0,桥接网络 网络:802.11n,通过 Airport Extreme(位于 8 英尺外) 下载客户端:Mac OS X 上的curl
测试结果
IIS Express 8.0 配置为提供 18 MB 文件,HttpListenerSpeed 程序设置为返回 10 MB 和 100 MB 响应。测试结果基本一致。
IIS Express 8.0 结果
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8000/TortoiseSVN-1.8.2.24708-x64-svn-1.8.3.msi > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 18.4M 100 18.4M 0 0 13.1M 0 0:00:01 0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8000/TortoiseSVN-1.8.2.24708-x64-svn-1.8.3.msi > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 18.4M 100 18.4M 0 0 13.0M 0 0:00:01 0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8000/TortoiseSVN-1.8.2.24708-x64-svn-1.8.3.msi > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 18.4M 100 18.4M 0 0 9688k 0 0:00:01 0:00:01 --:--:-- 9737k
HttpListenerSpeed 结果
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8080/garbage > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 18.4M 100 18.4M 0 0 12.6M 0 0:00:01 0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8080/garbage > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 18.4M 100 18.4M 0 0 13.1M 0 0:00:01 0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8080/garbage > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 18.4M 100 18.4M 0 0 13.2M 0 0:00:01 0:00:01 --:--:-- 13.2M
HttpListenerSpeed 代码
using System;
using System.Threading.Tasks;
using System.Net;
using System.Threading;
namespace HttpListenerSpeed
{
class Program
{
static void Main(string[] args)
{
var listener = new Listener();
Console.WriteLine("Press Enter to exit");
Console.ReadLine();
listener.Shutdown();
}
}
internal class Listener
{
private const int RequestDispatchThreadCount = 4;
private readonly HttpListener _httpListener = new HttpListener();
private readonly Thread[] _requestThreads;
private readonly byte[] _garbage;
internal Listener()
{
_garbage = CreateGarbage();
_httpListener.Prefixes.Add("http://*:8080/");
_httpListener.Start();
_requestThreads = new Thread[RequestDispatchThreadCount];
for (int i = 0; i < _requestThreads.Length; i++)
{
_requestThreads[i] = new Thread(RequestDispatchThread);
_requestThreads[i].Start();
}
}
private static byte[] CreateGarbage()
{
int[] numbers = new int[2150000];
for (int i = 0; i < numbers.Length; i++)
{
numbers[i] = 1000000 + i;
}
Shuffle(numbers);
return System.Text.Encoding.UTF8.GetBytes(string.Join<int>(", ", numbers));
}
private static void Shuffle<T>(T[] array)
{
Random random = new Random();
for (int i = array.Length; i > 1; i--)
{
// Pick random element to swap.
int j = random.Next(i); // 0 <= j <= i-1
// Swap.
T tmp = array[j];
array[j] = array[i - 1];
array[i - 1] = tmp;
}
}
private void RequestDispatchThread()
{
while (_httpListener.IsListening)
{
string url = string.Empty;
try
{
// Yeah, this blocks, but that's the whole point of this thread
// Note: the number of threads that are dispatching requets in no way limits the number of "open" requests that we can have
var context = _httpListener.GetContext();
// For this demo we only support GET
if (context.Request.HttpMethod != "GET")
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
}
// Don't care what the URL is... you're getting a bunch of garbage, and you better like it!
context.Response.StatusCode = (int)HttpStatusCode.OK;
context.Response.ContentLength64 = _garbage.Length;
context.Response.OutputStream.BeginWrite(_garbage, 0, _garbage.Length, result =>
{
context.Response.OutputStream.EndWrite(result);
context.Response.Close();
}, context);
}
catch (System.Net.HttpListenerException e)
{
// Bail out - this happens on shutdown
return;
}
catch (Exception e)
{
Console.WriteLine("Unexpected exception: {0}", e.Message);
}
}
}
internal void Shutdown()
{
if (!_httpListener.IsListening)
{
return;
}
// Stop the listener
_httpListener.Stop();
// Wait for all the request threads to stop
for (int i = 0; i < _requestThreads.Length; i++)
{
var thread = _requestThreads[i];
if (thread != null) thread.Join();
_requestThreads[i] = null;
}
}
}
}
关于c# - 使用 HttpListener 提供文件下载时性能不佳,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10485985/