im currently working on a TCP Server using Sockets in C#.
I encountered a problem and i'm not able to solve it..
My Server accepts connections and creates a new NetEntity for each client.
These NetEntities handle Sending/Receive packets and are updated in a loop that ticks 30 times per second.
Problem :
After the 4th client is connected i receive this exception(only on the 4th NetEntity).
An asynchronous socket operation is already in progress using this SocketAsyncEventArgs instance.
If i use if (Interlocked.Read(ref m_sending) == 1) return; to make sure it is done sending, the 4th NetEntity never send any data.
When deleting this line the exception occurs.
I just cant wrap my head around using SendAsync and SocketAsyncEventArgs properly with a fixed tickrate.
public void Send(Packet packet)
{
OutgoingQueue.Enqueue(packet);
}
private void QueueSend()
{
Interlocked.Exchange(ref m_sending, 1);
var sendTask = new Task(Send);
sendTask.ContinueWith((t) => t.Dispose());
sendTask.Start();
}
private void Send()
{
if (Interlocked.Read(ref m_sending) == 1) return;
if (!Socket.Connected) return;
if (OutgoingQueue.TryDequeue(out var packet))
{
packet.Serialize(m_packetWriter);
}
var writerBuffer = m_packetWriter.ToArray();
Array.Copy(writerBuffer, m_sendBuffer, writerBuffer.Length);
try
{
SendArgs.SetBuffer(SendArgs.Offset, writerBuffer.Length);
if (!Socket.SendAsync(SendArgs))
{
SendCompleted(Socket, SendArgs);
}
}
catch (SocketException)
{
//Client disconnect
}
catch (Exception ex)
{
Log.Error($"NetEntity {m_id} send exception: {ex.Message} - {ex.StackTrace} - {ex.InnerException}");
}
}
private void SendCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
if (OutgoingQueue.Count > 0)
{
//QueueSend();
}
m_packetWriter.Reset();
}
else
{
Interlocked.Exchange(ref m_sending, 0);
}
}
Setup looks like this
SendArgs = new SocketAsyncEventArgs();
SendArgs.SetBuffer(m_sendBuffer, 0, BufferSize);
SendArgs.Completed += SendCompleted;
I appreciate any help and code Improvements.
Thanks!
I'm trying to build a command line chat room where the server is handling the connections and repeating input from one client back to all the other clients.
Currently the server is able to take in input from multiple clients, but can only send information back to those clients individually. I think my problem is that each connection is being handled on an individual thread. How would I allow for the threads to communicate with each other or be able to send data to each thread?
Server code:
namespace ConsoleApplication
{
class TcpHelper
{
private static object _lock = new object();
private static List<Task> _connections = new List<Task>();
private static TcpListener listener { get; set; }
private static bool accept { get; set; } = false;
private static Task StartListener()
{
return Task.Run(async () =>
{
IPAddress address = IPAddress.Parse("127.0.0.1");
int port = 5678;
listener = new TcpListener(address, port);
listener.Start();
Console.WriteLine($"Server started. Listening to TCP clients at 127.0.0.1:{port}");
while (true)
{
var tcpClient = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client has connected");
var task = StartHandleConnectionAsync(tcpClient);
if (task.IsFaulted)
task.Wait();
}
});
}
// Register and handle the connection
private static async Task StartHandleConnectionAsync(TcpClient tcpClient)
{
// start the new connection task
var connectionTask = HandleConnectionAsync(tcpClient);
// add it to the list of pending task
lock (_lock)
_connections.Add(connectionTask);
// catch all errors of HandleConnectionAsync
try
{
await connectionTask;
}
catch (Exception ex)
{
// log the error
Console.WriteLine(ex.ToString());
}
finally
{
// remove pending task
lock (_lock)
_connections.Remove(connectionTask);
}
}
private static async Task HandleConnectionAsync(TcpClient client)
{
await Task.Yield();
{
using (var networkStream = client.GetStream())
{
if (client != null)
{
Console.WriteLine("Client connected. Waiting for data.");
StreamReader streamreader = new StreamReader(networkStream);
StreamWriter streamwriter = new StreamWriter(networkStream);
string clientmessage = "";
string servermessage = "";
while (clientmessage != null && clientmessage != "quit")
{
clientmessage = await streamreader.ReadLineAsync();
Console.WriteLine(clientmessage);
servermessage = clientmessage;
streamwriter.WriteLine(servermessage);
streamwriter.Flush();
}
Console.WriteLine("Closing connection.");
networkStream.Dispose();
}
}
}
}
public static void Main(string[] args)
{
// Start the server
Console.WriteLine("Hit Ctrl-C to close the chat server");
TcpHelper.StartListener().Wait();
}
}
}
Client Code:
namespace Client2
{
public class Program
{
private static void clientConnect()
{
TcpClient socketForServer = new TcpClient();
bool status = true;
string userName;
Console.Write("Input Username: ");
userName = Console.ReadLine();
try
{
IPAddress address = IPAddress.Parse("127.0.0.1");
socketForServer.ConnectAsync(address, 5678);
Console.WriteLine("Connected to Server");
}
catch
{
Console.WriteLine("Failed to Connect to server{0}:999", "localhost");
return;
}
NetworkStream networkStream = socketForServer.GetStream();
StreamReader streamreader = new StreamReader(networkStream);
StreamWriter streamwriter = new StreamWriter(networkStream);
try
{
string clientmessage = "";
string servermessage = "";
while (status)
{
Console.Write(userName + ": ");
clientmessage = Console.ReadLine();
if ((clientmessage == "quit") || (clientmessage == "QUIT"))
{
status = false;
streamwriter.WriteLine("quit");
streamwriter.WriteLine(userName + " has left the conversation");
streamwriter.Flush();
}
if ((clientmessage != "quit") && (clientmessage != "quit"))
{
streamwriter.WriteLine(userName + ": " + clientmessage);
streamwriter.Flush();
servermessage = streamreader.ReadLine();
Console.WriteLine("Server:" + servermessage);
}
}
}
catch
{
Console.WriteLine("Exception reading from the server");
}
streamreader.Dispose();
networkStream.Dispose();
streamwriter.Dispose();
}
public static void Main(string[] args)
{
clientConnect();
}
}
}
The main thing wrong in your code is that you make no attempt to send data received from one client to the other connected clients. You have the _connections list in your server, but the only thing stored in the list are the Task objects for the connections, and you don't even do anything with those.
Instead, you should maintain a list of the connections themselves, so that when you received a message from one client, you can then retransmit that message to the other clients.
At a minimum, this should be a List<TcpClient>, but because you are using StreamReader and StreamWriter, you'll want to initialize and store those objects in the list as well. In addition, you should include a client identifier. One obvious choice for this would be the name of the client (i.e. what the user enters as their name), but your example doesn't provide any mechanism in the chat protocol to transmit that identification as part of the connection initialization, so in my example (below) I just use a simple integer value.
There are some other irregularities in the code you posted, such as:
Starting a task in a brand new thread, just to execute a few statements that get you to the point of initiating an asynchronous operation. In my example, I simply omit the Task.Run() part of the code, as it's not needed.
Checking the connection-specific task when it's returned for IsFaulted. Since it's unlikely any I/O will actually have occurred by the time this Task object is returned, this logic has very little use. The call to Wait() will throw an exception, which will propagate to the main thread's Wait() call, terminating the server. But you don't terminate the server in the event of any other error, so it's not clear why you'd want to do that here.
There's a spurious call to Task.Yield(). I have no idea what you're trying to accomplish there, but whatever it is, that statement isn't useful. I simply removed it.
In your client code, you only attempt to receive data from the server when you've sent data. This is very wrong; you want clients to be responsive and receive data as soon as it's sent to them. In my version, I included a simple little anonymous method that is called immediately to start a separate message-receiving loop that will execute asynchronously and concurrently with the main user input loop.
Also in the client code, you were sending the "…has left…" message after the "quit" message that would cause the server to close the connection. This means that the server would never actually receive the "…has left…" message. I reversed the order of the messages so that "quit" is always the last thing the client ever sends.
My version looks like this:
Server:
class TcpHelper
{
class ClientData : IDisposable
{
private static int _nextId;
public int ID { get; private set; }
public TcpClient Client { get; private set; }
public TextReader Reader { get; private set; }
public TextWriter Writer { get; private set; }
public ClientData(TcpClient client)
{
ID = _nextId++;
Client = client;
NetworkStream stream = client.GetStream();
Reader = new StreamReader(stream);
Writer = new StreamWriter(stream);
}
public void Dispose()
{
Writer.Close();
Reader.Close();
Client.Close();
}
}
private static readonly object _lock = new object();
private static readonly List<ClientData> _connections = new List<ClientData>();
private static TcpListener listener { get; set; }
private static bool accept { get; set; }
public static async Task StartListener()
{
IPAddress address = IPAddress.Any;
int port = 5678;
listener = new TcpListener(address, port);
listener.Start();
Console.WriteLine("Server started. Listening to TCP clients on port {0}", port);
while (true)
{
var tcpClient = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client has connected");
var task = StartHandleConnectionAsync(tcpClient);
if (task.IsFaulted)
task.Wait();
}
}
// Register and handle the connection
private static async Task StartHandleConnectionAsync(TcpClient tcpClient)
{
ClientData clientData = new ClientData(tcpClient);
lock (_lock) _connections.Add(clientData);
// catch all errors of HandleConnectionAsync
try
{
await HandleConnectionAsync(clientData);
}
catch (Exception ex)
{
// log the error
Console.WriteLine(ex.ToString());
}
finally
{
lock (_lock) _connections.Remove(clientData);
clientData.Dispose();
}
}
private static async Task HandleConnectionAsync(ClientData clientData)
{
Console.WriteLine("Client connected. Waiting for data.");
string clientmessage;
while ((clientmessage = await clientData.Reader.ReadLineAsync()) != null && clientmessage != "quit")
{
string message = "From " + clientData.ID + ": " + clientmessage;
Console.WriteLine(message);
lock (_lock)
{
// Locking the entire operation ensures that a) none of the client objects
// are disposed before we can write to them, and b) all of the chat messages
// are received in the same order by all clients.
foreach (ClientData recipient in _connections.Where(r => r.ID != clientData.ID))
{
recipient.Writer.WriteLine(message);
recipient.Writer.Flush();
}
}
}
Console.WriteLine("Closing connection.");
}
}
Client:
class Program
{
private const int _kport = 5678;
private static async Task clientConnect()
{
IPAddress address = IPAddress.Loopback;
TcpClient socketForServer = new TcpClient();
string userName;
Console.Write("Input Username: ");
userName = Console.ReadLine();
try
{
await socketForServer.ConnectAsync(address, _kport);
Console.WriteLine("Connected to Server");
}
catch (Exception e)
{
Console.WriteLine("Failed to Connect to server {0}:{1}", address, _kport);
return;
}
using (NetworkStream networkStream = socketForServer.GetStream())
{
var readTask = ((Func<Task>)(async () =>
{
using (StreamReader reader = new StreamReader(networkStream))
{
string receivedText;
while ((receivedText = await reader.ReadLineAsync()) != null)
{
Console.WriteLine("Server:" + receivedText);
}
}
}))();
using (StreamWriter streamwriter = new StreamWriter(networkStream))
{
try
{
while (true)
{
Console.Write(userName + ": ");
string clientmessage = Console.ReadLine();
if ((clientmessage == "quit") || (clientmessage == "QUIT"))
{
streamwriter.WriteLine(userName + " has left the conversation");
streamwriter.WriteLine("quit");
streamwriter.Flush();
break;
}
else
{
streamwriter.WriteLine(userName + ": " + clientmessage);
streamwriter.Flush();
}
}
await readTask;
}
catch (Exception e)
{
Console.WriteLine("Exception writing to server: " + e);
throw;
}
}
}
}
public static void Main(string[] args)
{
clientConnect().Wait();
}
}
There is still a lot you'll need to work on. You'll probably want to implement proper initialization of chat user names on the server side. At the very least, for real-world code you'd want to do more error checking, and make sure the client ID is generated reliably (if you only want positive ID values, you can't have more than 2^31-1 connections before it rolls back over to 0).
I also made some other minor changes that weren't strictly necessary, such as using the IPAddress.Any and IPAddress.Loopback values instead of parsing strings, and just generally simplifying and cleaning up the code here and there. Also, I'm not using a C# 6 compiler at the moment, so I changed the code where you were using C# 6 features so that it would compile using C# 5 instead.
To do a full-blown chat server, you still have your work cut out for you. But I hope that the above gets you back on the right track.
I don't understand what information I have available to me with async operations in C# even after reading the docs. I have a TpcClient and I want it to try to connect x number of times. So far:
public async Task SocketConnect() {
tcpClient = new TcpClient();
for(int i = 0; i < maxConnectionAttempts; i++) {
OpenSocket();
await Task.Delay(5000);
}
}
private void OpenSocket() {
try {
tcpClient.BeginConnect(host, port, ConnectCallback, tcpClient);
}
catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
private void ConnectCallback(IAsyncResult result) {
try {
tcpClient.EndConnect(result);
// Connected
if (tcpClient.Connected) {
Console.WriteLine("connected");
if (OnClientEvent != null)
OnClientEvent(this, new ClientEventArgs(Action.Connect));
stream = tcpClient.GetStream();
BeginReadAsync();
}
// Not connected
else {
Console.WriteLine("not connected");
Console.WriteLine("Retrying");
}
}
catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
I'm missing the logic in the SocketConnect() method. I'm not sure how I could await OpenSocket() because its result is carried to a different callback. What can I return from OpenSocket() so that I know it connected?
What a muddle you've gotten into.
You're mixing two different types of asynchrony... the Task Asynchronous Pattern and the (considerably more confusing) Asynchronous Programming Model. I suggest you ditch APM (it's old and sh*t) and stick with TAP from herein, because it allows you to write asynchronous code without callbacks so you end up much simpler, readable code.
If I understand correctly, all you're trying to do is this:
public async Task SocketConnect() {
var tcpClient = new TcpClient();
for(var retries = 0; retries < 5; retries++)
{
try
{
await tcpClient.ConnectAsync(host, port);
}
catch(Exception ex)
{
//handle errors
continue;
}
if(tcpClient.Connected) break;
}
if(tcpClient.Connected)
{
//yay
}
}
We're currently using Redis 2.8.4 and StackExchange.Redis (and loving it) but don't have any sort of protection against hardware failures etc at the moment. I'm trying to get the solution working whereby we have master/slaves and sentinel monitoring but can't quite get there and I'm unable to find any real pointers after searching.
So currently we have got this far:
We have 3 redis servers and sentinel on each node (setup by the Linux guys):
devredis01:6383 (master)
devredis02:6383 (slave)
devredis03:6383 (slave)
devredis01:26379 (sentinel)
devredis02:26379 (sentinel)
devredis03:26379 (sentinel)
I am able to connect the StackExchange client to the redis servers and write/read and verify that the data is being replicated across all redis instances using Redis Desktop Manager.
I can also connect to the sentinel services using a different ConnectionMultiplexer, query the config, ask for master redis node, ask for slaves etc.
We can also kill the master redis node and verify that one of the slaves is promoted to master and replication to the other slave continues to work. We can observe the redis connection trying to reconnect to the master, and also if I recreate the ConnectionMultiplexer I can write/read again to the newly promoted master and read from the slave.
So far so good!
The bit I'm missing is how do you bring it all together in a production system?
Should I be getting the redis endpoints from sentinel and using 2 ConnectionMultiplexers?
What exactly do I have to do to detect that a node has gone down?
Can StackExchange do this for me automatically or does it pass an event so I can reconnect my redis ConnectionMultiplexer?
Should I handle the ConnectionFailed event and then reconnect in order for the ConnectionMuliplexer to find out what the new master is?
Presumably while I am reconnecting any attempts to write will be lost?
I hope I'm not missing something very obvious here I'm just struggling to put it all together.
Thanks in advance!
I was able to spend some time last week with the Linux guys testing scenarios and working on the C# side of this implementation and am using the following approach:
Read the sentinel addresses from config and create a ConnectionMultiplexer to connect to them
Subscribe to the +switch-master channel
Ask each sentinel server in turn what they think the master redis and slaves are, compare them all to make sure they all agree
Create a new ConnectionMultiplexer with the redis server addresses read from sentinel and connect, add event handler to ConnectionFailed and ConnectionRestored.
When I receive the +switch-master message I call Configure() on the redis ConnectionMultiplexer
As a belt and braces approach I always call Configure() on the redis ConnectionMultiplexer 12 seconds after receiving a connectionFailed or connectionRestored event when the connection type is ConnectionType.Interactive.
I find that generally I am working and reconfigured after about 5 seconds of losing the redis master. During this time I can't write but I can read (since you can read off a slave). 5 seconds is ok for us since our data updates very quickly and becomes stale after a few seconds (and is subsequently overwritten).
One thing I wasn't sure about was whether or not I should remove the redis server from the redis ConnectionMultiplexer when an instance goes down, or let it continue to retry the connection. I decided to leave it retrying as it comes back into the mix as a slave as soon as it comes back up. I did some performance testing with and without a connection being retried and it seemed to make little difference. Maybe someone can clarify whether this is the correct approach.
Every now and then bringing back an instance that was previously a master did seem to cause some confusion - a few seconds after it came back up I would receive an exception from writing - "READONLY" suggesting I can't write to a slave. This was rare but I found that my "catch-all" approach of calling Configure() 12 seconds after a connection state change caught this problem. Calling Configure() seems very cheap and therefore calling it twice regardless of whether or not it's necessary seemed OK.
Now that I have slaves I have offloaded some of my data cleanup code which does key scans to the slaves, which makes me happy.
All in all I'm pretty satisfied, it's not perfect but for something that should very rarely happen it's more than good enough.
I am including our Redis wrapper, it has changed somewhat from the original answer, for various reasons:
We wanted to use pub/sub
Sentinel didn't always appear to give us the master changed message at the 'right' time (i.e. it we called Configure() and ended up thinking a slave was a master)
The connectionMultiplexer didn't always seem to restore connctions every time, affecting pub/sub
I rather suspect this is down to our sentinel/redis configuration more than anything else. Either way, it just wasn't perfectly reliable despite destructive testing. Added to which, the master changed message took a long time since we had to increase timeouts due to sentinel being "too sensitive" and calling failovers when there weren't any. I think running in a virtual environment also exacerbated the problem.
Instead of listening to subscriptions now we simply attempt a write test every 5 seconds, and also have a "last message received" check for pub/sub. If we encounter any problems we strip down completely the connections and rebuild them. It seems overkill but actually it's pretty fast and still faster than waiting for the master changed message from sentinel...
This won't compile without various extension methods and other classes etc, but you get the idea.
namespace Smartodds.Framework.Redis
{
public class RedisClient : IDisposable
{
public RedisClient(RedisEnvironmentElement environment, Int32 databaseId)
{
m_ConnectTimeout = environment.ConnectTimeout;
m_Timeout = environment.Timeout;
m_DatabaseId = databaseId;
m_ReconnectTime = environment.ReconnectTime;
m_CheckSubscriptionsTime = environment.CheckSubscriptions;
if (environment.TestWrite == true)
{
m_CheckWriteTime = environment.TestWriteTime;
}
environment.Password.ToCharArray().ForEach((c) => m_Password.AppendChar(c));
foreach (var server in environment.Servers)
{
if (server.Type == ServerType.Redis)
{
// will be ignored if sentinel servers are used
m_RedisServers.Add(new RedisConnection { Address = server.Host, Port = server.Port });
}
else
{
m_SentinelServers.Add(new RedisConnection { Address = server.Host, Port = server.Port });
}
}
}
public bool IsSentinel { get { return m_SentinelServers.Count > 0; } }
public IDatabase Database { get { return _Redis.GetDatabase(m_DatabaseId); } }
private ConnectionMultiplexer _Redis
{
get
{
if (m_Connecting == true)
{
throw new RedisConnectionNotReadyException();
}
ConnectionMultiplexer redis = m_Redis;
if (redis == null)
{
throw new RedisConnectionNotReadyException();
}
return redis;
}
}
private ConnectionMultiplexer _Sentinel
{
get
{
if (m_Connecting == true)
{
throw new RedisConnectionNotReadyException("Sentinel connection not ready");
}
ConnectionMultiplexer sentinel = m_Sentinel;
if (sentinel == null)
{
throw new RedisConnectionNotReadyException("Sentinel connection not ready");
}
return sentinel;
}
}
public void RegisterSubscription(string channel, Action<RedisChannel, RedisValue> handler, Int32 maxNoReceiveSeconds)
{
m_Subscriptions.Add(channel, new RedisSubscription
{
Channel = channel,
Handler = handler,
MaxNoReceiveSeconds = maxNoReceiveSeconds,
LastUsed = DateTime.UtcNow,
});
}
public void Connect()
{
_Connect(true);
}
private void _Connect(object state)
{
bool throwException = (bool)state;
// if a reconnect is already being attempted, don't hang around waiting
if (Monitor.TryEnter(m_ConnectionLocker) == false)
{
return;
}
// we took the lock, notify everything we are connecting
m_Connecting = true;
try
{
Stopwatch sw = Stopwatch.StartNew();
LoggerQueue.Debug(">>>>>> REDIS CONNECTING... >>>>>>");
// if this is a reconnect, make absolutely sure everything is cleaned up first
_KillTimers();
_KillRedisClient();
if (this.IsSentinel == true && m_Sentinel == null)
{
LoggerQueue.Debug(">>>>>> CONNECTING TO SENTINEL >>>>>> - " + sw.Elapsed);
// we'll be getting the redis servers from sentinel
ConfigurationOptions sentinelConnection = _CreateRedisConfiguration(CommandMap.Sentinel, null, m_SentinelServers);
m_Sentinel = ConnectionMultiplexer.Connect(sentinelConnection);
LoggerQueue.Debug(">>>>>> CONNECTED TO SENTINEL >>>>>> - " + sw.Elapsed);
_OutputConfigurationFromSentinel();
// get all the redis servers from sentinel and ignore any set by caller
m_RedisServers.Clear();
m_RedisServers.AddRange(_GetAllRedisServersFromSentinel());
if (m_RedisServers.Count == 0)
{
throw new RedisException("Sentinel found no redis servers");
}
}
LoggerQueue.Debug(">>>>>> CONNECTING TO REDIS >>>>>> - " + sw.Elapsed);
// try to connect to all redis servers
ConfigurationOptions connection = _CreateRedisConfiguration(CommandMap.Default, _SecureStringToString(m_Password), m_RedisServers);
m_Redis = ConnectionMultiplexer.Connect(connection);
LoggerQueue.Debug(">>>>>> CONNECTED TO REDIS >>>>>> - " + sw.Elapsed);
// register subscription channels
m_Subscriptions.ForEach(s =>
{
m_Redis.GetSubscriber().Subscribe(s.Key, (channel, value) => _SubscriptionHandler(channel, value));
s.Value.LastUsed = DateTime.UtcNow;
});
if (this.IsSentinel == true)
{
// check subscriptions have been sending messages
if (m_Subscriptions.Count > 0)
{
m_CheckSubscriptionsTimer = new Timer(_CheckSubscriptions, null, 30000, m_CheckSubscriptionsTime);
}
if (m_CheckWriteTime != null)
{
// check that we can write to redis
m_CheckWriteTimer = new Timer(_CheckWrite, null, 32000, m_CheckWriteTime.Value);
}
// monitor for connection status change to any redis servers
m_Redis.ConnectionFailed += _ConnectionFailure;
m_Redis.ConnectionRestored += _ConnectionRestored;
}
LoggerQueue.Debug(string.Format(">>>>>> ALL REDIS CONNECTED ({0}) >>>>>>", sw.Elapsed));
}
catch (Exception ex)
{
LoggerQueue.Error(">>>>>> REDIS CONNECT FAILURE >>>>>>", ex);
if (throwException == true)
{
throw;
}
else
{
// internal reconnect, the reconnect has failed so might as well clean everything and try again
_KillTimers();
_KillRedisClient();
// faster than usual reconnect if failure
_ReconnectTimer(1000);
}
}
finally
{
// finished connection attempt, notify everything and remove lock
m_Connecting = false;
Monitor.Exit(m_ConnectionLocker);
}
}
private ConfigurationOptions _CreateRedisConfiguration(CommandMap commandMap, string password, List<RedisConnection> connections)
{
ConfigurationOptions connection = new ConfigurationOptions
{
CommandMap = commandMap,
AbortOnConnectFail = true,
AllowAdmin = true,
ConnectTimeout = m_ConnectTimeout,
SyncTimeout = m_Timeout,
ServiceName = "master",
TieBreaker = string.Empty,
Password = password,
};
connections.ForEach(s =>
{
connection.EndPoints.Add(s.Address, s.Port);
});
return connection;
}
private void _OutputConfigurationFromSentinel()
{
m_SentinelServers.ForEach(s =>
{
try
{
IServer server = m_Sentinel.GetServer(s.Address, s.Port);
if (server.IsConnected == true)
{
try
{
IPEndPoint master = server.SentinelGetMasterAddressByName("master") as IPEndPoint;
var slaves = server.SentinelSlaves("master");
StringBuilder sb = new StringBuilder();
sb.Append(">>>>>> _OutputConfigurationFromSentinel Server ");
sb.Append(s.Address);
sb.Append(" thinks that master is ");
sb.Append(master);
sb.Append(" and slaves are ");
foreach (var slave in slaves)
{
string name = slave.Where(i => i.Key == "name").Single().Value;
bool up = slave.Where(i => i.Key == "flags").Single().Value.Contains("disconnected") == false;
sb.Append(name);
sb.Append("(");
sb.Append(up == true ? "connected" : "down");
sb.Append(") ");
}
sb.Append(">>>>>>");
LoggerQueue.Debug(sb.ToString());
}
catch (Exception ex)
{
LoggerQueue.Error(string.Format(">>>>>> _OutputConfigurationFromSentinel Could not get configuration from sentinel server ({0}) >>>>>>", s.Address), ex);
}
}
else
{
LoggerQueue.Error(string.Format(">>>>>> _OutputConfigurationFromSentinel Sentinel server {0} was not connected", s.Address));
}
}
catch (Exception ex)
{
LoggerQueue.Error(string.Format(">>>>>> _OutputConfigurationFromSentinel Could not get IServer from sentinel ({0}) >>>>>>", s.Address), ex);
}
});
}
private RedisConnection[] _GetAllRedisServersFromSentinel()
{
// ask each sentinel server for its configuration
List<RedisConnection> redisServers = new List<RedisConnection>();
m_SentinelServers.ForEach(s =>
{
try
{
IServer server = m_Sentinel.GetServer(s.Address, s.Port);
if (server.IsConnected == true)
{
try
{
// store master in list
IPEndPoint master = server.SentinelGetMasterAddressByName("master") as IPEndPoint;
redisServers.Add(new RedisConnection { Address = master.Address.ToString(), Port = master.Port });
var slaves = server.SentinelSlaves("master");
foreach (var slave in slaves)
{
string address = slave.Where(i => i.Key == "ip").Single().Value;
string port = slave.Where(i => i.Key == "port").Single().Value;
redisServers.Add(new RedisConnection { Address = address, Port = Convert.ToInt32(port) });
}
}
catch (Exception ex)
{
LoggerQueue.Error(string.Format(">>>>>> _GetAllRedisServersFromSentinel Could not get redis servers from sentinel server ({0}) >>>>>>", s.Address), ex);
}
}
else
{
LoggerQueue.Error(string.Format(">>>>>> _GetAllRedisServersFromSentinel Sentinel server {0} was not connected", s.Address));
}
}
catch (Exception ex)
{
LoggerQueue.Error(string.Format(">>>>>> _GetAllRedisServersFromSentinel Could not get IServer from sentinel ({0}) >>>>>>", s.Address), ex);
}
});
return redisServers.Distinct().ToArray();
}
private IServer _GetRedisMasterFromSentinel()
{
// ask each sentinel server for its configuration
foreach (RedisConnection sentinel in m_SentinelServers)
{
IServer sentinelServer = _Sentinel.GetServer(sentinel.Address, sentinel.Port);
if (sentinelServer.IsConnected == true)
{
try
{
IPEndPoint master = sentinelServer.SentinelGetMasterAddressByName("master") as IPEndPoint;
return _Redis.GetServer(master);
}
catch (Exception ex)
{
LoggerQueue.Error(string.Format(">>>>>> Could not get redis master from sentinel server ({0}) >>>>>>", sentinel.Address), ex);
}
}
}
throw new InvalidOperationException("No sentinel server available to get master");
}
private void _ReconnectTimer(Nullable<Int32> reconnectMilliseconds)
{
try
{
lock (m_ReconnectLocker)
{
if (m_ReconnectTimer != null)
{
m_ReconnectTimer.Dispose();
m_ReconnectTimer = null;
}
// since a reconnect will definately occur we can stop the check timers for now until reconnect succeeds (where they are recreated)
_KillTimers();
LoggerQueue.Warn(">>>>>> REDIS STARTING RECONNECT TIMER >>>>>>");
m_ReconnectTimer = new Timer(_Connect, false, reconnectMilliseconds.GetValueOrDefault(m_ReconnectTime), Timeout.Infinite);
}
}
catch (Exception ex)
{
LoggerQueue.Error("Error during _ReconnectTimer", ex);
}
}
private void _CheckSubscriptions(object state)
{
if (Monitor.TryEnter(m_ConnectionLocker, TimeSpan.FromSeconds(1)) == false)
{
return;
}
try
{
DateTime now = DateTime.UtcNow;
foreach (RedisSubscription subscription in m_Subscriptions.Values)
{
if ((now - subscription.LastUsed) > TimeSpan.FromSeconds(subscription.MaxNoReceiveSeconds))
{
try
{
EndPoint endpoint = m_Redis.GetSubscriber().IdentifyEndpoint(subscription.Channel);
EndPoint subscribedEndpoint = m_Redis.GetSubscriber().SubscribedEndpoint(subscription.Channel);
LoggerQueue.Warn(string.Format(">>>>>> REDIS Channel '{0}' has not been used for longer than {1}s, IsConnected: {2}, IsConnectedChannel: {3}, EndPoint: {4}, SubscribedEndPoint: {5}, reconnecting...", subscription.Channel, subscription.MaxNoReceiveSeconds, m_Redis.GetSubscriber().IsConnected(), m_Redis.GetSubscriber().IsConnected(subscription.Channel), endpoint != null ? endpoint.ToString() : "null", subscribedEndpoint != null ? subscribedEndpoint.ToString() : "null"));
}
catch (Exception ex)
{
LoggerQueue.Error(string.Format(">>>>>> REDIS Error logging out details of Channel '{0}' reconnect", subscription.Channel), ex);
}
_ReconnectTimer(null);
return;
}
}
}
catch (Exception ex)
{
LoggerQueue.Error(">>>>>> REDIS Exception ERROR during _CheckSubscriptions", ex);
}
finally
{
Monitor.Exit(m_ConnectionLocker);
}
}
private void _CheckWrite(object state)
{
if (Monitor.TryEnter(m_ConnectionLocker, TimeSpan.FromSeconds(1)) == false)
{
return;
}
try
{
this.Database.HashSet(Environment.MachineName + "SmartoddsWriteCheck", m_CheckWriteGuid.ToString(), DateTime.UtcNow.Ticks);
}
catch (RedisConnectionNotReadyException)
{
LoggerQueue.Warn(">>>>>> REDIS RedisConnectionNotReadyException ERROR DURING _CheckWrite");
}
catch (RedisServerException ex)
{
LoggerQueue.Warn(">>>>>> REDIS RedisServerException ERROR DURING _CheckWrite, reconnecting... - " + ex.Message);
_ReconnectTimer(null);
}
catch (RedisConnectionException ex)
{
LoggerQueue.Warn(">>>>>> REDIS RedisConnectionException ERROR DURING _CheckWrite, reconnecting... - " + ex.Message);
_ReconnectTimer(null);
}
catch (TimeoutException ex)
{
LoggerQueue.Warn(">>>>>> REDIS TimeoutException ERROR DURING _CheckWrite - " + ex.Message);
}
catch (Exception ex)
{
LoggerQueue.Error(">>>>>> REDIS Exception ERROR during _CheckWrite", ex);
}
finally
{
Monitor.Exit(m_ConnectionLocker);
}
}
private void _ConnectionFailure(object sender, ConnectionFailedEventArgs e)
{
LoggerQueue.Warn(string.Format(">>>>>> REDIS CONNECTION FAILURE, {0}, {1}, {2} >>>>>>", e.ConnectionType, e.EndPoint.ToString(), e.FailureType));
}
private void _ConnectionRestored(object sender, ConnectionFailedEventArgs e)
{
LoggerQueue.Warn(string.Format(">>>>>> REDIS CONNECTION RESTORED, {0}, {1}, {2} >>>>>>", e.ConnectionType, e.EndPoint.ToString(), e.FailureType));
}
private void _SubscriptionHandler(string channel, RedisValue value)
{
// get handler lookup
RedisSubscription subscription = null;
if (m_Subscriptions.TryGetValue(channel, out subscription) == false || subscription == null)
{
return;
}
// update last used
subscription.LastUsed = DateTime.UtcNow;
// call handler
subscription.Handler(channel, value);
}
public Int64 Publish(string channel, RedisValue message)
{
try
{
return _Redis.GetSubscriber().Publish(channel, message);
}
catch (RedisConnectionNotReadyException)
{
LoggerQueue.Error("REDIS RedisConnectionNotReadyException ERROR DURING Publish");
throw;
}
catch (RedisServerException ex)
{
LoggerQueue.Error("REDIS RedisServerException ERROR DURING Publish - " + ex.Message);
throw;
}
catch (RedisConnectionException ex)
{
LoggerQueue.Error("REDIS RedisConnectionException ERROR DURING Publish - " + ex.Message);
throw;
}
catch (TimeoutException ex)
{
LoggerQueue.Error("REDIS TimeoutException ERROR DURING Publish - " + ex.Message);
throw;
}
catch (Exception ex)
{
LoggerQueue.Error("REDIS Exception ERROR DURING Publish", ex);
throw;
}
}
public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry)
{
return _Execute(() => this.Database.LockTake(key, value, expiry));
}
public bool LockExtend(RedisKey key, RedisValue value, TimeSpan extension)
{
return _Execute(() => this.Database.LockExtend(key, value, extension));
}
public bool LockRelease(RedisKey key, RedisValue value)
{
return _Execute(() => this.Database.LockRelease(key, value));
}
private void _Execute(Action action)
{
try
{
action.Invoke();
}
catch (RedisServerException ex)
{
LoggerQueue.Error("REDIS RedisServerException ERROR DURING _Execute - " + ex.Message);
throw;
}
catch (RedisConnectionException ex)
{
LoggerQueue.Error("REDIS RedisConnectionException ERROR DURING _Execute - " + ex.Message);
throw;
}
catch (TimeoutException ex)
{
LoggerQueue.Error("REDIS TimeoutException ERROR DURING _Execute - " + ex.Message);
throw;
}
catch (Exception ex)
{
LoggerQueue.Error("REDIS Exception ERROR DURING _Execute", ex);
throw;
}
}
private TResult _Execute<TResult>(Func<TResult> function)
{
try
{
return function.Invoke();
}
catch (RedisServerException ex)
{
LoggerQueue.Error("REDIS RedisServerException ERROR DURING _Execute - " + ex.Message);
throw;
}
catch (RedisConnectionException ex)
{
LoggerQueue.Error("REDIS RedisConnectionException ERROR DURING _Execute - " + ex.Message);
throw;
}
catch (TimeoutException ex)
{
LoggerQueue.Error("REDIS TimeoutException ERROR DURING _Execute - " + ex.Message);
throw;
}
catch (Exception ex)
{
LoggerQueue.Error("REDIS ERROR DURING _Execute", ex);
throw;
}
}
public string[] GetAllKeys(string pattern)
{
if (m_Sentinel != null)
{
return _GetAnyRedisSlaveFromSentinel().Keys(m_DatabaseId, pattern).Select(k => (string)k).ToArray();
}
else
{
return _Redis.GetServer(_Redis.GetEndPoints().First()).Keys(m_DatabaseId, pattern).Select(k => (string)k).ToArray();
}
}
private void _KillSentinelClient()
{
try
{
if (m_Sentinel != null)
{
LoggerQueue.Debug(">>>>>> KILLING SENTINEL CONNECTION >>>>>>");
ConnectionMultiplexer sentinel = m_Sentinel;
m_Sentinel = null;
sentinel.Close(false);
sentinel.Dispose();
}
}
catch (Exception ex)
{
LoggerQueue.Error(">>>>>> Error during _KillSentinelClient", ex);
}
}
private void _KillRedisClient()
{
try
{
if (m_Redis != null)
{
Stopwatch sw = Stopwatch.StartNew();
LoggerQueue.Debug(">>>>>> KILLING REDIS CONNECTION >>>>>>");
ConnectionMultiplexer redis = m_Redis;
m_Redis = null;
if (this.IsSentinel == true)
{
redis.ConnectionFailed -= _ConnectionFailure;
redis.ConnectionRestored -= _ConnectionRestored;
}
redis.Close(false);
redis.Dispose();
LoggerQueue.Debug(">>>>>> KILLED REDIS CONNECTION >>>>>> " + sw.Elapsed);
}
}
catch (Exception ex)
{
LoggerQueue.Error(">>>>>> Error during _KillRedisClient", ex);
}
}
private void _KillClients()
{
lock (m_ConnectionLocker)
{
_KillSentinelClient();
_KillRedisClient();
}
}
private void _KillTimers()
{
if (m_CheckSubscriptionsTimer != null)
{
m_CheckSubscriptionsTimer.Dispose();
m_CheckSubscriptionsTimer = null;
}
if (m_CheckWriteTimer != null)
{
m_CheckWriteTimer.Dispose();
m_CheckWriteTimer = null;
}
}
public void Dispose()
{
_KillClients();
_KillTimers();
}
}
}
I just asked this question, and found a similar question to yours and mine which I believe answers the question of how does our code (the client) know now which is the new master server when the current master goes down?
How to tell a Client where the new Redis master is using Sentinel
Apparently you just have to subscribe and listen to events from the Sentinels. Makes sense.. I just figured there was a more streamlined way.
I read something about the Twemproxy for Linux which acts as a proxy and probably does this for you? But I was on redis for Windows and was trying to find a Windows option. We might just moved to Linux if that's the approved way it should be done.
Today (I just configured StackExchange.Redis 2.1.58 to use sentinel) it's enough to specify a sentinel endpoint and serviceName in the redis connection string or Configuration. All the rest has been encapsulated as a part of this commit. So you just point stackexchange.redis to your sentinel nodes and ConnectionMuliplexer gives you up and running IDatabase each time you call GetDatabase().
var conn = ConnectionMultiplexer.Connect("sentinel:26379,serviceName=mymaster");
var db = conn.GetDatabase();
db.StringSet("key", "value");
I am currently working on C# application which requires to read serial port. In UI, there is a ON/OFF button which enables user click on it to start and stop reading data from serial port. If I continuously click on the button on and off. It threw an exception - Access to COM3 is denied or even said "The device is not connected". Can anyone suggest a better way to implement the serial port function which is able to resolve the situation as described above? Here is the code I use:
**// Start reading data from serial port**
public override void StartReading(string portname)
{
try
{
int k = int.Parse(portname.Replace("COM", ""));
if (startThread != null)
{
startThread.Abort();
startThread = null;
}
startThread = new Thread(new ThreadStart(delegate
{
isActive = true;
try
{
using (SerialPort sp = new SerialPort(portname))
{
if (!isActive)
{
DisposeBT(sp);
return;
}
sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
if (!isActive)
{
DisposeBT(sp);
return;
}
if (!isActive)
{
DisposeBT(sp);
return;
}
else
{
Thread.Sleep(6500);
try
{
if (sp != null && !sp.IsOpen)
{
sp.Open();
}
}
catch (Exception ex)
{
Logger.Warn("Failed to open the serial port for HRM once. Try it again.");
Logger.Error(ex);
////////////////////// new added below
if(sp !=null && sp.IsOpen)
{
sp.Dispose();
}
Thread.Sleep(6500);
if (IsPortAvailable(k))
{
try
{
if (sp != null && !sp.IsOpen)
{
sp.Open();
}
}
catch (Exception ex1)
{
////////////////////// new added below
if (sp != null && sp.IsOpen)
{
sp.Dispose();
}
Logger.Warn("Failed to open the serial for HRM twice.");
Logger.Error(ex1);
// return;
}
}
}
}
while (true)
{
if (!isActive)
{
DisposeBT(sp);
break;
}
}
if (!isActive)
{
DisposeBT(sp);
return;
}
DisposeBT(sp);
}
}
catch (Exception ex)
{
Logger.Warn("Exception thrown for HRM.");
Logger.Error(ex);
}
}));
startThread.IsBackground = true;
startThread.Priority = ThreadPriority.Highest;
startThread.Start();
}
catch (Exception ex)
{
Logger.Warn("Failed to start reading for HRM02I3A1 bluetooth device.");
Logger.Error(ex);
}
}
// Stop reading data from serial port
public override void StopReading()
{
try
{
isActive = false;
}
catch { }
}
// event handler for the serial port to read data from sp.
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (isActive)// && startThread.IsAlive
{
SerialPort sp1 = (SerialPort)sender;
try
{
sp1.Read(data, 0, 8);
decoder.Decode(data);
}
catch(Exception ex)
{
Logger.Warn("------data received from Serial Port error for HRM-------");
Logger.Error(ex);
};
}
}
first make background worker thread that accept the cancel event.
in the DoWork method you can write something like that
void DoWork{
// init com port
while(no body cancelled the background worker){
// if there any waiting data receive and process it. do not use event handlers
}
// close the serial port so you can open it again later.
}
Also if you want to cancel the background work it would be a piece of cake
// send cancel command.
// wait till it is canceled.
Try adding startThread.Join() directly after the call to startThread.Abort().
Take a look at the msdn documentation on Thread.Abort and perhaps you also should check what join does.