# How IPC Communication Between Multiple Shadowsocks Instances Works

> Learn how multiple Shadowsocks Windows instances communicate using named pipes and mutexes. Understand the IPC protocol for seamless command delegation.

- Repository: [shadowsocks/shadowsocks-windows](https://github.com/shadowsocks/shadowsocks-windows)
- Tags: internals
- Published: 2026-03-05

---

**Shadowsocks-Windows uses a named mutex to enforce a single UI instance and delegates commands from secondary processes to the primary instance through a lightweight named-pipe IPC protocol.**

Shadowsocks-Windows is architected to prevent multiple UI windows from running simultaneously while still supporting command-line interactions. When you launch a second copy of the application, it detects the existing instance and routes operations through a **IPC communication between multiple Shadowsocks instances** mechanism based on Windows named pipes and a custom binary protocol.

## Single-Instance Detection via Named Mutex

The application implements a robust singleton pattern using a system-wide mutex to ensure only one UI process manages the proxy configuration.

### Mutex Creation and Naming Convention

At startup, the application attempts to create a named mutex with a unique identifier derived from the executable path. In [`shadowsocks-csharp/Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Program.cs), the mutex is defined as:

```csharp
new Mutex(true, $"Shadowsocks_{ExecutablePath.GetHashCode()}")

```

This naming scheme ensures that different builds or copies of the executable running from separate directories operate independently, while identical paths share the same mutex namespace.

### Detecting Existing Instances

The startup logic checks mutex ownership to determine if another instance is active. From [`shadowsocks-csharp/Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Program.cs) (lines 46-49):

```csharp
bool hasAnotherInstance = !mutex.WaitOne(TimeSpan.Zero, true);

```

If `hasAnotherInstance` evaluates to **true**, the current process recognizes it is secondary and must communicate with the primary instance rather than launching its own UI.

## Named Pipe IPC Protocol Architecture

The IPC system relies on Windows **named pipes** with a uniquely generated pipe name that matches both instances.

### Unique Pipe Identification

Both primary and secondary instances derive the same pipe identifier from the executable path hash. In [`shadowsocks-csharp/Controller/Service/IPCService.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/IPCService.cs) (line 22), the pipe name follows this pattern:

```

Shadowsocks\<ExecutablePathHash>

```

This ensures that multiple Shadowsocks installations on the same machine do not interfere with each other's IPC channels.

### Message Structure

The protocol uses a minimal binary format consisting of:
1. A **4-byte opcode** (integer)
2. A **4-byte length prefix** (integer)
3. The **UTF-8 payload** (variable length)

Currently, the only supported operation is `OP_OPEN_URL = 1`, which instructs the primary instance to parse and add a Shadowsocks server URL.

## Server-Side Implementation in the Primary Instance

The primary instance spawns an `IPCService` object that listens for incoming connections on a background Task.

### The IPCService Listening Loop

The server implementation in [`shadowsocks-csharp/Controller/Service/IPCService.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/IPCService.cs) (lines 26-48) creates a `NamedPipeServerStream` in an infinite loop:

```csharp
public async Task RunServer()
{
    while (true)
    {
        using (var pipe = new NamedPipeServerStream(pipeName, ...))
        {
            await pipe.WaitForConnectionAsync();
            int opcode = pipe.ReadByte(); // Simplified; actual uses 4-byte read
            if (opcode == OP_OPEN_URL)
            {
                // Read length and URL bytes
                byte[] urlBytes = ...;
                string url = Encoding.UTF8.GetString(urlBytes);
                OpenUrlRequested?.Invoke(this, new RequestAddUrlEventArgs(url));
            }
        }
    }
}

```

This asynchronous pattern ensures the UI remains responsive while handling IPC requests.

### Processing Open URL Requests

When the server receives a valid `OP_OPEN_URL` command, it raises the `OpenUrlRequested` event. The main application subscribes to this event in [`shadowsocks-csharp/Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Program.cs):

```csharp
ipcService.OpenUrlRequested += (_, e) => 
    MainController.AskAddServerBySSURL(e.Url);

```

The `AskAddServerBySSURL` method in [`shadowsocks-csharp/Controller/ShadowsocksController.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/ShadowsocksController.cs) (lines 421-432) parses the **ss://** link and adds the server configuration to the running instance.

## Client-Side Implementation for Secondary Instances

When a secondary instance detects an existing primary process, it acts as an IPC client rather than initializing the full application stack.

### RequestOpenUrl Method

The client implementation in [`shadowsocks-csharp/Controller/Service/IPCService.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/IPCService.cs) (lines 74-86) connects to the named pipe with a 10-millisecond timeout:

```csharp
public static void RequestOpenUrl(string url)
{
    using (var pipe = new NamedPipeClientStream(".", pipeName, ...))
    {
        pipe.Connect(10);
        byte[] urlBytes = Encoding.UTF8.GetBytes(url);
        pipe.Write(BitConverter.GetBytes(OP_OPEN_URL), 0, 4);
        pipe.Write(BitConverter.GetBytes(urlBytes.Length), 0, 4);
        pipe.Write(urlBytes, 0, urlBytes.Length);
    }
}

```

After writing the opcode, length, and payload, the client closes the pipe and terminates immediately without displaying any UI.

### Command-Line Trigger (--openurl)

Secondary instances are typically launched via command-line arguments. To add a server from the command line:

```bash
Shadowsocks.exe --openurl "ss://aes-256-cfb:password@host:port#My%20Server"

```

If the secondary instance is started without the `--openurl` argument, it simply displays a message box directing the user to the existing tray icon, as implemented in [`shadowsocks-csharp/Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Program.cs) (lines 61-66).

## Summary

- **Mutex-based singleton**: Shadowsocks-Windows creates a named mutex (`Shadowsocks_{hash}`) to prevent multiple UI instances.
- **Named pipe identification**: IPC channels use the identifier `Shadowsocks\<ExecutablePathHash>` to ensure uniqueness per installation.
- **Binary protocol**: Communication uses a 4-byte opcode followed by length-prefixed UTF-8 strings.
- **Current capability**: The system exclusively supports the `OP_OPEN_URL` operation for adding servers via ss:// links.
- **Graceful delegation**: Secondary processes exit immediately after relaying commands, leaving the primary instance to handle all state changes.

## Frequently Asked Questions

### How does Shadowsocks-Windows prevent multiple UI windows from opening?

The application creates a system-wide named mutex at startup in [`shadowsocks-csharp/Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Program.cs). If `mutex.WaitOne()` returns false, indicating another process already holds the mutex, the new instance recognizes it is secondary and either sends an IPC command or displays a notification, then exits without creating a window.

### What protocol does the IPC system use to communicate?

The IPC mechanism uses Windows named pipes with a custom binary protocol. Messages consist of a 4-byte integer opcode (currently only `OP_OPEN_URL = 1` is supported), followed by a 4-byte length field, and the UTF-8 encoded payload. This implementation resides in [`shadowsocks-csharp/Controller/Service/IPCService.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/IPCService.cs).

### Can I programmatically send commands to a running Shadowsocks instance?

Yes, any process can invoke the static `IPCService.RequestOpenUrl()` method after connecting to the named pipe `Shadowsocks\<ExecutablePathHash>`. This allows scripts or browser extensions to add servers by sending properly formatted ss:// URLs to the primary instance without restarting the application.

### Where is the IPC logic implemented in the source code?

The three critical files are:
- [`shadowsocks-csharp/Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Program.cs): Handles mutex creation, instance detection, and event subscription.
- [`shadowsocks-csharp/Controller/Service/IPCService.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/IPCService.cs): Implements the named pipe server and client.
- [`shadowsocks-csharp/Controller/ShadowsocksController.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/ShadowsocksController.cs): Contains `AskAddServerBySSURL`, which processes the URL received via IPC.