# How Scrcpy Server Communicates with the Client: UNIX Socket Architecture Explained

> Discover how the Scrcpy server uses UNIX sockets to communicate with the client. Learn how traffic is split for video, audio, and control over dedicated channels.

- Repository: [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy)
- Tags: internals
- Published: 2026-02-25

---

**The Scrcpy server communicates with the desktop client over one to three local UNIX-domain sockets, splitting traffic across dedicated channels for video streaming, audio streaming, and bidirectional control messages.**

The Genymobile/scrcpy project uses a sophisticated socket-based architecture to mirror Android devices to desktops with minimal latency. Understanding how the **server component communicates with the client** requires examining the Java-based server implementation that runs on the Android device and its protocol for exchanging screen data and input events.

## Socket Channel Architecture

The Scrcpy server establishes up to three distinct logical channels over local UNIX-domain sockets. Each channel serves a specific purpose in the scrcpy server client communication pipeline:

- **First socket (Control + Metadata)**: Transmits the device name for window titling and carries full-duplex control traffic including key events, clipboard data, and UHID messages
- **Video socket**: Streams raw H.264 encoded screen data from server to client
- **Audio socket** (optional): Streams raw PCM audio data when audio forwarding is enabled

The server determines which channels to open based on command-line flags (`--no-video`, `--no-audio`, `--no-control`).

## Establishing the Connection

Connection initialization happens in `DesktopConnection.open()`, located in [`server/src/main/java/com/genymobile/scrcpy/device/DesktopConnection.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/device/DesktopConnection.java). This method creates the socket infrastructure using either **forward-tunnel** (server listens) or **reverse-tunnel** (client connects) mode.

The socket name follows the pattern `scrcpy_<scid>` where `scid` is an optional session identifier. Depending on the tunnel direction, the server either accepts incoming connections or connects to an existing socket:

```java
// Simplified logic from DesktopConnection.open()
if (tunnelForward) {
    try (LocalServerSocket server = new LocalServerSocket(socketName)) {
        if (video)   videoSocket = server.accept();
        if (audio)   audioSocket = server.accept();
        if (control) controlSocket = server.accept();
    }
} else {
    if (video)   videoSocket   = connect(socketName);
    if (audio)   audioSocket   = connect(socketName);
    if (control) controlSocket = connect(socketName);
}

```

After establishing sockets, the server immediately sends device metadata via `sendDeviceMeta()`, writing a fixed-size, zero-padded UTF-8 string containing the device name to the first socket.

## The Control Channel (Bidirectional)

The control socket enables two-way scrcpy server client communication through the `ControlChannel` class in [`server/src/main/java/com/genymobile/scrcpy/control/ControlChannel.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/control/ControlChannel.java). This wrapper encapsulates both reading and writing capabilities:

```java
public ControlChannel(LocalSocket controlSocket) throws IOException {
    reader = new ControlMessageReader(controlSocket.getInputStream());
    writer = new DeviceMessageWriter(controlSocket.getOutputStream());
}

```

**Inbound traffic** (client → server) uses `ControlMessageReader` to parse a binary protocol consisting of a type byte followed by payload data. This handles `KEYCODE` events, touch injections, and clipboard requests.

**Outbound traffic** (server → client) uses `DeviceMessageWriter` to serialize `DeviceMessage` objects containing clipboard updates, acknowledgments, and UHID output reports. Both sides use the protocol definitions in [`ControlMessage.java`](https://github.com/Genymobile/scrcpy/blob/main/ControlMessage.java) and [`DeviceMessage.java`](https://github.com/Genymobile/scrcpy/blob/main/DeviceMessage.java).

## Video and Audio Streaming

Unlike the control channel, media streaming bypasses the Java layer's message framing. The server exposes raw file descriptors via `DesktopConnection.getVideoFd()` and `getAudioFd()`, passing these directly to native encoders:

```java
// Server side after DesktopConnection initialization
FileDescriptor videoFd = connection.getVideoFd();   // Passed to SurfaceEncoder
FileDescriptor audioFd = connection.getAudioFd();   // Passed to AudioEncoder

```

The native **SurfaceEncoder** writes H.264 NAL units directly to the video socket's file descriptor, while **AudioEncoder** streams PCM data to the audio socket. The client reads these raw byte streams and decodes them using FFmpeg or MediaCodec, achieving zero-copy performance for high-framerate screen mirroring.

## Complete Communication Example

The following example demonstrates the full initialization sequence used in [`Server.java`](https://github.com/Genymobile/scrcpy/blob/main/Server.java):

```java
int scid = -1;                      // No SCID -> socket name = "scrcpy"
boolean tunnelForward = false;      // Client connects directly (reverse tunnel)
boolean wantVideo = true;
boolean wantAudio = true;
boolean wantControl = true;

// Step 1: Establish socket connections
DesktopConnection conn = DesktopConnection.open(
        scid, tunnelForward, wantVideo, wantAudio, wantControl, false);

// Step 2: Send device metadata for window title
conn.sendDeviceMeta("Pixel_5");

// Step 3: Obtain file descriptors for native encoders
FileDescriptor videoFd = conn.getVideoFd();
FileDescriptor audioFd = conn.getAudioFd();

// Step 4: Initialize control channel for input handling
ControlChannel control = conn.getControlChannel();

// Step 5: Process incoming control messages
ControlMessage msg = control.recv();
if (msg.getType() == ControlMessage.TYPE_INJECT_KEYCODE) {
    int action = msg.getKeycodeAction();
    int keycode = msg.getKeycode();
    // Forward to Android InputManager
}

// Step 6: Send clipboard update to client
DeviceMessage clipboard = DeviceMessage.createClipboard("Android text");
control.send(clipboard);

```

## Summary

- **Scrcpy server client communication** relies on 1-3 local UNIX-domain sockets created via `DesktopConnection.open()`.
- The **control channel** provides full-duplex communication for input events and device messages using `ControlMessageReader` and `DeviceMessageWriter`.
- **Video and audio data** stream through raw file descriptors passed to native encoders, avoiding Java overhead for media frames.
- The server supports both **forward-tunnel** (server listens) and **reverse-tunnel** (server connects) modes for different ADB forwarding scenarios.
- Device metadata transmits as a fixed-size UTF-8 string on the first socket before media streaming begins.

## Frequently Asked Questions

### What transport protocol does scrcpy use for server-client communication?

Scrcpy uses **local UNIX-domain sockets** on the Android device, tunneled through ADB to the desktop client. The server creates abstract socket names (e.g., `scrcpy` or `scrcpy_<scid>`) and communicates over one to three separate sockets for video, audio, and control data.

### How does scrcpy handle bidirectional control messages?

The `ControlChannel` class wraps the control socket with two components: a `ControlMessageReader` that parses incoming binary messages (key events, touches) from the client, and a `DeviceMessageWriter` that serializes outgoing messages (clipboard updates, ACKs) to the client. Both use the same binary protocol defined in [`ControlMessage.java`](https://github.com/Genymobile/scrcpy/blob/main/ControlMessage.java) and [`DeviceMessage.java`](https://github.com/Genymobile/scrcpy/blob/main/DeviceMessage.java).

### Can scrcpy stream video and audio over the same socket?

No, scrcpy uses **dedicated sockets** for each media type. The video socket carries raw H.264 data, while the optional audio socket carries PCM audio data. This separation allows the client to handle video and audio decoding independently without multiplexing overhead on the media streams.

### What is the difference between forward and reverse tunnel mode?

In **reverse-tunnel mode** (default), the client creates the listening socket and the server connects to it using `connect(socketName)`. In **forward-tunnel mode**, the server creates a `LocalServerSocket` and accepts connections from the client. The mode is determined by the `tunnelForward` parameter in `DesktopConnection.open()` and affects how ADB port forwarding is configured.