使用 IServerEvents.NotifyChannel 的缺失事件
Missing events using IServerEvents.NotifyChannel
我正在尝试使用服务器发送事件向 JS 客户端发送消息。客户仅在每 6 或 7 个事件中获得一次。我做错了什么?
可以使用简单的独立示例重现该行为:
using System;
using System.Threading;
using Funq;
using ServiceStack;
namespace ServerSentEvents
{
public class AppHost : AppSelfHostBase
{
/// <summary>
/// Default constructor.
/// Base constructor requires a name and assembly to locate web service classes.
/// </summary>
public AppHost()
: base("ServerSentEvents", typeof(AppHost).Assembly)
{
}
/// <summary>
/// Application specific configuration
/// This method should initialize any IoC resources utilized by your web service classes.
/// </summary>
/// <param name="container"></param>
public override void Configure(Container container)
{
SetConfig(new HostConfig
{
#if DEBUG
DebugMode = true,
WebHostPhysicalPath = "~/../..".MapServerPath(),
#endif
});
container.Register<IServerEvents>(c => new MemoryServerEvents());
Plugins.Add(new ServerEventsFeature
{
OnPublish = (res, msg) =>
{
// Throws an exception
//res.Write("\n\n\n\n\n\n\n\n\n\n"); // Force flush:
//res.Flush();
}
});
container.Register(new FrontendMessages(container.Resolve<IServerEvents>()));
}
}
public class FrontendMessage
{
public string Level { get; set; }
public string Message { get; set; }
}
public class FrontendMessages
{
private readonly IServerEvents _serverEvents;
private Timer _timer;
public FrontendMessages(IServerEvents serverEvents)
{
if (serverEvents == null) throw new ArgumentNullException(nameof(serverEvents));
_serverEvents = serverEvents;
var ticks = 0;
_timer = new Timer(_ => Info($"Tick {ticks++}"), null, 500, 500);
}
public void Info(string message, params object[] parameters)
{
var frontendMessage = new FrontendMessage
{
Level = "success",
Message = message
};
Console.WriteLine("Sending message: " + frontendMessage.Message);
_serverEvents.NotifyChannel("messages", frontendMessage);
}
}
}
和客户:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/jquery-1.11.1.min.js"></script>
<script src="js/ss-utils.js"></script>
</head>
<body>
<script>
// Handle messages
var msgSource = new EventSource('event-stream?channel=messages&t=' + new Date().getTime());
$(msgSource).handleServerEvents({
handlers: {
FrontendMessage: function (msg) {
console.log('Message from server', msg);
}
}
});
</script>
</body>
</html>
控制台日志如下所示:
Message from server Object {Level: "success", Message: "Tick 28"}
Message from server Object {Level: "success", Message: "Tick 35"}
Message from server Object {Level: "success", Message: "Tick 42"}
Message from server Object {Level: "success", Message: "Tick 49"}
问题是您试图在 ServerEventsFeature
注册之前向 IServerEvents
发送消息,因为您是在 AppHost.Configure()
中立即启动它,而不是在 AppHost.Configure()
之后AppHost
已初始化。问题的实际原因是计时器启动时 IdleTimeout
未正确初始化,导致每个服务器事件连接的生命周期为 00:00:00
,这意味着它们会收到一条消息自动处理并再次自动重新连接 - 整个过程大约需要 6-7 个 ticks :)
ServiceStack Plugins aren't registered when they're added, they get registered together after AppHost.Configure()
which gives other plugins a chance to add/remove/inspect other plugins before they're registered. You also don't need to register MemoryServerEvents
since that's the default and the recommended way to initialize a timer with an interval是在定时器回调中使用timer.Change()
。
鉴于此,我会将您的 AppHost 重写为:
public class AppHost : AppSelfHostBase
{
public AppHost()
: base("ServerSentEvents", typeof(AppHost).Assembly) { }
public override void Configure(Container container)
{
SetConfig(new HostConfig {
#if DEBUG
DebugMode = true,
WebHostPhysicalPath = "~/../..".MapServerPath(),
#endif
});
Plugins.Add(new ServerEventsFeature());
container.Register(c => new FrontendMessages(c.Resolve<IServerEvents>()));
}
}
并让您的 FrontendMessages 仅在显式调用 Start()
时启动,即:
public class FrontendMessage
{
public string Level { get; set; }
public string Message { get; set; }
}
public class FrontendMessages
{
private readonly IServerEvents _serverEvents;
private Timer _timer;
public FrontendMessages(IServerEvents serverEvents)
{
if (serverEvents == null) throw new ArgumentNullException(nameof(serverEvents));
_serverEvents = serverEvents;
}
public void Start()
{
var ticks = 0;
_timer = new Timer(_ => {
Info($"Tick {ticks++}");
_timer.Change(500, Timeout.Infinite);
}, null, 500, Timeout.Infinite);
}
public void Info(string message, params object[] parameters)
{
var frontendMessage = new FrontendMessage {
Level = "success",
Message = message
};
Console.WriteLine("Sending message: " + frontendMessage.Message);
_serverEvents.NotifyChannel("messages", frontendMessage);
}
}
然后在AppHost初始化完成后才启动,即:
class Program
{
static void Main(string[] args)
{
var appHost = new AppHost()
.Init()
.Start("http://*:2000/"); //Start AppSelfHost
appHost.Resolve<FrontendMessages>().Start(); //Start timer
Process.Start("http://localhost:2000/"); //View in Web browser
Console.ReadLine(); //Prevent Console App from existing
}
}
我正在尝试使用服务器发送事件向 JS 客户端发送消息。客户仅在每 6 或 7 个事件中获得一次。我做错了什么?
可以使用简单的独立示例重现该行为:
using System;
using System.Threading;
using Funq;
using ServiceStack;
namespace ServerSentEvents
{
public class AppHost : AppSelfHostBase
{
/// <summary>
/// Default constructor.
/// Base constructor requires a name and assembly to locate web service classes.
/// </summary>
public AppHost()
: base("ServerSentEvents", typeof(AppHost).Assembly)
{
}
/// <summary>
/// Application specific configuration
/// This method should initialize any IoC resources utilized by your web service classes.
/// </summary>
/// <param name="container"></param>
public override void Configure(Container container)
{
SetConfig(new HostConfig
{
#if DEBUG
DebugMode = true,
WebHostPhysicalPath = "~/../..".MapServerPath(),
#endif
});
container.Register<IServerEvents>(c => new MemoryServerEvents());
Plugins.Add(new ServerEventsFeature
{
OnPublish = (res, msg) =>
{
// Throws an exception
//res.Write("\n\n\n\n\n\n\n\n\n\n"); // Force flush:
//res.Flush();
}
});
container.Register(new FrontendMessages(container.Resolve<IServerEvents>()));
}
}
public class FrontendMessage
{
public string Level { get; set; }
public string Message { get; set; }
}
public class FrontendMessages
{
private readonly IServerEvents _serverEvents;
private Timer _timer;
public FrontendMessages(IServerEvents serverEvents)
{
if (serverEvents == null) throw new ArgumentNullException(nameof(serverEvents));
_serverEvents = serverEvents;
var ticks = 0;
_timer = new Timer(_ => Info($"Tick {ticks++}"), null, 500, 500);
}
public void Info(string message, params object[] parameters)
{
var frontendMessage = new FrontendMessage
{
Level = "success",
Message = message
};
Console.WriteLine("Sending message: " + frontendMessage.Message);
_serverEvents.NotifyChannel("messages", frontendMessage);
}
}
}
和客户:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/jquery-1.11.1.min.js"></script>
<script src="js/ss-utils.js"></script>
</head>
<body>
<script>
// Handle messages
var msgSource = new EventSource('event-stream?channel=messages&t=' + new Date().getTime());
$(msgSource).handleServerEvents({
handlers: {
FrontendMessage: function (msg) {
console.log('Message from server', msg);
}
}
});
</script>
</body>
</html>
控制台日志如下所示:
Message from server Object {Level: "success", Message: "Tick 28"}
Message from server Object {Level: "success", Message: "Tick 35"}
Message from server Object {Level: "success", Message: "Tick 42"}
Message from server Object {Level: "success", Message: "Tick 49"}
问题是您试图在 ServerEventsFeature
注册之前向 IServerEvents
发送消息,因为您是在 AppHost.Configure()
中立即启动它,而不是在 AppHost.Configure()
之后AppHost
已初始化。问题的实际原因是计时器启动时 IdleTimeout
未正确初始化,导致每个服务器事件连接的生命周期为 00:00:00
,这意味着它们会收到一条消息自动处理并再次自动重新连接 - 整个过程大约需要 6-7 个 ticks :)
ServiceStack Plugins aren't registered when they're added, they get registered together after AppHost.Configure()
which gives other plugins a chance to add/remove/inspect other plugins before they're registered. You also don't need to register MemoryServerEvents
since that's the default and the recommended way to initialize a timer with an interval是在定时器回调中使用timer.Change()
。
鉴于此,我会将您的 AppHost 重写为:
public class AppHost : AppSelfHostBase
{
public AppHost()
: base("ServerSentEvents", typeof(AppHost).Assembly) { }
public override void Configure(Container container)
{
SetConfig(new HostConfig {
#if DEBUG
DebugMode = true,
WebHostPhysicalPath = "~/../..".MapServerPath(),
#endif
});
Plugins.Add(new ServerEventsFeature());
container.Register(c => new FrontendMessages(c.Resolve<IServerEvents>()));
}
}
并让您的 FrontendMessages 仅在显式调用 Start()
时启动,即:
public class FrontendMessage
{
public string Level { get; set; }
public string Message { get; set; }
}
public class FrontendMessages
{
private readonly IServerEvents _serverEvents;
private Timer _timer;
public FrontendMessages(IServerEvents serverEvents)
{
if (serverEvents == null) throw new ArgumentNullException(nameof(serverEvents));
_serverEvents = serverEvents;
}
public void Start()
{
var ticks = 0;
_timer = new Timer(_ => {
Info($"Tick {ticks++}");
_timer.Change(500, Timeout.Infinite);
}, null, 500, Timeout.Infinite);
}
public void Info(string message, params object[] parameters)
{
var frontendMessage = new FrontendMessage {
Level = "success",
Message = message
};
Console.WriteLine("Sending message: " + frontendMessage.Message);
_serverEvents.NotifyChannel("messages", frontendMessage);
}
}
然后在AppHost初始化完成后才启动,即:
class Program
{
static void Main(string[] args)
{
var appHost = new AppHost()
.Init()
.Start("http://*:2000/"); //Start AppSelfHost
appHost.Resolve<FrontendMessages>().Start(); //Start timer
Process.Start("http://localhost:2000/"); //View in Web browser
Console.ReadLine(); //Prevent Console App from existing
}
}