I am using RabbitMQ.Client(C#) to work with RabbitMQ. I am having trouble retrieving the messages from the queue once I remove and re-add the message received event handler.
consumer.Received -= OnMessageRecieved;
I have a complex system, where a windows service subscribes to the RabbitMQ queues and process the messages. There are multiple threads running for various things - timer to call PUSH api, another timer to do api authentication etc. If api authentication fails, we don't want to process the messages from the queue. We want to keep the messages in ready state. Only, when the api authentication is success, we want to process the messages. So, on failure event, we remove the event handler and on success we add it back. When we do that, the event handler is added successfully, but now the messages are not retrieved from the queue.
To simulate this, I have created a console app. I have written this in less than an hour, I know this code is very raw and dirty - please excuse me for that.
sub.StopReceiveMessages(); has the code that removes handler consumer.Received -= OnMessageRecieved. And, sub.StartReceiveMessages(); has the code that removes handler consumer.Received += OnMessageRecieved. When you add it back I thought it would work as normal. But, it doesn't hit the MessageReceived() anymore. Is it that we have to call BasicConsume again although I am using the same consumer? Any help would be appreciate.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Client = RabbitMQ.Client;
namespace RabbitMQTest
{
class Program
{
static void Main(string[] args)
{
MessageBusSubscription sub = new MessageBusSubscription();
sub.Subscription("EmployeeDataChanged", "HR", "CompanyA", 5, 5000);
sub.MessagesReceived += MessageReceived;
Console.WriteLine("Press ESC to exit");
while (!(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape))
{
// Simulating an event where we have to stop pulling the messages from the queue
sub.StopReceiveMessages();
Thread.Sleep(2000);
// After some time, the issue is resolved and now we resume reading the messages from the queue
sub.StartReceiveMessages();
}
sub.Dispose();
Environment.Exit(0);
}
private static bool MessageReceived(string topic, string subscription, List<MessageContainer> messages)
{
List<MessageContainer> data = null;
data = messages as List<MessageContainer>;
foreach (var messageContainer in data)
{
// Do something with the message
// Ack or Reject based on some logic
}
return true;
}
}
public class MessageBusSubscription : IDisposable
{
#region variables
Client.Events.EventingBasicConsumer consumer;
Client.ConnectionFactory factory;
private System.Timers.Timer _timer = null;
private Client.IModel _channel = null;
private string _topic = string.Empty;
private string _subscription = string.Empty;
int batchCounter = 0;
int batchSize = 0;
ManualResetEvent _waitHandle = new ManualResetEvent(false);
bool _disposing = false;
bool _isSubscribed = false;
List<MessageContainer> messages = new List<MessageContainer>();
private object _processMessageLocker = new object();
public event Func<string, string, List<MessageContainer>, bool> MessagesReceived;
#endregion
public MessageBusSubscription()
{
Client.IConnection conn = GetConnection();
_channel = conn.CreateModel();
}
public void Subscription(string exchangeName, string queueName, string routingKey, int batchSize, double batchInterval)
{
_topic = exchangeName;
_subscription = queueName;
DeclareExchangeAndQueue(exchangeName, queueName, routingKey);
if (batchInterval > 0 && batchSize > 1)
{
_timer = new System.Timers.Timer(batchInterval);
_timer.Elapsed += (o, e) => {
ProcessMessagesReceived(exchangeName, queueName, true);
};
}
Subscribe(routingKey, exchangeName, queueName, batchSize, batchInterval);
}
public Task Subscribe(string routingKey, string topic, string subscription, int _batchSize, double batchInterval)
{
try
{
consumer = new Client.Events.EventingBasicConsumer(_channel);
batchCounter = 0;
batchSize = _batchSize;
consumer.Received += OnMessageRecieved;
_isSubscribed = true;
//RabbitMQ PUSH implementation using RabbitMQ.Client library
var t = Task.Factory.StartNew(() =>
{
try
{
if (_timer != null)
{
_timer.Start();
}
var queueName = string.Join(".", routingKey, topic, subscription);
if (!_disposing)
{
_channel.BasicConsume(queueName, false, consumer);
_waitHandle.WaitOne();
}
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
}
if (_channel != null)
{
if (_channel.IsOpen)
_channel.Close();
_channel.Dispose();
}
}
catch (Exception ex)
{
}
});
return t;
}
catch (Exception ex)
{
var exTask = new Task(() => { throw new AggregateException(ex); });
exTask.RunSynchronously();
return exTask;
}
}
public void OnMessageRecieved(Client.IBasicConsumer sender, Client.Events.BasicDeliverEventArgs e)
{
try
{
string sourceExchange = string.Empty;
string sourceQueue = string.Empty;
string body = Encoding.ASCII.GetString(e.Body);
string routingKey = e.RoutingKey;
ulong deliveryTag = e.DeliveryTag;
sourceExchange = "";
sourceQueue = "";
MessageContainer msgContainer = new MessageContainer();
msgContainer.Message = body;
batchCounter++;
msgContainer.DeliveryTag = deliveryTag;
lock (_processMessageLocker)
{
messages.Add(msgContainer);
ProcessMessagesReceived(_topic, _subscription, false);
}
}
catch (Exception ex)
{
}
}
public void ProcessMessagesReceived(string topic, string subscription, bool hasTimerElapsed)
{
try
{
// if it's the last message in the batch, or the interval has elapsed
if ((batchCounter % batchSize == 0 && messages.Count > 0) || (hasTimerElapsed && messages.Count > 0))
{
if (_timer != null)
{
_timer.Stop();
}
lock (_processMessageLocker)
{
// process the message
if (!MessagesReceived(topic, subscription, messages))
{
throw new Exception("Message processing exception - look in the default error queue (broker)");
}
messages.Clear();
}
batchCounter = 0;
if (_timer != null)
{
_timer.Start();
}
}
}
catch (Exception ex)
{
throw ex;
}
}
public Client.IConnection GetConnection()
{
factory = new Client.ConnectionFactory();
factory.UserName = "guest";
factory.Password = "guest";
factory.VirtualHost = "/";
factory.HostName = "localhost";
factory.Protocol = Client.Protocols.AMQP_0_9_1;
factory.Port = 5673;
return factory.CreateConnection();
}
public void DeclareExchangeAndQueue(string exchangeName, string queueName, string routingKey)
{
using (var exchangeConn = factory.CreateConnection())
using (Client.IModel channel = exchangeConn.CreateModel())
{
channel.ExchangeDeclare(exchangeName, Client.ExchangeType.Direct);
var queue = String.Join(".", routingKey, exchangeName, queueName);
channel.QueueDeclare(queue, false, false, false, null);
channel.QueueBind(queue, exchangeName, routingKey, null);
}
}
public void StartReceiveMessages()
{
if (_timer != null && !_isSubscribed)
{
_timer.Start();
}
if (consumer != null && !_isSubscribed)
{
consumer.Received += OnMessageRecieved;
_isSubscribed = true;
}
}
public void StopReceiveMessages()
{
if (_timer != null)
{
_timer.Stop();
}
if (consumer != null)
{
consumer.Received -= OnMessageRecieved;
_isSubscribed = false;
}
}
public void Dispose()
{
_disposing = true;
_waitHandle.Set();
_waitHandle?.Dispose();
_waitHandle = null;
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
}
}
}
public class MessageContainer
{
public ulong DeliveryTag { get; set; }
public string Message { get; set; }
}
}
Don't unsubscribe from the Received event, instead use the BasicCancel method to stop consuming messages, then use BasicConsume again to start consuming.
A lot of the thread synchronization code as well as running the consumer in a Task isn't really the best practice. If you'd like further assistance with this code, save it in a git repository or gist somewhere and follow-up on the official mailing list.
NOTE: the RabbitMQ team monitors the rabbitmq-users mailing list and only sometimes answers questions on StackOverflow.
Related
I have Pinger Application that gets all ping informations of servers written in the Servers.txt file
Half of them working servers, half of them not. I want to make my application write working servers to the another .txt file called WorkingServers.txt located in C:\Program Files\Servers folder
Here is my code:
using System;
using System.IO;
using System.Threading;
namespace AnchovyMultiPing
{
class Program
{
static void Main(string[] args)
{
var servers = File.ReadAllLines(#"C:\Program Files\Servers\Servers.txt");
foreach (var line in servers)
{
string currentServer = line;
if (args == null) throw new ArgumentNullException("args");
var waiter = new ManualResetEventSlim(false);
var pingData = new MultiPing(new[] { line }, waiter, 300);
waiter.Wait();
Console.WriteLine(pingData.GetPingInformation());
}
Console.WriteLine();
Console.WriteLine("- * - * - The Job is Done - * - * -");
Console.ReadLine();
}
}
}
and MultiPing.cs:
using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
namespace AnchovyMultiPing
{
internal sealed class MultiPing : AbstractMultiPing
{
private int Timeout { get; set; }
private string[] Hosts { get; set; }
private int _count;
private ManualResetEventSlim Waiter { get; set; }
private readonly byte[] _buffer = Encoding.ASCII.GetBytes("aaa");
private Dictionary<string, long> _table = new Dictionary<string, long>();
private class Parameters
{
public String Host { get; set; }
public ManualResetEventSlim Event { get; set; }
}
public MultiPing(string[] hosts, ManualResetEventSlim waiter, int timeout = 12000)
{
Hosts = hosts;
Waiter = waiter;
Timeout = timeout;
RequestTime();
}
public override IMultiPings RequestTime()
{
try
{
_count = 0;
_table = new Dictionary<string, long>();
foreach (string host in Hosts)
{
using (var pingSender = new Ping())
{
pingSender.PingCompleted += PingCompletedCallback;
var options = new PingOptions(64, true);
try
{
pingSender.SendAsync(host, Timeout, _buffer, options,
new Parameters {Host = host, Event = Waiter});
}
catch
{
_count += 1;
if (_count == Hosts.Length)
Waiter.Set();
}
}
}
}
catch (MultiPingException ex)
{
Console.Write("RequestTime Exception");
Console.ReadKey();
}
return this;
}
//[MethodImpl(MethodImplOptions.Synchronized)] leaving this in favour of the operating system scheduler for better performance.
private void PingCompletedCallback(object sender, PingCompletedEventArgs e)
{
try
{
_count += 1;
PingReply reply = e.Reply;
if (reply != null && reply.Address != null && reply.Address.ToString() != "0.0.0.0")
{
if (_count > 0)
{
try
{
if (!_table.ContainsKey(reply.Address.ToString()))
_table.Add(((Parameters) e.UserState).Host, reply.RoundtripTime);
}
catch (NullReferenceException ex)
{
// catch null exception
throw new MultiPingException("Ping round trip time is null");
}
}
}
if (_count == Hosts.Length)
{
((Parameters) e.UserState).Event.Set();
}
if (e.Error != null)
{
Console.WriteLine("Ping failed:");
Console.WriteLine(e.Error.ToString());
((ManualResetEventSlim) e.UserState).Set();
}
}
catch (MultiPingException ex)
{
Console.Write("Exception");
Console.ReadKey();
}
}
public override String GetPingInformation()
{
var build = new StringBuilder();
foreach (string host in _table.Keys)
{
build.AppendLine(string.Format("{0} : {1}", host, _table[host]));
}
return build.ToString();
}
public override String GetIp()
{
string ip = "";
long time = -1L;
foreach (string host in _table.Keys)
{
long roundTime = _table[host];
if ((time == -1L) || (roundTime >= 0 && roundTime < time))
{
time = roundTime;
ip = host;
}
}
return ip;
}
}
}
Declare new string at the beginning var log = string.Empty; At the end of the iteration add the server name to it, if the ping is successful. I don't know how the MultiPing class looks like, so I added artificial IsSuccessful, but what I mean is something like this:
waiter.Wait();
if(pingData.IsSuccessful)
{
log += line;
}
Console.WriteLine(pingData.GetPingInformation());
Then as the last line of your program save everything to the other file.
File.AppendAllText("C:\Program Files\Servers\WorkingServers.txt", log);
I have been given the task to create a interface where I receive data through socket from the sender, for this purpose I am using NetMQ PushSocket for the sender side and then I receive the data at client side sung PullSocket and I have to update the UI (WPF app) when data is received so I receive data using poller in ReceiveReady event of the PullSocket when I do this in a seperate service class and call that class in UI ViewModel the UI thread hangs, so I use Poller.Run in a task, now the problem is that when I stop the poller and then restart it again it doesn't call the ReceiveReady event
Here is the ReceiverService for receiving the data.
public class ReceiverService
{
string msg;
string _address;
int _port;
PullSocket receiver;
NetMQPoller poller;
private MapViewModel ViewModel { get; set; }
public ReceiverService(MapViewModel mapViewModel, int port = 5555)
{
_address = GetComputerLanIP();
_port = port;
receiver = new PullSocket($"tcp://{_address}:{_port}");
receiver.Options.Linger = TimeSpan.Zero;
this.ViewModel = mapViewModel;
poller = new NetMQPoller { receiver };
receiver.ReceiveReady += receiver_ReceiveReady;
}
public void Start()
{
receiver.Connect($"tcp://{_address}:{_port}");
poller.Run();
}
public void Stop()
{
receiver.Disconnect($"tcp://{_address}:{_port}");
poller.Stop();
}
private void receiver_ReceiveReady(object sender, NetMQSocketEventArgs e)
{
// receive won't block as a message is ready
msg = e.Socket.ReceiveFrameString();
// send a response
if (!string.IsNullOrEmpty(msg))
{
try
{
//Updaing the ViewModel here
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
private string GetComputerLanIP()
{
string strHostName = Dns.GetHostName();
IPHostEntry ipEntry = Dns.GetHostEntry(strHostName);
foreach (var ipAddress in ipEntry.AddressList)
{
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
return ipAddress.ToString();
}
}
return "";
}
private string GetValueFromMessage(string identifier)
{
msg.Replace("{", "");
msg.Replace("}", "");
identifier = /*" " + */identifier + " = ";
try
{
int index = msg.IndexOf(identifier) + identifier.Length;
if (index != -1)
{
int index2 = msg.IndexOf(";", index);
if (index2 == -1)
{
index2 = msg.Length;
}
return msg.Substring(index, index2 - index);
}
}
catch (IndexOutOfRangeException ex)
{
return null;
}
return null;
}
}
and in my ViewModel I have set commands for these
private void StartReceiver()
{
Task.Run(() => ReceiverService.Start());
}
private void StopReceiver()
{
Task.Run(() => ReceiverService.Stop());
}
What am I doing wrong? I am new to NetMQ and WPF. TIA
at first it would be good to make a task inside ReceiverService, kind of an ActorModel, because in the end if You would like to reuse it anywhere You need to remember that You should creat a Task first.
always it would be good to have socket in using statement, because You should always close socket if You are not using it
public async Task StartAsync() {
await Task.Run(() => ThreadBody())
}
public void Stop()
{
_poller.Stop();
}
private void ThreadBody()
{
using (PullSocket receiverSocket = new PullSocket())
using (_poller = new NetMQPoller())
{
receiverSocket.Connect($"tcp://{_address}:{_port}");
receiverSocket.ReceiveReady += receiver_ReceiveReady;
_poller.Add(receiverSocket);
_poller.Run();
}
}
I have a v4.0.0.1 implementation of Somdron's "Reliable Pub-Sub" pattern for communication between two parts of a new application. This application will have a "Server" (the engine that does all the heavy calculations) and "Clients" that will send requests and get information on progress back from the server.
The problem I have with my current version of "Reliable Pub-Sub" is that I don't seem to have a proper way for sending requests to the sever from the client. Let me start by showing you the code:
SERVER:
using NetMQ;
using NetMQ.Sockets;
using System;
using System.Linq;
namespace Demo.Messaging.Server
{
public class ZeroMqMessageServer : IDisposable
{
private const string WELCOME_MESSAGE = "WM";
private const string HEARTBEAT_MESSAGE = "HB";
private const string PUBLISH_MESSAGE_TOPIC = "PUB";
private readonly TimeSpan HEARTBEAT_INTERVAL = TimeSpan.FromSeconds(2);
private NetMQActor actor;
private NetMQTimer heartbeatTimer;
private XPublisherSocket publisher;
private NetMQPoller poller;
public ZeroMqMessageServer(string address)
{
Address = address;
actor = NetMQActor.Create(Start);
}
private void Start(PairSocket shim)
{
using (publisher = new XPublisherSocket())
{
publisher.SetWelcomeMessage(WELCOME_MESSAGE);
publisher.Bind(Address);
//publisher.ReceiveReady -= DropPublisherSubscriptions;
publisher.ReceiveReady += DropPublisherSubscriptions;
heartbeatTimer = new NetMQTimer(HEARTBEAT_INTERVAL);
heartbeatTimer.Elapsed += OnHeartbeatTimeElapsed;
shim.ReceiveReady += OnShimReceiveReady;
shim.SignalOK(); // Let the actor know we are ready to work.
poller = new NetMQPoller() { publisher, shim, heartbeatTimer };
poller.Run();
}
}
private void DropPublisherSubscriptions(object sender, NetMQSocketEventArgs e)
{
publisher.SkipMultipartMessage();
}
private void OnHeartbeatTimeElapsed(object sender, NetMQTimerEventArgs e)
{
publisher.SendFrame(HEARTBEAT_MESSAGE);
}
private void OnShimReceiveReady(object sender, NetMQSocketEventArgs e)
{
var socket = e.Socket;
string command = socket.ReceiveFrameString();
if (command == PUBLISH_MESSAGE_TOPIC)
{
// Forward the message to the publisher.
NetMQMessage message = socket.ReceiveMultipartMessage();
publisher.SendMultipartMessage(message);
}
else if (command == NetMQActor.EndShimMessage)
{
// Dispose command received, stop the poller.
poller.Stop();
}
}
public void PublishMessage(NetMQMessage message)
{
// We can use actor like NetMQSocket and publish messages.
actor.SendMoreFrame(PUBLISH_MESSAGE_TOPIC)
.SendMultipartMessage(message);
}
public string Address { get; private set; }
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
actor?.Dispose();
publisher?.Dispose();
poller?.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
}
CLIENT:
using NetMQ;
using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using Messaging.Helpers;
namespace Demo.Messaging.Client
{
public class ZeroMqMessageClient : IDisposable
{
private string SUBSCRIBE_COMMAND = "S";
private const string WELCOME_MESSAGE = "WM";
private const string HEARTBEAT_MESSAGE = "HB";
private const string PUBLISH_MESSAGE_TOPIC = "PUB";
private readonly TimeSpan TIMEOUT = TimeSpan.FromSeconds(5);
private readonly TimeSpan RECONNECTION_PERIOD = TimeSpan.FromSeconds(5);
private readonly string[] addressCollection;
private List<string> subscriptions = new List<string>();
private NetMQTimer timeoutTimer;
private NetMQTimer reconnectionTimer;
private NetMQActor actor;
private SubscriberSocket subscriber;
private PairSocket shim;
private NetMQPoller poller;
public ZeroMqMessageClient(params string[] addresses)
{
addressCollection = addresses;
actor = NetMQActor.Create(Start);
}
private void Start(PairSocket shim)
{
this.shim = shim;
shim.ReceiveReady += OnShimReceiveReady;
timeoutTimer = new NetMQTimer(TIMEOUT);
timeoutTimer.Elapsed += OnTimeoutTimerElapsed;
reconnectionTimer = new NetMQTimer(RECONNECTION_PERIOD);
reconnectionTimer.Elapsed += OnReconnectionTimerElapsed;
poller = new NetMQPoller() { shim, timeoutTimer, reconnectionTimer };
shim.SignalOK();
Connect();
poller.Run();
if (subscriber != null)
subscriber.Dispose();
}
private void Connect()
{
using (NetMQPoller tmpPoller = new NetMQPoller())
{
List<SubscriberSocket> socketCollection = new List<SubscriberSocket>();
SubscriberSocket connectedSocket = null;
EventHandler<NetMQSocketEventArgs> messageHandler = (s, e) =>
{
connectedSocket = (SubscriberSocket)e.Socket;
tmpPoller.Stop();
};
// We cancel the poller without setting the connected socket.
NetMQTimer tmpTimeoutTimer = new NetMQTimer(TIMEOUT);
tmpTimeoutTimer.Elapsed += (s, e) => tmpPoller.Stop();
tmpPoller.Add(tmpTimeoutTimer);
// Attempt to subscribe to the supplied list of addresses.
foreach (var address in addressCollection)
{
SubscriberSocket socket = new SubscriberSocket();
socketCollection.Add(socket);
//socket.ReceiveReady -= messageHandler;
socket.ReceiveReady += messageHandler;
tmpPoller.Add(socket);
// Subscribe to welcome messages.
socket.Subscribe(WELCOME_MESSAGE);
socket.Connect(address);
}
tmpPoller.Run(); // Block and wait for connection.
// We should have an active socket/conection.
if (connectedSocket != null)
{
// Remove the connected socket from the collection.
socketCollection.Remove(connectedSocket);
ZeroMqHelpers.CloseConnectionsImmediately(socketCollection);
// Set the active socket.
subscriber = connectedSocket;
//subscriber.SkipMultipartMessage(); // This skips the welcome message.
// Subscribe to subscriptions.
subscriber.Subscribe(HEARTBEAT_MESSAGE);
foreach (var subscription in subscriptions)
subscriber.Subscribe(subscription);
// Remove start-up handler, now handle messages properly.
subscriber.ReceiveReady -= messageHandler;
subscriber.ReceiveReady += OnSubscriberReceiveReady;
poller.Add(subscriber);
// Reset timers.
timeoutTimer.Enable = true;
reconnectionTimer.Enable = false;
}
else // We need to attempt re-connection.
{
// Close all existing connections.
ZeroMqHelpers.CloseConnectionsImmediately(socketCollection);
timeoutTimer.Enable = false;
reconnectionTimer.Enable = true;
}
}
}
private void OnShimReceiveReady(object sender, NetMQSocketEventArgs e)
{
string command = e.Socket.ReceiveFrameString();
if (command == NetMQActor.EndShimMessage)
{
poller.Stop();
}
else if (command == SUBSCRIBE_COMMAND)
{
string topic = e.Socket.ReceiveFrameString();
subscriptions.Add(topic);
if (subscriber != null)
subscriber.Subscribe(topic);
}
}
private void OnTimeoutTimerElapsed(object sender, NetMQTimerEventArgs e)
{
if (subscriber != null)
{
poller.Remove(subscriber);
subscriber.Dispose();
subscriber = null;
Connect();
}
}
private void OnReconnectionTimerElapsed(object sender, NetMQTimerEventArgs e)
{
// We re-attempt connection.
Connect();
}
private void OnSubscriberReceiveReady(object sender, NetMQSocketEventArgs e)
{
// Here we just forwward the message on to the actor.
var message = subscriber.ReceiveMultipartMessage();
string topic = message[0].ConvertToString();
// Let us see what is in the message.
if (message.Count() > 1)
{
string content = message[1].ConvertToString();
Console.WriteLine($"ZMQ_ALT - {topic}:: {content}");
}
if (topic == WELCOME_MESSAGE)
{
// Disconnection has occurred we might want to restore state from a snapshot.
}
else if (topic == HEARTBEAT_MESSAGE)
{
// We got a heartbeat, lets postponed the timer.
timeoutTimer.Enable = false;
timeoutTimer.Enable = true;
}
else
{
shim.SendMultipartMessage(message);
}
}
public void Subscribe(string topic)
{
actor.SendMoreFrame(SUBSCRIBE_COMMAND).SendFrame(topic);
}
public NetMQMessage ReceiveMessage()
{
return actor.ReceiveMultipartMessage();
}
public void PublishMessage(NetMQMessage message)
{
actor.SendMoreFrame(PUBLISH_MESSAGE_TOPIC)
.SendMultipartMessage(message);
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
actor?.Dispose();
subscriber?.Dispose();
shim?.Dispose();
poller?.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
}
Now, I can send messages from the server to the client which is awesome and the client using the following code from the main method in two separate console applications
Program.cs for SERVER:
class Program
{
static void Main(string[] args)
{
using (ZeroMqMessageServer server = new ZeroMqMessageServer("tcp://127.0.0.1:6669"))
{
while (true)
{
NetMQMessage message = new NetMQMessage();
message.Append("A");
message.Append(new Random().Next().ToString());
server.PublishMessage(message);
Thread.Sleep(200);
}
}
}
}
Program.cs for CLIENT:
class Program
{
static void Main(string[] args)
{
Task.Run(() =>
{
using (ZeroMqMessageClient client = new ZeroMqMessageClient("tcp://127.0.0.1:6669"))
{
client.Subscribe(String.Empty);
while (true) { }
}
});
Console.ReadLine();
}
}
The client correctly auto-detects dropped connections and reconnects, fantastic little pattern.
However, this pattern out-of-the-box does not allow the client to send messages to the server. So in the client I have added the following code
public void PublishMessage(NetMQMessage message)
{
actor.SendMoreFrame(PUBLISH_MESSAGE_TOPIC)
.SendMultipartMessage(message);
}
and in the client I have changed the publisher.ReceiveReady += DropPublisherSubscriptions; event handler to
private void DropPublisherSubscriptions(object sender, NetMQSocketEventArgs e)
{
var message = e.Socket.ReceiveMultipartMessage();
string topic = message[0].ConvertToString();
Console.WriteLine($"TOPIC = {topic}");
// Let us see what is in the message.
if (message.Count() > 1)
{
string content = message[1].ConvertToString();
Console.WriteLine($"TEST RECIEVE FROM CLIENT - {topic}:: {content}");
}
publisher.SkipMultipartMessage();
}
but this does not seem to handle my messages. It receives the heartbeats and welcome messages, but I am not doing this right.
How can I enable/facilitate the client to talk to the server without breaking what I have already?
Thanks for your time.
I have a WPF application that starts a new process using Process.Start(ProcessStartInfo info).
How do I get the group process ID of the process so that I can send a Ctrl+C signal using GenerateConsoleCtrlEvent? https://msdn.microsoft.com/en-us/library/windows/desktop/ms683155%28v=vs.85%29.aspx
However, I can't seem to find the group process ID of the console window in the new process that is created. It has a session ID for the cur windows user and a process ID.
edit: I finally got my program to work but I still never found a true answer to my real question.
I was able to send ctrl c to a process by using GenerateConsoleCtrlEvent to broadcast to all processes in a console.
However, I was not able to figure out how to get the process group of a process that is running. You can of course save the process group if you create a new process (it should be the ID of the process that called createprocess with the creation flag for create new process group). However, I cannot find anything related to actually grabbing this ID if you are not making a new group yourself and just wish to know the group that a process belongs to. Surely this information is stored somewhere and can be retrieved!
I can get the parent ID in windows NT versions using this function:
Fetching parent process Id from child process
However, this does not guarantee the same process group. I am starting to conclude that windows does not have get process group id from process id function.
Linux has a simple getpgrp function that does want I am looking for. I don't understand why windows has a process group if I can't get the value of it
The documentation for GenerateConsoleCtrlEvent states (emphasis mine):
The identifier of the process group to receive the signal. A process group is created when the CREATE_NEW_PROCESS_GROUP flag is specified in a call to the CreateProcess function. The process identifier of the new process is also the process group identifier of a new process group.
So if your processes are in a group, the WPF application's PID should be the group ID.
Rather than using GenerateConsoleCtrlEvent, here is how I have found to send CTRL-C to a process. FYI, in this case, I didn't ever need to find the group process ID.
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
public class ConsoleAppManager
{
private readonly string appName;
private readonly Process process = new Process();
private readonly object theLock = new object();
private SynchronizationContext context;
private string pendingWriteData;
public ConsoleAppManager(string appName)
{
this.appName = appName;
this.process.StartInfo.FileName = this.appName;
this.process.StartInfo.RedirectStandardError = true;
this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
this.process.StartInfo.RedirectStandardInput = true;
this.process.StartInfo.RedirectStandardOutput = true;
this.process.EnableRaisingEvents = true;
this.process.StartInfo.CreateNoWindow = true;
this.process.StartInfo.UseShellExecute = false;
this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
this.process.Exited += this.ProcessOnExited;
}
public event EventHandler<string> ErrorTextReceived;
public event EventHandler ProcessExited;
public event EventHandler<string> StandartTextReceived;
public int ExitCode
{
get { return this.process.ExitCode; }
}
public bool Running
{
get; private set;
}
public void ExecuteAsync(params string[] args)
{
if (this.Running)
{
throw new InvalidOperationException(
"Process is still Running. Please wait for the process to complete.");
}
string arguments = string.Join(" ", args);
this.process.StartInfo.Arguments = arguments;
this.context = SynchronizationContext.Current;
this.process.Start();
this.Running = true;
new Task(this.ReadOutputAsync).Start();
new Task(this.WriteInputTask).Start();
new Task(this.ReadOutputErrorAsync).Start();
}
public void Write(string data)
{
if (data == null)
{
return;
}
lock (this.theLock)
{
this.pendingWriteData = data;
}
}
public void WriteLine(string data)
{
this.Write(data + Environment.NewLine);
}
protected virtual void OnErrorTextReceived(string e)
{
EventHandler<string> handler = this.ErrorTextReceived;
if (handler != null)
{
if (this.context != null)
{
this.context.Post(delegate { handler(this, e); }, null);
}
else
{
handler(this, e);
}
}
}
protected virtual void OnProcessExited()
{
EventHandler handler = this.ProcessExited;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
protected virtual void OnStandartTextReceived(string e)
{
EventHandler<string> handler = this.StandartTextReceived;
if (handler != null)
{
if (this.context != null)
{
this.context.Post(delegate { handler(this, e); }, null);
}
else
{
handler(this, e);
}
}
}
private void ProcessOnExited(object sender, EventArgs eventArgs)
{
this.OnProcessExited();
}
private async void ReadOutputAsync()
{
var standart = new StringBuilder();
var buff = new char[1024];
int length;
while (this.process.HasExited == false)
{
standart.Clear();
length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
standart.Append(buff.SubArray(0, length));
this.OnStandartTextReceived(standart.ToString());
Thread.Sleep(1);
}
this.Running = false;
}
private async void ReadOutputErrorAsync()
{
var sb = new StringBuilder();
do
{
sb.Clear();
var buff = new char[1024];
int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
sb.Append(buff.SubArray(0, length));
this.OnErrorTextReceived(sb.ToString());
Thread.Sleep(1);
}
while (this.process.HasExited == false);
}
private async void WriteInputTask()
{
while (this.process.HasExited == false)
{
Thread.Sleep(1);
if (this.pendingWriteData != null)
{
await this.process.StandardInput.WriteLineAsync(this.pendingWriteData);
await this.process.StandardInput.FlushAsync();
lock (this.theLock)
{
this.pendingWriteData = null;
}
}
}
}
}
Then, in actually running the process and sending the CTRL-C in my main app:
DateTime maxStartDateTime = //... some date time;
DateTime maxEndDateTime = //... some later date time
var duration = maxEndDateTime.Subtract(maxStartDateTime);
ConsoleAppManager appManager = new ConsoleAppManager("myapp.exe");
string[] args = new string[] { "args here" };
appManager.ExecuteAsync(args);
await Task.Delay(Convert.ToInt32(duration.TotalSeconds * 1000) + 20000);
if (appManager.Running)
{
// If stilll running, send CTRL-C
appManager.Write("\x3");
}
For details, please see Redirecting standard input of console application
I've been trying to implement a windows service that would keep vpn connection alive. I've found that it is possible to achieve using DotRas library by subscribing to RasConnectionWatcher.Disconnected event:
public class SampleService {
public SampleService() {
this.shutdownEvent = new ManualResetEvent(false);
this.connectionWatcher = new RasConnectionWatcher();
this.connectionWatcher.Disconnected += onVpnDisconnected;
}
// redial
void onVpnDisconnected(Object sender, RasConnectionEventArgs e) {
this.DialUp();
}
void DialUp() {
// connection setup is omitted
// keep the handle of the connection
this.connectionWatcher.Handle = dialer.Dial();
}
public void Start() {
this.thread = new Thread(WorkerThreadFunc);
this.thread.IsBackground = true;
this.thread.Start();
}
public void Stop() {
this.shutdownEvent.Set();
if(!this.thread.Join(3000)) this.thread.Abort();
}
private void WorkerThreadFunc() {
this.DialUp();
while(!this.shutdownEvent.WaitOne(0)) Thread.Sleep(1000);
}
}
When I start the service vpn connection opens without any problem, but when I manually interrupt the connection it seems that Disconnected event doesn't fire up.
solution 1
Found similar question/answer here:
http://social.msdn.microsoft.com/Forums/en-US/56ab2d0d-2425-4d76-81fc-04a1e1136141/ras-connection-application-and-service?forum=netfxnetcom.
solution 2
Got an answer from Jeff Winn yesterday:
https://dotras.codeplex.com/discussions/547038
public class VpnKeeperService : IService {
private ManualResetEvent shutdownEvent;
private RasConnectionWatcher connWatcher;
private Thread thread;
public VpnKeeperService() {
this.shutdownEvent = new ManualResetEvent(false);
this.connWatcher = new RasConnectionWatcher();
this.connWatcher.EnableRaisingEvents = true;
this.connWatcher.Disconnected += (s, args) => { this.DialUp(); };
}
Boolean DialUp() {
try {
using(var phoneBook = new RasPhoneBook()) {
var name = VpnConfig.GetConfig().ConnectionName;
var user = VpnConfig.GetConfig().Username;
var pass = VpnConfig.GetConfig().Password;
var pbPath = VpnConfig.GetConfig().PhoneBookPath;
phoneBook.Open(pbPath);
var entry = phoneBook.Entries.FirstOrDefault(e => e.Name.Equals(name));
if(entry != null) {
using(var dialer = new RasDialer()) {
dialer.EntryName = name;
dialer.Credentials = new NetworkCredential(user, pass);
dialer.PhoneBookPath = pbPath;
dialer.Dial();
}
}
else throw new ArgumentException(
message: "entry wasn't found: " + name,
paramName: "entry"
);
}
return true;
}
catch {
// log the exception
return false;
}
}
public void Start() {
this.thread = new Thread(WorkerThreadFunc);
this.thread.Name = "vpn keeper";
this.thread.IsBackground = true;
this.thread.Start();
}
public void Stop() {
this.shutdownEvent.Set();
if(!this.thread.Join(3000)) {
this.thread.Abort();
}
}
private void WorkerThreadFunc() {
if(this.DialUp()) {
while(!this.shutdownEvent.WaitOne(0)) {
Thread.Sleep(1000);
}
}
}
}
Hope it helps someone.