C#
TCP port
port availability
network programming
coding tutorial

In C, how to check if a TCP port is available?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, checking if a TCP port is available means verifying that no other process is currently listening on or bound to that port. The most reliable method is to attempt to bind a TcpListener to the port and catch the SocketException if it fails. Alternatively, you can query the system's active TCP connections using IPGlobalProperties.GetActiveTcpListeners() for a non-binding check. Both approaches have trade-offs between reliability and race conditions.

Method 1: Try to Bind with TcpListener

The most reliable approach — actually attempt to use the port:

csharp
1using System.Net;
2using System.Net.Sockets;
3
4public static bool IsPortAvailable(int port)
5{
6    try
7    {
8        using var listener = new TcpListener(IPAddress.Loopback, port);
9        listener.Start();
10        listener.Stop();
11        return true;  // Port is available
12    }
13    catch (SocketException)
14    {
15        return false;  // Port is in use
16    }
17}
18
19// Usage
20if (IsPortAvailable(8080))
21    Console.WriteLine("Port 8080 is free");
22else
23    Console.WriteLine("Port 8080 is in use");

This method is reliable because it tests the actual system state. However, there is a race condition — the port could be taken between checking and actually using it.

Method 2: Check Active TCP Listeners

Query the OS for all active TCP endpoints without binding:

csharp
1using System.Net;
2using System.Net.NetworkInformation;
3
4public static bool IsPortAvailable(int port)
5{
6    IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
7
8    // Check active TCP listeners
9    IPEndPoint[] listeners = properties.GetActiveTcpListeners();
10    foreach (var endpoint in listeners)
11    {
12        if (endpoint.Port == port)
13            return false;
14    }
15
16    // Also check active TCP connections
17    TcpConnectionInformation[] connections = properties.GetActiveTcpConnections();
18    foreach (var conn in connections)
19    {
20        if (conn.LocalEndPoint.Port == port)
21            return false;
22    }
23
24    return true;
25}

This avoids the side effect of binding, but can miss ports in TIME_WAIT state or ports bound to specific interfaces.

Method 3: Find a Free Port Automatically

Let the OS assign an available port:

csharp
1using System.Net;
2using System.Net.Sockets;
3
4public static int GetFreePort()
5{
6    // Binding to port 0 lets the OS assign any available port
7    using var listener = new TcpListener(IPAddress.Loopback, 0);
8    listener.Start();
9    int port = ((IPEndPoint)listener.LocalEndpoint).Port;
10    listener.Stop();
11    return port;
12}
13
14// Usage
15int freePort = GetFreePort();
16Console.WriteLine($"Available port: {freePort}");  // e.g., 52431

This is the safest approach when you need any free port and do not require a specific number.

Method 4: Check a Range of Ports

csharp
1using System.Net;
2using System.Net.Sockets;
3
4public static int FindAvailablePort(int startPort, int endPort)
5{
6    for (int port = startPort; port <= endPort; port++)
7    {
8        try
9        {
10            using var listener = new TcpListener(IPAddress.Loopback, port);
11            listener.Start();
12            listener.Stop();
13            return port;
14        }
15        catch (SocketException)
16        {
17            continue;  // Port in use, try next
18        }
19    }
20
21    throw new InvalidOperationException(
22        $"No available port found in range {startPort}-{endPort}");
23}
24
25// Find first available port between 8000-9000
26int port = FindAvailablePort(8000, 9000);
27Console.WriteLine($"Using port: {port}");

Method 5: Check with Async Support

csharp
1using System.Net;
2using System.Net.Sockets;
3
4public static async Task<bool> IsPortAvailableAsync(int port)
5{
6    try
7    {
8        using var listener = new TcpListener(IPAddress.Any, port);
9        listener.Start();
10        // Optionally verify by connecting
11        using var client = new TcpClient();
12        await client.ConnectAsync(IPAddress.Loopback, port);
13        listener.Stop();
14        return true;
15    }
16    catch (SocketException)
17    {
18        return false;
19    }
20}

Checking Both TCP and UDP

csharp
1using System.Net;
2using System.Net.Sockets;
3
4public static bool IsPortAvailable(int port, bool checkUdp = false)
5{
6    // Check TCP
7    try
8    {
9        using var tcpListener = new TcpListener(IPAddress.Loopback, port);
10        tcpListener.Start();
11        tcpListener.Stop();
12    }
13    catch (SocketException)
14    {
15        return false;
16    }
17
18    // Optionally check UDP
19    if (checkUdp)
20    {
21        try
22        {
23            using var udpClient = new UdpClient(port);
24            udpClient.Close();
25        }
26        catch (SocketException)
27        {
28            return false;
29        }
30    }
31
32    return true;
33}

Bind-Then-Use Pattern (Race-Free)

To avoid the race condition between checking and using:

csharp
1using System.Net;
2using System.Net.Sockets;
3
4public static TcpListener StartOnAvailablePort(int preferredPort)
5{
6    try
7    {
8        var listener = new TcpListener(IPAddress.Any, preferredPort);
9        listener.Start();
10        return listener;  // Keep it running — no gap for races
11    }
12    catch (SocketException)
13    {
14        // Preferred port taken — let OS assign one
15        var listener = new TcpListener(IPAddress.Any, 0);
16        listener.Start();
17        int actualPort = ((IPEndPoint)listener.LocalEndpoint).Port;
18        Console.WriteLine($"Preferred port {preferredPort} taken, using {actualPort}");
19        return listener;
20    }
21}

Common Pitfalls

  • Race condition between check and use: Checking port availability and then starting a server are two separate operations. Another process can bind the port in between. The safest pattern is to attempt to bind directly and handle SocketException if the port is taken, rather than checking first.
  • Only checking IPAddress.Loopback: A port bound to IPAddress.Any (0.0.0.0) is not detected by a check against IPAddress.Loopback (127.0.0.1) on some platforms. Check against the same address your server will use, or check both Loopback and Any.
  • Ignoring TIME_WAIT state: After a TCP connection closes, the port enters TIME_WAIT for 60-240 seconds. GetActiveTcpListeners() may not show it, but binding fails. Use Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true) to bind to TIME_WAIT ports.
  • Checking ports in the ephemeral range: Ports 49152-65535 are used by the OS for outbound connections. Even if a port appears free, the OS may assign it to an outbound connection at any time. Use ports in the registered range (1024-49151) for services.
  • Not running as administrator for well-known ports: Binding to ports 0-1023 requires elevated privileges on most operating systems. A SocketException on these ports does not necessarily mean the port is in use — it may be a permission issue. Check the exception's SocketErrorCode property.

Summary

  • Use TcpListener with try/catch to reliably test if a port is available
  • Use IPGlobalProperties.GetActiveTcpListeners() for a non-binding check (less reliable)
  • Bind to port 0 to let the OS assign any available port automatically
  • Prefer the bind-then-use pattern to avoid race conditions between checking and binding
  • Check both IPAddress.Loopback and IPAddress.Any to match your server's binding address
  • Handle SocketException.SocketErrorCode to distinguish "port in use" from "permission denied"

Course illustration
Course illustration

All Rights Reserved.