I would like to write a simple chat on the principle as omegle.com. I wrote that if the user enters the server and the queue is empty creates a new group and falls to the queue. When the other person enters, it connects with this in the queue.
Here's my code:
public class UserGroup
{
public string GroupName { get; set; }
}
public class ChatHub : Hub
{
public static Queue<UserGroup> Users = new Queue<UserGroup>();
public static string Group { get; set; }
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public override System.Threading.Tasks.Task OnConnected()
{
if(Users.Count == 0)
{
var user = new UserGroup { GroupName = Context.ConnectionId };
Users.Enqueue(user);
Group = user.GroupName;
JoinGroup(user.GroupName);
}
else
{
JoinGroup(Users.Peek().GroupName);
Group = Users.Peek().GroupName;
Users.Dequeue();
}
return base.OnConnected();
}
public void SayHello(string name, string helloMsg)
{
Clients.Caller.Hello(name, helloMsg);
}
public void Send(string msg)
{
Clients.Group(Group).SendMessage(msg);
}
}
Unfortunately, when I connect to someone else, everything breaks down and does not create a new group for new people. All static data, but unfortunately not SignalR allows otherwise. You have an idea how to get around this?
I don't think you need a Queue or use groups in this scenario. The easiest solution would be to send the stranger's client id to the other client. You should incorporate locking because you access shared state from different hub instances:
Create a new ASP.NET Web Application project and select MVC as framework.
Add Microsoft.AspNet.SignalR and knockoutjs nuget packages and update all packages.
The current SignalR version requires that you add a Startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
Add a hub class:
public class ChatHub : Hub
{
private static string waitingUser;
private static readonly object SyncLock = new object();
public void SendMessage(string text, string clientId)
{
this.Clients.Client(clientId).addMessage(text);
}
public override Task OnConnected()
{
var newUser = this.Context.ConnectionId;
string otherUser;
lock (SyncLock)
{
if (waitingUser == null)
{
waitingUser = newUser;
return base.OnConnected();
}
otherUser = waitingUser;
waitingUser = null;
}
this.Clients.Caller.startChat(otherUser);
this.Clients.Client(otherUser).startChat(newUser);
return base.OnConnected();
}
}
Finally, add the following code to your view:
#{
ViewBag.Title = "Chat wit a Stranger";
}
<h1>Chat with a Stranger</h1>
<div data-bind="foreach: messages">
<div data-bind="text: $data"></div>
</div>
<hr/>
<form data-bind="submit: send, visible: connected">
<input type="text" data-bind="value: text" />
<button type="submit">Send</button>
</form>
<script src="~/Scripts/knockout-3.2.0.debug.js"></script>
<script src="~/Scripts/jquery-2.1.1.js"></script>
<script src="~/Scripts/jquery.signalR-2.1.2.js"></script>
<script src="~/signalr/hubs"></script>
<script>
var hub = $.connection.chatHub;
var vm = {
otherUser: "",
messages: ko.observableArray(["Waiting for a stranger..."]),
connected: ko.observable(false),
text: ko.observable(""),
send: function () {
var text = vm.text();
if (text.length == 0) return;
hub.server.sendMessage(text, vm.otherUser);
vm.messages.push("You: " + text);
vm.text("");
},
addMessage: function(text) {
vm.messages.push("Stranger: " + text);
},
startChat: function (otherUser) {
vm.otherUser = otherUser;
vm.messages(["A stranger has connected. Say hello!"]);
vm.connected(true);
}
}
ko.applyBindings(vm);
hub.client.startChat = vm.startChat;
hub.client.addMessage = vm.addMessage;
$.connection.hub.start();
</script>
Related
I want to Create WebSocket Example in which i do not want to refresh the page for getting latest data.
I Create one Html page in which create one object of websocket.
E.g
ClientSide Implementation
var ws = new WebSocket(hostURL);
ws.onopen = function ()
{
// When Connection Open
};
ws.onmessage = function (evt)
{
// When Any Response come from WebSocket
}
ws.onclose = function (e)
{
// OnClose of WebSocket Conection
}
Server Side Implementation
public class WebSocketManager : WebSocketHandler
{
private static WebSocketCollection WebSocketObj4AddMessage = new WebSocketCollection();
public override void OnOpen()
{
// Do when Connection Is Open
}
public override void OnClose()
{
// Close Connection
}
public override void OnMessage(string message)
{
// When Any Message Sent to Client
}
}
Is I am doing right way to use WebSocket ?
Please help me to clear out in this section.
Here a sample.
First you have to install Asp.net SignalR package along with its dependenies.
You have call the SignalR when the app starts
namespace ABC
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR(); <--{Add this line}
}
}
}
You have start the SqlDependency when app start and stop when app stops in the Global.asax file.
string ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionStringsName"].ConnectionString;
protected void Application_Start()
{
SqlDependency.Start(ConnectionString);
}
protected void Application_End()
{
SqlDependency.Stop(ConnectionString);
}
You have to create custom Hubclass extending Hub Base class
public class MessagesHub : Hub
{
[HubMethodName("sendMessages")]
public void SendMessages()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessagesHub>();
context.Clients.All.updateMessages();
}
}
Then in the client page, you have add these code in the javascript section
$(function () {
// Declare a proxy to reference the hub.
var notifications = $.connection.messagesHub;
//debugger;
// Create a function that the hub can call to broadcast messages.
notifications.client.updateMessages = function () {
getAllMessages()
};
// Start the connection.
$.connection.hub.start().done(function () {
getAllMessages();
}).fail(function (e) {
alert(e);
});
});
function getAllMessages() {
$.ajax({
url: '../../Notifications/GetNotificationMessages',
.
.
}
The server call this function when there there is any change in the database table using sqlDependency
The getAllMessages() is the controller for your code to handle, that should be shown in the view page and it will be call when the app starts and any change in db
public ActionResult GetNotificationMessages()
{
NotificationRepository notification = new NotificationRepository();
return PartialView("_NotificationMessage");
}
The in model class
public class NotificationRepository
{
readonly string connectionString = ConfigurationManager.ConnectionStrings["InexDbContext"].ConnectionString;
public IEnumerable<Notification> GetAllMessages(string userId)
{
var messages = new List<Notification>();
using(var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var command = new SqlCommand(#"SELECT [NotificationID], [Message], [NotificationDate], [Active], [Url], [userId] FROM [dbo].[Notifications] WHERE [Active] = 1 AND [userId] ='" + userId + "'", connection))
{
command.Notification = null;
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
var reader = command.ExecuteReader();
while (reader.Read())
{
messages.Add(item: new Notification { NotificationID = (int)reader["NotificationID"], Message = (string)reader["Message"], Url = (string)reader["Url"] });
}
}
}
return messages;
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
MessagesHub message = new MessagesHub();
message.SendMessages();
}
}
}
This well show latest data when the database table is updated. the message will shown at runtime.
Hope this helps
You are on the right path
You can refer this if I am not late ...This is working example
CLIENT SIDE
var ws;
var username = "JOHN";
function startchat() {
var log= $('log');
var url = 'ws://<server path>/WebSocketsServer.ashx?username=' + username;
ws = new WebSocket(url);
ws.onerror = function (e) {
log.appendChild(createSpan('Problem with connection: ' + e.message));
};
ws.onopen = function () {
ws.send("I am Active-" +username);
};
ws.onmessage = function (e) {
if (e.data.toString() == "Active?") {
ws.send("I am Active-" + username);
}
else {
}
};
ws.onclose = function () {
log.innerHTML = 'Closed connection!';
};
}
</script>
<div id="log">
</div>
Server Side in Websocketserver.ashx page
public class WebSocketsServer : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
{
context.AcceptWebSocketRequest(new MicrosoftWebSockets());
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
Add below class in the server side
public class MicrosoftWebSockets : WebSocketHandler
{
private static WebSocketCollection clients = new WebSocketCollection();
private string msg;
public override void OnOpen()
{
this.msg = this.WebSocketContext.QueryString["username"];
clients.Add(this);
clients.Broadcast(msg);
}
public override void OnMessage(string message)
{
clients.Broadcast(string.Format(message));
}
public override void OnClose()
{
clients.Remove(this);
clients.Broadcast(string.Format(msg));
}
add this dll to the above class
using Microsoft.Web.WebSockets;
I donot remeber where I got the reference ...but above code is derived from my current working application
I am new in using SignalR. When I send a message to a particular user are:
I do not get the message back (I do not see it on the screen).
The user receives a notice can not send a message back.
That means sending a message can only be in one direction.
How to do that two users can send messages to each other?**
There is my classes i use:
[HubName("TSChatHub")]
[Authorize]
public class ChatHub : Hub
{
private readonly static ConnectionMapping<string> _connections =
new ConnectionMapping<string>();
public void SendChatMessage(string who, string message)
{
string name = Context.User.Identity.Name;
foreach (var connectionId in _connections.GetConnections(who))
{
Clients.Client(connectionId).addChatMessage(name + ": " + message);
}
}
public override Task OnConnected()
{
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string name = Context.User.Identity.Name;
_connections.Remove(name, Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
string name = Context.User.Identity.Name;
if (!_connections.GetConnections(name).Contains(Context.ConnectionId))
{
_connections.Add(name, Context.ConnectionId);
}
return base.OnReconnected();
}
}
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;
}
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);
}
}
}
}
}
There is my View:
<div class="chatcontainer">
<input type="text" id="message" />
<input type="button" id="sendmessage" value="Send" />
<input type="hidden" id="displayname" />
<ul id="discussion">
</ul>
</div>
$(function () {
// Declare a proxy to reference the hub.
var chat = $.connection.TSChatHub;
debugger;
// Create a function that the hub can call to broadcast messages.
chat.client.addChatMessage = function (name, message) {
// Add the message to the page.
$('#discussion').append('<li><strong>' + htmlEncode(name)
+ '</strong>: ' + htmlEncode(name) + '</li>');
};
// Get the user name and store it to prepend to messages.
// $('#displayname').val(prompt('Enter your name:', ''));
// Set initial focus to message input box.
$('#message').focus();
// Start the connection.
$.connection.hub.start().done(function () {
console.log('Now connected, connection ID=' + $.connection.hub.id);
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.sendChatMessage($('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
}).fail(function () { console.log("Could not connect"); });
});
// This optional function html-encodes messages for display in the page.
function htmlEncode(value) {
var encodedValue = $('<div />').text(value).html();
return encodedValue;
}
Anyone has a solution to help me
If I correctly understand you, you can send a message to specific user like this.
public void SendChatMessage(string who, string message)
{
string name = Context.User.Identity.Name;
//foreach (var connectionId in _connections.GetConnections(who))
//{
//Clients.Client(connectionId).addChatMessage(name + ": " + message);
//}
Clients.User(who).addChatMessage(name + ": " + message);
}
And "string who" should be receiver user username. Hope this help.
Just change your the arguments of your addChatMessage method
public void SendChatMessage(string who, string message)
{
string name = Context.User.Identity.Name;
foreach (var connectionId in _connections.GetConnections(who))
{
Clients.Client(connectionId).addChatMessage(name , message);
}
}
This line of code is calling your client side addChatMessage method that you have written on the View
Clients.Client(connectionId).addChatMessage(name , message);
The function was not getting mapped properly since the argument you were supplying from your cs page was different than expected by the client side method.
Your call
Clients.Client(connectionId).addChatMessage(name + ": " + message);
params expected by client method
chat.client.addChatMessage = function (name, message)
you were supplying a single argument hence it was not getting mapped.
If still the problem persists try to check _connections dictionary in the SendChatMessage method the only reason a message will not come back to you is if your connection id is not added to this dictionary which is not possible since that is the first step that happens as soon as you run the app in the OnConnected event
I have application in which I am showing data from sensors using SignalR. It uses ASP.net membership to authenticate the users. It all works fine if I only open one browser window(e.g. Firefox). If I open same website in another browser e.g. Chrome at the same time then signalR connection to firefox browser drops even if the user is different. This is what I am using to broadcast message:
Hub
[Authorize]
public class DataHub:Hub
{
private readonly RealTimeData _sensor;
public DataHub() : this(RealTimeData.Instance) { }
public DataHub(RealTimeData data)
{
_sensor = data;
}
public override Task OnConnected()
{
// _sensor.UserId = Context.ConnectionId; changed to
_sensor.UserId = Membership.GetUser().ProviderUserKey.ToString();
return base.OnConnected();
}
}
public class RealTimeData
{
//User Id
public String UserId { get; set; }
private readonly static Lazy<RealTimeData> _instance = new Lazy<RealTimeData>(() => new RealTimeData(GlobalHost.ConnectionManager.GetHubContext<DataHub>().Clients));// Singleton instance
private IHubConnectionContext Clients;
private void BroadcastDataOfAllSensors(List<SensorDetails> sensor)
{
//Clients.Client(UserId).updateDashboard(sensor);changed to
Clients.User(UserId).updateDashboard(sensor);
}
}
Application Startup
public class StartUp
{
public void Configuration(IAppBuilder app)
{
var idProvider = new UserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
app.MapSignalR();
}
}
UserId
public class UserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
var userId = Membership.GetUser().ProviderUserKey;
return userId.ToString();
}
}
My connection does not start.
This code worked in 1.x but in version 2 is not working.
SignalR seems to be trying to connect but without success.
The hub method is never called.
Attached sending an image with SignalR debug.
Javascript:
<script type="text/javascript">
$.connection.hub.logging = true;
var options = { transport: ['webSockets', 'longPolling'] };
$(function() {
var userHub = $.connection.userHub;
//Iniciar connecção
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
</script>
Hub:
[HubName("userHub")]
public class UserHub : Hub
{
public void Ini()
{
Clients.Client(Context.ConnectionId).iniDone(string.Format("Conectado com o id: {0}", Context.ConnectionId));
}
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Add(connectionId);
return base.OnConnected();
}
public override Task OnDisconnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Remove(connectionId);
return base.OnDisconnected();
}
}
Debug:
SignalR Debug Image
EDIT:
I found the problem! The GetInstance method of my Singleton has problems.
public static UserData GetInstance(string username)
{
if (_sharedUsers == null)
lock (_lockCreate)
_sharedUsers = new Dictionary<string, UserData>();
if (!_sharedUsers.ContainsKey(username))
lock (_lockAdd)
_sharedUsers.Add(username, new UserData(username));
return _sharedUsers[username];
}
the method stops always here: lock (_lockAdd)
I want to save all user connectionsIds Any ideas?
Thanks
Try moving the client method subscription to be before you connect. If it's not registered by the time the connection is started, then it will not be callable from the server.
So change it to the following:
$(function() {
var userHub = $.connection.userHub;
//Register Client handlers first
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
//Now you can connect.
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
Edit
Based on your comment around a server error in the OnConnected method, it seems like you may have a two problems then. Isolate the connection tracking part out (just comment it out) to get the full round-trip going between client and server. Then add back the connection tracking which is possibly a DB connection error - check the server logs.
Edit
In terms of storing the user connections, you've a few options.
Use ConcurrentDictionary:
One of the simplest is storing in a static ConcurrentDictionary, similar to what you have. Try to avoid the use of so many locks - using a ConcurrentDictionary means you'll actually end up with none.
e.g.
public class UserData
{
public UserData(string username)
{
UserName = username;
ConnectionIds = new HashSet<string>();
}
public string UserName { get; private set; }
public HashSet<string> ConnectionIds { get; private set; }
}
public static class ConnectionStore
{
private static readonly ConcurrentDictionary<string, UserData> _userData = new ConcurrentDictionary<string, UserData>();
public static void Join(string username, string connectionId)
{
_userData.AddOrUpdate(username,
u => new UserData(u), /* Lambda to call when it's an Add */
(u, ud) => { /* Lambda to call when it's an Update */
ud.ConnectionIds.Add(connectionId);
return ud;
});
}
}
See MSDN for more info: http://msdn.microsoft.com/en-us/library/ee378675(v=vs.110).aspx
Use a database:
The other option is to store in a database (using Entity Framework) which has the added benefit of tracking user data across server recycles.
Have a look at http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections which shows all these options a couple of others.
Had the same problem for so long, so gave up the whole signalR at some point, but had to pick it up again for our project:
I have written an answer which might lead you and others on the right track (step by step)...In the answer I am using PersistentConnection rather than Hub, but the principle should be the same:
https://stackoverflow.com/a/25304790/3940626
I am trying to pass a custom object from self hosted signalr hub server to all the clients, the method in client side not getting invoked .But if the same custom class object is passed from client to server works fine, meaning it invokes the server method.
below is the sample code :
public class ChatHub : Hub
{
public void Send(DataContract message)
{
//below call not reaching to client while passing custom obj
Clients.All.SendMessage(message);
//below string passing works - means invokes client method
Clients.All.SendMsg("test");
}
}
custom class defined in both client and server project via dll:
public class DataContract
{
public string Name
{
get;set;
}
public int Id
{
get;set;
}
}
client side method:
public class SignalRClient
{
HubConnection hubConnection = null;
IHubProxy chat;
public SignalRClient()
{
hubConnection = new HubConnection("https://localhost/");
chat = hubConnection.CreateHubProxy("ChatHub");
}
public void StartConnection()
{
if (hubConnection != null)
{
hubConnection.Start().Wait();
}
chat.On<DataContract>("SendMessage", (stock) =>
{
Console.WriteLine("name {0} id {1}", stock.Name, stock.Id.ToString());
});
chat.On<string>("SendMsg", (message) =>
{
Console.WriteLine(message);
});
}
public void SendMessage(DataContract dd)
{
dd.Name = "test";
chat.Invoke("Send", dd).Wait();
}
public void SendMessage(string msg)
{
chat.Invoke("SendMsg", "Console app", msg).Wait();
}
}
//program.cs
main()
{
SignalRClient client = new SignalRClient();
client.StartConnection();
string msg = null;
while ((msg = Console.ReadLine()) != null)
{
DataContract dd = new DataContract { Name = "arun", Id = 9 };
//below calls reaches to server both string type and custome obj
client.SendMessage(dd);
client.SendMessage("client");
}
}
Any clue on why when calling from server (i.e Clients.All.SendMessage(message); ) not invoking client method when param is custom object.
Thanks in advance.