-
-
Save LaurensVergote/ba4b4ea1825f2ff5fed3d1aee9214dd8 to your computer and use it in GitHub Desktop.
NatsClientPortLock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using NATS.Client; | |
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Net; | |
using System.Net.NetworkInformation; | |
using System.Net.Sockets; | |
using System.Runtime.CompilerServices; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace NatsPortDemo | |
{ | |
public class NatsClientDemo | |
{ | |
public static void Main(string[] args) | |
{ | |
var localHost = Dns.GetHostName(); | |
var availablePort = PortProvider.GetFreePort(); | |
var port = availablePort + 200; //Should give us an approximation on the "next" available ports range | |
PortProvider.UnlockPort(availablePort); | |
//1] Start nats-server on port | |
var natsProcess = Process.Start("nats-server.exe", $"--addr {localHost} --port {port}"); | |
Thread.Sleep(5000); //Wait for nats-server.exe to start up completely | |
//2] Make a few connections. They should all succeed, client ports taken should be [port+1;port+20] | |
List<IConnection> connections = new List<IConnection>(); | |
for (int i = 0; i < 20; i++) | |
{ | |
var options = ConnectionFactory.GetDefaultOptions(); | |
options.AllowReconnect = true; //Not required. default is true already | |
options.MaxReconnect = Options.ReconnectForever; //Never give up | |
options.ReconnectWait = 1000; //Try reconnect every ~1s | |
options.Servers = new string[] { $"nats://{localHost}:{port}" }; | |
try | |
{ | |
IConnection connection = new ConnectionFactory().CreateConnection(options); | |
connections.Add(connection); | |
} | |
catch (NATSConnectionException nce) | |
{ | |
Debugger.Launch(); | |
} | |
catch (Exception ex) | |
{ | |
Debugger.Launch(); | |
} | |
} | |
Thread.Sleep(5000); //Let the connections settle in a bit, don't want to scare them | |
//Print port usage for baseline. Should see 20 Established connections, all to the same port (the one nats-server.exe is hosting on) | |
PrintPortUsage(port); | |
//Stop the nats-server process. This forces the connections into the reconnection loop. | |
natsProcess.Kill(); | |
//At some point in the reconnection loop, 1 of the connections will grab "port" as its local endpoint and manage to establish a connection. | |
//This blocks the nats-server process from starting again on that port. | |
//After a while, You'll see that a connection gets established between <localhost>:<port> and <localhost>:<port> | |
//This Established connection persists, which should be avoided. | |
//Might have to rerun the process a few times to trigger it. It's possible that other processes in the mean time grab the port for a second to do a http request or something. | |
//Rerun if you notice the logged LocalEndpoint port getting bigger than the RemoteEndpoint port and no "Established" connection is seen. | |
while (true) | |
{ | |
Thread.Sleep(5000); | |
PrintPortUsage(port); | |
} | |
} | |
private static void PrintPortUsage(int remotePort) | |
{ | |
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); | |
IPEndPoint[] endPoints = ipProperties.GetActiveTcpListeners(); | |
//Filter on remote port, we can be pretty confident the only process trying to connect to this port is us. | |
var tcpConnections = | |
ipProperties.GetActiveTcpConnections() | |
.Where(c => c.RemoteEndPoint.Port == remotePort); | |
string horizontalLine = new string('-', 3 + 3 * 20); | |
string rowFormat = " {0,-20}| {1,-20}| {2,-20}"; | |
Console.WriteLine(); //Give some space | |
Console.WriteLine(horizontalLine); | |
Console.WriteLine(rowFormat, "Local Endpoint", "Remote Endpoint", "State"); | |
Console.WriteLine(horizontalLine); | |
foreach (TcpConnectionInformation info in tcpConnections) | |
{ | |
Console.WriteLine(rowFormat, | |
$"{info.LocalEndPoint.Address}:{info.LocalEndPoint.Port}", | |
$"{info.RemoteEndPoint.Address}:{info.RemoteEndPoint.Port}", | |
info.State.ToString()); | |
} | |
Console.WriteLine(horizontalLine); | |
} | |
} | |
public static class PortProvider | |
{ | |
private static ConcurrentDictionary<int, TcpListener> _lockedPorts = new ConcurrentDictionary<int, TcpListener>(); | |
/// <summary> | |
/// Don't forget to unlock the port just before you want to use it for another application | |
/// </summary> | |
/// <returns></returns> | |
public static int GetFreePort() | |
{ | |
var l = new TcpListener(IPAddress.Loopback, 0); | |
l.Start(); | |
int port = ((IPEndPoint)l.LocalEndpoint).Port; | |
_lockedPorts.TryAdd(port, l); | |
//l.Stop(); | |
return port; | |
} | |
/// <summary> | |
/// Make the port free for use by another application. | |
/// </summary> | |
/// <param name="port"></param> | |
public static void UnlockPort(int port) | |
{ | |
if (_lockedPorts.TryGetValue(port, out var listener)) | |
listener.Stop(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment