チャットルームを実装しています。これまでのところ、ユーザーはブラウザからJSクライアントを介してメッセージを送信でき、C#クライアントを使用して同じことを実行できます。これらのメッセージは他のユーザーにブロードキャストされます。今、私は「オンラインユーザー」を実装しようとしています。
私のアプローチは次のとおりです:
OnConnected
-データベースのユーザーをIsOnline = trueに更新しますOnDisconnected
-ユーザーが他に接続していない場合は、データベース内のユーザーをIsOnline = false
に更新します私が遭遇している問題は、OnDisconnected
がすべてのクライアントIDで常に呼び出されるわけではないということです。古い接続により、「ユーザーが他の接続を持っていない場合」ビットがtrueに解決されないため、ユーザーは常に「オンライン」です。
私が考えることができる1つのハックな解決策は、alwaysにユーザーをオフラインでOnDisconnect
に設定することですが、これは、ユーザーが2つのタブを開いて閉じると1つは、「オフライン」になります。その後、送信されるメッセージごとにユーザーをオンラインに再設定することもできますが、これは処理サイクルの無駄のようであり、ユーザーが実際にオンラインであるときに、ユーザーがオフラインであると見なされる時間を残します。
OnDisconnectedがすべてのクライアントで呼び出されることを保証する方法があれば、この問題は解消されると思います。これは、クライアントを長時間(> 10分)開いたままにしてから切断するようなようですOnDisconnected
が呼び出されません。私は最善を尽くして、再現手順を特定し、これを更新し続けます。
だから-これはオンラインステータスを処理するための有効なアプローチですか?もしそうなら、最終的にすべての接続に対してOnDisconnected
が確実に実行されるようにするために他に何ができるでしょうか?
この問題は、既存の接続が時間の経過とともに増加し続けるため、私が間違っていない場合、未処理の状態接続が原因で最終的にオーバーフローするため、私は心配しています。
コード:
グループ化には In-memory アプローチを使用しています。
メッセージの送信(C#):
private readonly static ConnectionMapping<string> _chatConnections =
new ConnectionMapping<string>();
public void SendChatMessage(string key, ChatMessageViewModel message) {
message.HtmlContent = _compiler.Transform(message.HtmlContent);
foreach (var connectionId in _chatConnections.GetConnections(key)) {
Clients.Client(connectionId).addChatMessage(JsonConvert.SerializeObject(message).SanitizeData());
}
}
状態管理:
public override Task OnConnected() {
HandleConnection();
return base.OnConnected();
}
public override Task OnDisconnected() {
HandleConnection(true);
return base.OnDisconnected();
}
public override Task OnReconnected() {
HandleConnection();
return base.OnReconnected();
}
private void HandleConnection(bool shouldDisconnect = false) {
if (Context.User == null) return;
var username = Context.User.Identity.Name;
var _userService = new UserService();
var key = username;
if (shouldDisconnect) {
_chatConnections.Remove(key, Context.ConnectionId);
var existingConnections = _chatConnections.GetConnections(key);
// this is the problem - existingConnections occasionally gets to a point where there's always a connection - as if the OnDisconnected() never got called for that client
if (!existingConnections.Any()) { // THIS is the issue - existingConnections sometimes contains connections despite there being no open tabs/clients
// save status serverside
var onlineUserDto = _userService.SetChatStatus(username, false);
SendOnlineUserUpdate(_baseUrl, onlineUserDto, false);
}
} else {
if (!_chatConnections.GetConnections(key).Contains(Context.ConnectionId)) {
_chatConnections.Add(key, Context.ConnectionId);
}
var onlineUserDto = _userService.SetChatStatus(Context.User.Identity.Name, true);
SendOnlineUserUpdate(_baseUrl, onlineUserDto, true);
// broadcast to clients
}
}
ConnectionMapping:
public class ConnectionMapping<T> {
private readonly Dictionary<T, HashSet<string>> _connections =
new Dictionary<T, HashSet<string>>();
public int Count {
get {
return _connections.Count;
}
}
public void Add(T key, string connectionId) {
lock (_connections) {
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections)) {
connections = new HashSet<string>();
_connections.Add(key, connections);
}
lock (connections) {
connections.Add(connectionId);
}
}
}
public IEnumerable<string> GetConnections(T key) {
HashSet<string> connections;
if (_connections.TryGetValue(key, out connections)) {
return connections.ToList();
}
return Enumerable.Empty<string>();
}
public void Remove(T key, string connectionId) {
lock (_connections) {
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections)) {
return;
}
lock (connections) {
connections.Remove(connectionId);
if (connections.Count == 0) {
_connections.Remove(key);
}
}
}
}
}
更新
Dfowlerの提案によれば、代替のアプローチは、インメモリではなくインデータベースマッピングを実装することです。このようにして、ゾンビ化された接続を識別するためにより多くのメタデータを使用できます。ただし、既に実装されている 推奨されるアプローチ から再設計する代わりに、メモリ内の問題の解決策を期待しています。
ここでこのサンプルに従ってください:
https://github.com/DamianEdwards/NDCLondon2013/tree/master/UserPresence