我正在使用 PushStreamContent 来保持与每个客户端的持久连接。每 20 秒向每个客户端流推送短心跳消息对于 100 个客户端来说效果很好,但在大约 200 个客户端时,客户端首先开始延迟几秒钟接收,然后根本不显示。
我的控制器代码是
// Based loosely on https://aspnetwebstack.codeplex.com/discussions/359056
// and http://blogs.msdn.com/b/henrikn/archive/2012/04/23/using-cookies-with-asp-net-web-api.aspx
public class LiveController : ApiController
{
public HttpResponseMessage Get(HttpRequestMessage request)
{
if (_timer == null)
{
// 20 second timer
_timer = new Timer(TimerCallback, this, 20000, 20000);
}
// Get '?clientid=xxx'
HttpResponseMessage response = request.CreateResponse();
var kvp = request.GetQueryNameValuePairs().Where(q => q.Key.ToLower() == "clientid").FirstOrDefault();
string clientId = kvp.Value;
HttpContext.Current.Response.ClientDisconnectedToken.Register(
delegate(object obj)
{
// Client has cleanly disconnected
var disconnectedClientId = (string)obj;
CloseStreamFor(disconnectedClientId);
}
, clientId);
response.Content = new PushStreamContent(
delegate(Stream stream, HttpContent content, TransportContext context)
{
SaveStreamFor(clientId, stream);
}
, "text/event-stream");
return response;
}
private static void CloseStreamFor(string clientId)
{
Stream oldStream;
_streams.TryRemove(clientId, out oldStream);
if (oldStream != null)
oldStream.Close();
}
private static void SaveStreamFor(string clientId, Stream stream)
{
_streams.TryAdd(clientId, stream);
}
private static void TimerCallback(object obj)
{
DateTime start = DateTime.Now;
// Disable timer
_timer.Change(Timeout.Infinite, Timeout.Infinite);
// Every 20 seconds, send a heartbeat to each client
var recipients = _streams.ToArray();
foreach (var kvp in recipients)
{
string clientId = kvp.Key;
var stream = kvp.Value;
try
{
// ***
// Adding this Trace statement and running in debugger caused
// heartbeats to be reliably flushed!
// ***
Trace.WriteLine(string.Format("** {0}: Timercallback: {1}", DateTime.Now.ToString("G"), clientId));
WriteHeartBeat(stream);
}
catch (Exception ex)
{
CloseStreamFor(clientId);
}
}
// Trace... (this trace statement had no effect)
_timer.Change(20000, 20000); // re-enable timer
}
private static void WriteHeartBeat(Stream stream)
{
WriteStream(stream, "event:heartbeat\ndata:-\n\n");
}
private static void WriteStream(Stream stream, string data)
{
byte[] arr = Encoding.ASCII.GetBytes(data);
stream.Write(arr, 0, arr.Length);
stream.Flush();
}
private static readonly ConcurrentDictionary<string, Stream> _streams = new ConcurrentDictionary<string, Stream>();
private static Timer _timer;
}
是否有某些 ASP.NET 或 IIS 设置会影响此问题?我在 Windows Server 2008 R2 上运行。
更新:
如果 1) 添加了 Trace.WriteLine 语句,2) 连接了 Visual Studio 2013 调试器并调试和捕获 Trace.WriteLines,则可以可靠地发送心跳。
这两点都是必要的;如果删除 Trace.WriteLine,则在调试器下运行没有任何效果。如果 Trace.WriteLine 存在,但程序未在调试器下运行(而是 SysInternals 的 DbgView 显示跟踪消息),则心跳不可靠。
更新2:
后来与微软发生了两次支持事件,结论如下:
1) 通过使用企业级互联网连接而不是家庭连接解决了 200 个客户端的延迟问题
2)是否附加调试器实际上没有任何区别;
3) 需要在 web.config 中添加以下两个内容,以确保及时发送心跳,并且由于客户端“不干净”地断开连接(例如,通过拔掉计算机而不是正常关闭干净地发出 TCP RST 的程序)而导致心跳失败,会触发及时的 ClientDisconnected回调以及:
<httpRuntime executionTimeout="5" />
<serverRuntime appConcurrentRequestLimit="50000" uploadReadAheadSize="1" frequentHitThreshold="2147483647" />