I have a very simple C# command line app that connects to an MQTT server and prints messages to the console.
using MQTTnet;
using MQTTnet.Client.Options;
using MQTTnet.Extensions.ManagedClient;
using System.Text;
var options = new MqttClientOptionsBuilder()
.WithTcpServer(MqttConfig.Server, MqttConfig.Port)
.WithCredentials(MqttConfig.User, MqttConfig.Password)
.WithClientId("MqttTest")
.WithCleanSession()
.Build();
var MqttClient = new MqttFactory().CreateMqttClient();
var cancellationToken = new CancellationToken();
var subscribeOptions = new MQTTnet.Client.Subscribing.MqttClientSubscribeOptions();
subscribeOptions.TopicFilters.Add(new MqttTopicFilter { Topic = MqttConfig.Topic });
MqttClient.ConnectAsync(options, cancellationToken);
MqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
MqttClient.UseApplicationMessageReceivedHandler(e => { HandleMessageReceived(e.ApplicationMessage); });
while (true)
{
Task.Delay(1000).GetAwaiter().GetResult();
}
static void HandleMessageReceived(MqttApplicationMessage applicationMessage)
{
Console.WriteLine("### RECEIVED MESSAGE ###");
Console.WriteLine($"+ Topic = {applicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(applicationMessage.Payload)}");
Console.WriteLine();
}
abstract class MqttConfig
{
public static readonly string Server = "servername";
public static readonly int Port = 1883;
public static readonly string User = "user";
public static readonly string Password = "password";
public static readonly string Topic = "#";
}
Putting the MqttConfig class information into an app like MQTT X shows a bunch of incoming messages. But running this C# app just shows a blank console.
I ended up making basing the application on an MQTTnet sample. I'm posting it as an answer here in case anyone else has the same question in the future.
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using System.Text.Json;
#region Subscribe to topic & handle incoming messages
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer(MqttConfig.Server, MqttConfig.Port)
.WithCredentials(MqttConfig.User, MqttConfig.Password)
.Build();
mqttClient.UseApplicationMessageReceivedHandler(e =>
{
Console.WriteLine("Received application message.");
e.DumpToConsole();
return Task.CompletedTask;
});
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(f => f.WithTopic(MqttConfig.Topic))
.Build();
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
Console.ReadLine(); // Prevents app from immediately closing once MQTT subscription is complete.
// Will close if user presses "enter" before any messages are received.
}
static class ObjectExtensions
{
public static TObject DumpToConsole<TObject>(this TObject #object)
{
var output = "NULL";
if (#object != null)
{
output = JsonSerializer.Serialize(#object, new JsonSerializerOptions { WriteIndented = true });
}
Console.WriteLine($"[{#object?.GetType().Name}]:\r\n{output}");
return #object;
}
}
#endregion
static class MqttConfig
{
public static readonly string Server = "servername";
public static readonly int Port = 1883;
public static readonly string User = "user";
public static readonly string Password = "password";
public static readonly string Topic = "#";
}
Related
My Program doesnt seem to download users correctly. "AlwaysDownloadUsers = true" is added in the config but it doesnt seem to be working correctly. When starting the bot not all users seem to be download because the program always returns "User not Found" until the user starts typing or sends a message...
I think that my code in general is bad, as i used a few sources/tutorials and mixed the code together, so id be happy about every sort of feedback :)
Main Program:
class Program
{
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult();
public DiscordSocketClient _client;
public CommandService _command;
public LoggingService _log;
public CommandHandler _handler;
public async Task MainAsync()
{
var config = new DiscordSocketConfig
{
LogLevel = LogSeverity.Debug,
AlwaysDownloadUsers = true,
MessageCacheSize = 100
};
_client = new DiscordSocketClient(config);
_command = new CommandService();
_log = new LoggingService(_client, _command);
_handler = new CommandHandler(_client, _command);
var token = File.ReadAllText("token.txt");
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
_client.Ready += Ready;
await Task.Delay(-1);
}
public async Task Ready()
{
Console.WriteLine("Bot is Ready!");
await _handler.InstallCommandsAsync();
}
}
CommandHandler:
public class CommandHandler
{
private readonly DiscordSocketClient _client;
private readonly CommandService _command;
public CommandHandler(DiscordSocketClient client, CommandService command)
{
_client = client;
_command = command;
}
public async Task InstallCommandsAsync()
{
_client.MessageReceived += HandleCommandAsync;
await _command.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), services: null);
}
private async Task HandleCommandAsync(SocketMessage arg)
{
var msg = arg as SocketUserMessage;
if (msg == null) return;
int argPos = 0;
if (msg.HasCharPrefix('.', ref argPos) && !(msg.Author.IsBot))
{
var _context = new SocketCommandContext(_client, msg);
var result = await _command.ExecuteAsync(_context, argPos, services: null);
if (!result.IsSuccess)
Console.WriteLine(result.ErrorReason);
if (result.Error.Equals(CommandError.UnmetPrecondition))
await msg.Channel.SendMessageAsync(result.ErrorReason);
}
}
}
Command Class not included as im pretty sure its not at fault
Member intents in the developer portal were enabled already, but not in the DiscordSocketConfig
So i added "Gateway.Intents = GatewayIntents.All" ot the config and its working now
Not working:
var config = new DiscordSocketConfig
{
LogLevel = LogSeverity.Debug,
AlwaysDownloadUsers = true,
MessageCacheSize = 100,
};
Working now:
var config = new DiscordSocketConfig
{
LogLevel = LogSeverity.Debug,
AlwaysDownloadUsers = true,
MessageCacheSize = 100,
GatewayIntents = GatewayIntents.All
};
Thanks Anu6is
I have recently started working on a Kafka Streaming Application using .NET core. I have followed the tutorial: https://medium.com/#srigumm/building-realtime-streaming-applications-using-net-core-and-kafka-ad45ed081b31.
I have built a basic producer-consumer application in which the producer takes input data and pushes it into a kafka-topic. A consumer can subscribe to the topic and consume data from it. I am also able to push this data into another topic by using a new producer. But what I am unable to do is initialise multiple consumers to consume from the same topic.
In appsettings.json:
"consumer": {
"bootstrapservers": "localhost:9092", //specify your kafka broker address
"groupid": "csharp-consumer",
"enableautocommit": true,
"statisticsintervalms": 5000,
"sessiontimeoutms": 6000,
"autooffsetreset": 0,
"enablepartitioneof": true,
"SaslMechanism": 0, //0 for GSSAPI
//"SaslKerberosKeytab":"filename.keytab", //specify your keytab file here
"SaslKerberosPrincipal": "youralias#DOMAIN.COM", //specify your alias here
"SaslKerberosServiceName": "kafka"
//"SaslKerberosKinitCmd":"kinit -k -t %{sasl.kerberos.keytab} %{sasl.kerberos.principal}"
},
processOrderServices.cs:
namespace Api.Services
{
public class ProcessOrdersService : BackgroundService
{
private readonly ConsumerConfig consumerConfig;
private readonly ProducerConfig producerConfig;
//----------------------
//private readonly ConsumerConfig consumerConfig2;
public ProcessOrdersService(ConsumerConfig consumerConfig, ProducerConfig producerConfig)
{
this.producerConfig = producerConfig;
this.consumerConfig = consumerConfig;
//----------------------
//this.consumerConfig2 = consumerConfig;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Console.WriteLine("OrderProcessing Service Started\n\n");
while (!stoppingToken.IsCancellationRequested)
{
var consumerHelper = new ConsumerWrapper(consumerConfig, "orderrequests");
//var consumerHelper2 = new ConsumerWrapper(consumerConfig, "orderrequests");
string orderRequest = consumerHelper.readMessage();
//consumerHelper2.DisplayMessage();
//Deserilaize
OrderRequest order = JsonConvert.DeserializeObject<OrderRequest>(orderRequest);
//TODO:: Process Order
Console.WriteLine($"Info: OrderHandler => Processing the order for {order.productname}\n\n");
order.status = OrderStatus.COMPLETED;
//Write to ReadyToShip Queue
var producerWrapper = new ProducerWrapper(producerConfig,"readytoship");
await producerWrapper.writeMessage(JsonConvert.SerializeObject(order));
//--------------------------
// var consumerHelper2 = new ConsumerWrapper(consumerConfig2, "orderrequests");
//string processedOrder = consumerHelper2.readMessage();
//OrderRequest order2 = JsonConvert.DeserializeObject<OrderRequest>(processedOrder);
//Console.WriteLine($"Info: OrderHandler => Delivered the order for {order2.productname}\n\n");
//order2.status = OrderStatus.DELIVERED;
//----------------------------
}
}
}
}
ConsumerWrapper.cs:
namespace Api
{
public class ConsumerWrapper
{
private string _topicName;
private ConsumerConfig _consumerConfig;
private Consumer<string,string> _consumer;
private static readonly Random rand = new Random();
public ConsumerWrapper(ConsumerConfig config,string topicName)
{
this._topicName = topicName;
this._consumerConfig = config;
this._consumer = new Consumer<string,string>(this._consumerConfig);
this._consumer.Subscribe(topicName);
}
public string readMessage(){
var consumeResult = this._consumer.Consume();
return consumeResult.Value;
}
public void DisplayMessage()
{
var consumeResult = this._consumer.Consume();
Console.WriteLine(consumeResult.Value);
Console.WriteLine($"Info: OrderHandler => Delivered the order for {consumeResult.Value}\n\n");
return;
}
}
}
I want to be able to call the Consumer class multiple times and be able to read from the same topic. I have understood that there is a requirement to create multiple partitions/group-ids in order to be able to do that. But I am unable to figure out where and how to do that.
You can do this by using group id concept in Kafka, just use same group id for multiple consumers to avoid duplicate consumption of the data from the same topic.
I take data from Bittrex without WebSocket by this way
request = WebRequest.Create("https://bittrex.com/api/v1.1/public/getticker?market=USDT-BTC");
request.Credentials = CredentialCache.DefaultCredentials;
response = (HttpWebResponse)request.GetResponse();
dataStream = response.GetResponseStream();
reader = new StreamReader(dataStream);
responseFromServer = reader.ReadToEnd();
reader.Close();
dataStream.Close();
response.Close();
date = JsonConvert.DeserializeObject(responseFromServer);
It is very easy way and it is working on Bittrex. But I did a lot off request. I need do it on Bitfinex but I have the exeption "Too many request". As I understood I need WebSocket for this. By this adress https://api.bitfinex.com/v1/pubticker/BTCUSD. Somebody can show easy code to understand how I need to conect and write in Console info from WebSocket. Thanks!
BIttrex release in March beta version of new site and WebSocket. GitHub repository have samples for usage WebSocket channel to subscribe for events.
Here is C# example:
using System;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using Microsoft.AspNet.SignalR.Client;
namespace WebsocketSample
{
public sealed class BittrexWebsocket
{
public delegate void BittrexCallback(string info);
private HubConnection _hubConnection { get; }
private IHubProxy _hubProxy { get; }
private BittrexCallback _updateExchangeState { get; }
private BittrexCallback _updateOrderState { get; }
private BittrexCallback _updateBalanceState { get; }
public BittrexWebsocket(
string connectionUrl,
BittrexCallback updateExchangeState,
BittrexCallback updateOrderState,
BittrexCallback updateBalanceState
)
{
// Set delegates
_updateExchangeState = updateExchangeState;
_updateOrderState = updateOrderState;
_updateBalanceState = updateBalanceState;
// Create connection to c2 SignalR hub
_hubConnection = new HubConnection(connectionUrl);
_hubProxy = _hubConnection.CreateHubProxy("c2");
// Register callback for uE (exchange state delta) events
_hubProxy.On(
"uE",
exchangeStateDelta => _updateExchangeState?.Invoke(exchangeStateDelta)
);
// Register callback for uO (order status change) events
_hubProxy.On(
"uO",
orderStateDelta => _updateOrderState?.Invoke(orderStateDelta)
);
// Register callback for uB (balance status change) events
_hubProxy.On(
"uB",
balanceStateDelta => _updateBalanceState?.Invoke(balanceStateDelta)
);
_hubConnection.Start().Wait();
}
public void Shutdown() => _hubConnection.Stop();
// marketName example: "BTC-LTC"
public async Task<bool> SubscribeToExchangeDeltas(string marketName) => await _hubProxy.Invoke<bool>("SubscribeToExchangeDeltas", marketName);
// The return of GetAuthContext is a challenge string. Call CreateSignature(apiSecret, challenge)
// for the response to the challenge, and pass it to Authenticate().
public async Task<string> GetAuthContext(string apiKey) => await _hubProxy.Invoke<string>("GetAuthContext", apiKey);
public async Task<bool> Authenticate(string apiKey, string signedChallenge) => await _hubProxy.Invoke<bool>("Authenticate", apiKey, signedChallenge);
// Decode converts Bittrex CoreHub2 socket wire protocol data into JSON.
// Data goes from base64 encoded to gzip (byte[]) to minifed JSON.
public static string Decode(string wireData)
{
// Step 1: Base64 decode the wire data into a gzip blob
byte[] gzipData = Convert.FromBase64String(wireData);
// Step 2: Decompress gzip blob into minified JSON
using (var decompressedStream = new MemoryStream())
using (var compressedStream = new MemoryStream(gzipData))
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
deflateStream.CopyTo(decompressedStream);
decompressedStream.Position = 0;
using (var streamReader = new StreamReader(decompressedStream))
{
return streamReader.ReadToEnd();
}
}
}
public static string CreateSignature(string apiSecret, string challenge)
{
// Get hash by using apiSecret as key, and challenge as data
var hmacSha512 = new HMACSHA512(Encoding.ASCII.GetBytes(apiSecret));
var hash = hmacSha512.ComputeHash(Encoding.ASCII.GetBytes(challenge));
return BitConverter.ToString(hash).Replace("-", string.Empty);
}
}
class Program
{
static public BittrexWebsocket.BittrexCallback CreateCallback(string name)
{
//
// In a real app, your code would do something useful based on the
// information accompanying each event.
//
return (info) =>
{
Console.WriteLine($"Callback Invoked: {name}");
Console.WriteLine(
BittrexWebsocket.Decode(info)
);
};
}
static void Main(string[] args)
{
Task task = Task.Run(
async () =>
{
string apiKey = "YOUR_API_KEY";
string apiSecret = "YOUR_API_SECRET";
string baseUrl = "https://beta.bittrex.com/signalr";
var btx = new BittrexWebsocket(
baseUrl,
CreateCallback("exchange"),
CreateCallback("order"),
CreateCallback("balance")
);
// If we successfully authenticate, we'll be subscribed to the uO and uB events.
var isAuthenticated = await btx.Authenticate(
apiKey,
BittrexWebsocket.CreateSignature(apiSecret, await btx.GetAuthContext(apiKey))
);
// Register for orderbook updates on the BTC-ETH market
await btx.SubscribeToExchangeDeltas("BTC-ETH");
});
task.Wait();
Console.WriteLine("Press enter to exit sample...");
Console.ReadLine();
}
}
}
Run in PackageManager Console to add SignalR dependency:
Install-Package Microsoft.AspNet.SignalR.Client -Version 2.3.0
Also to connect you need get Key and Secret from your account on Bittrex.
string apiKey = "YOUR_API_KEY";
string apiSecret = "YOUR_API_SECRET";
For Bitfinex you can try next code:
using System;
using WebSocketSharp;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
using (var ws = new WebSocket("wss://api.bitfinex.com/ws/2"))
{
ws.OnMessage += (sender, e) => Console.WriteLine(e.Data);
ws.Connect();
ws.Send("{\"event\":\"subscribe\", \"channel\":\"ticker\", \"pair\":\"BTCUSD\"}");
Console.ReadKey(true);
}
}
}
}
Requires dependency from Nuget:
Install-Package WebSocketSharp-NonPreRelease -Version 1.0.0
I have a simple SignalR based timer on the server which updates my dashboard panel. Everything works fine on VS2012, however when I deploy to azure or IIS7.5 it can't stablish a connection. Here are the errors from console.
This is my hub:
public class BroadCastHub : Hub
{
// Is set via the constructor on each creation
//private readonly Broadcaster _broadcaster;
private readonly TimeSpan BroadcastInterval = TimeSpan.FromMilliseconds(AppConfig.Instance.Tracing.RefreshRate * 1000);
public BroadCastHub() {
if (AppConfig.Instance.Tracing.EnableServerAutoUpdates)
{
// Start the broadcast loop
var _broadcastLoop = new Timer(
UpdatePanel,
null,
BroadcastInterval,
BroadcastInterval);
}
}
public void UpdatePanel()
{
UpdatePanel(null);
}
public void UpdatePanel(object o)
{
var appService = DependencyResolver.Current.GetService<IApplicationService>();
var applications = appService.GetSummaryCollection();
var model = applications.Select(c => new ApplicationState
{
id = c.id,
lastRunTime = c.lastRunTime.GetValueOrDefault(),
totalTraces = c.totalTraces,
status = appService.GetStatus(c.lastRunTime, c.lastTraceType, c.traceInterval)
}.InjectFrom(c)).Cast<ApplicationState>().AsQueryable();
Clients.All.updatePanel(model);
}
}
This is my JS code (Angular):
function init() {
var broadCastHub = $.connection.broadCastHub;
broadCastHub.client.updatePanel = function(apps) {
console.log('Broadcasting');
};
$.connection.hub.start()
.done(function () {
console.log('Now connected, connection ID=' + $.connection.hub.id);
})
.fail(function () { console.log('Could not Connect!'); });
//listenBroadcast();
}
EDIT:
Curiously, I saw a similar article and implemented like this:
public class Broadcaster
{
private readonly IApplicationService _applicationService;
private readonly static Lazy<Broadcaster> _instance = new Lazy<Broadcaster>(() => new Broadcaster());
// We're going to broadcast to all clients a maximum of 25 times per second
private readonly TimeSpan BroadcastInterval = TimeSpan.FromMilliseconds(AppConfig.Instance.Tracing.RefreshRate * 1000);
private readonly IHubContext _hubContext;
private Timer _broadcastLoop;
public Broadcaster()
{
_applicationService = DependencyResolver.Current.GetService<IApplicationService>();
// Save our hub context so we can easily use it
// to send to its connected clients
_hubContext = GlobalHost.ConnectionManager.GetHubContext<BroadCastHub>();
if (AppConfig.Instance.Tracing.EnableServerAutoUpdates) {
// Start the broadcast loop
_broadcastLoop = new Timer(
UpdatePanel,
null,
BroadcastInterval,
BroadcastInterval);
}
}
public void UpdatePanel(object state)
{
var applications = _applicationService.GetSummaryCollection();
var model = applications.Select(c => new ApplicationState
{
id = c.id,
lastRunTime = c.lastRunTime.GetValueOrDefault(),
totalTraces = c.totalTraces,
status = _applicationService.GetStatus(c.lastRunTime, c.lastTraceType, c.traceInterval)
}.InjectFrom(c)).Cast<ApplicationState>().AsQueryable();
_hubContext.Clients.All.updatePanel(model);
}
public static Broadcaster Instance
{
get
{
return _instance.Value;
}
}
}
My hub:
public class BroadCastHub : Hub
{
// Is set via the constructor on each creation
private readonly Broadcaster _broadcaster;
public BroadCastHub()
: this(Broadcaster.Instance)
{
}
public BroadCastHub(Broadcaster broadcaster)
{
_broadcaster = broadcaster;
}
public void UpdatePanel()
{
_broadcaster.UpdatePanel(null);
}
}
Thanks for the article, I'll take a deeper look.
I would like to implement a pub/sub application with .NET clients, so I'm testing SignalR by means of this minimal code.
This is the server:
namespace Test.SignalRComm.SimpleServer
{
using System.Threading.Tasks;
using log4net;
using SignalR;
using SignalR.Hosting.Self;
using SignalR.Hubs;
using SignalR.Infrastructure;
class Program
{
private static SignalRServer signalRServer = null;
static void Main(string[] args)
{
signalRServer = new SignalRServer();
signalRServer.Start();
System.Console.WriteLine("Press Enter to close...");
System.Console.ReadLine();
signalRServer.Stop();
}
}
public class SignalRServer
{
private string serverUrl = null;
public Server signalRServer = null;
public SignalRServer()
{
serverUrl = #"http://localhost:5001/";
signalRServer = new SignalR.Hosting.Self.Server(serverUrl);
signalRServer.EnableHubs();
}
public void Start()
{
signalRServer.Start();
}
public void Stop()
{
IConnectionManager connManager = signalRServer.DependencyResolver.Resolve<IConnectionManager>();
dynamic clients = connManager.GetClients<SignalRTestHub>();
clients.AddMessage("Test");
signalRServer.Stop();
}
}
public class SignalRTestHub : Hub, IDisconnect
{
private static readonly ILog logger = LogManager.GetLogger(typeof(SignalRTestHub));
public void Register(string token)
{
AddToGroup(token).ContinueWith(task =>
{
if (task.IsFaulted)
logger.Error(task.Exception.GetBaseException());
else
{
string message = string.Format("Client {0} registered with token <{1}>", Context.ConnectionId, token);
logger.Info(message);
}
});
}
public void Unregister(string token)
{
RemoveFromGroup(token).ContinueWith(task =>
{
if (task.IsFaulted)
logger.Error(task.Exception.GetBaseException());
else
logger.InfoFormat("Client {0} unregistered from token <{1}>", Context.ConnectionId, token);
});
}
public Task Disconnect()
{
string message = string.Format("Client {0} disconnected", Context.ConnectionId);
logger.Info(message);
return null;
}
}
}
and this is the client:
namespace Test.SignalRComm.SimpleClient
{
using System.Threading.Tasks;
using log4net;
using SignalR.Client.Hubs;
class Program
{
private static readonly ILog logger = LogManager.GetLogger(typeof(Program));
static void Main(string[] args)
{
SignalRClient client = new SignalRClient("http://localhost:5001/");
client.Start().ContinueWith(task =>
{
if (task.IsFaulted)
{
logger.Error("Failed to start!", task.Exception.GetBaseException());
}
else
{
logger.InfoFormat("Success! Connected with client connection id {0}", client.ConnectionId);
// Do more stuff here
client.Invoke("Register", "Test").ContinueWith(tsk =>
{
if (tsk.IsFaulted)
logger.Error("Failed to start!", tsk.Exception.GetBaseException());
else
logger.Info("Success! Registered!");
});
}
});
System.Console.WriteLine("Press Enter to close...");
System.Console.ReadLine();
client.Invoke("Unregister", "Test").ContinueWith(tsk =>
{
if (tsk.IsFaulted)
logger.Error("Failed to stop!", tsk.Exception.GetBaseException());
else
logger.InfoFormat("Success! Unregistered!");
});
client.Stop();
}
}
public class SignalRClient : HubConnection
{
private static readonly ILog logger = LogManager.GetLogger(typeof(SignalRClient));
IHubProxy hub = null;
public SignalRClient(string url)
: base(url)
{
hub = CreateProxy("Test.SignalRComm.SimpleServer.SignalRTestHub");
}
public Task Invoke(string methodName, params object[] args)
{
return hub.Invoke(methodName, args);
}
public void AddMessage(string data)
{
logger.InfoFormat("Received {0}!", data);
}
}
}
While invoking hub methods from client (Register and Unregister) works fine, I'm not able to call client AddMessage method from hub.
Furthermore the Disconnect method of the hub is never called when a client is closed.
What I'm doing wrong? I'm not able to find any working example.
Edit
Subscribing to hubs events on the client like this:
hub.On<string>("Notify", message => Console.Writeline("Server sent message {0}", message);
and triggering the event on the hub like this:
Clients.Notify("Something to notify");
makes the notifications from server to clients working.
I'm still unable to detect a client disconnection. I implemented the IDisconnect interface on the hub, but when a client connection stops, the Disconnect method on the hub isn't triggered.
Thanks for your help.
Take a look at how to use the .NET client here:
https://gist.github.com/1324342
And API docs here:
https://github.com/SignalR/SignalR/wiki
TL;DR you need to subscribe to specific methods, deriving from the hubConnection doesn't make any magic happen.