Dart SocketControlMessage
last modified October 18, 2026
The SocketControlMessage class in Dart represents a control
message that travels with data on a socket. Control messages carry
platform-specific information and can also transfer open resource handles.
Control messages are received as part of a SocketMessage from
RawSocket.readMessage. They are sent with
RawSocket.sendMessage. This functionality is part of Dart's
dart:io library and is used with Unix domain sockets on platforms
that support handle passing.
Basic Definition
SocketControlMessage is an abstract class representing a
platform-specific control message. It is used with RawSocket,
not with RawDatagramSocket.
A control message is created with SocketControlMessage.fromHandles.
The received object exposes the platform-specific level,
type, and raw data bytes, and
extractHandles returns any transferred ResourceHandle
objects.
Basic SocketControlMessage Usage
This example sends a file handle in a control message over a Unix domain socket.
import 'dart:async';
import 'dart:io';
Future<void> main() async {
var path = '/tmp/dart_control_message.sock';
try {
File(path).deleteSync();
} catch (_) {}
var address = InternetAddress(path, type: InternetAddressType.unix);
var server = await RawServerSocket.bind(address, 0);
var received = Completer<void>();
server.listen((client) {
client.listen((event) {
if (event == RawSocketEvent.read) {
var message = client.readMessage();
if (message != null) {
print('Received ${message.controlMessages.length} control message(s)');
for (var control in message.controlMessages) {
print('level: ${control.level}, type: ${control.type}');
}
received.complete();
client.close();
}
}
});
});
var client = await RawSocket.connect(address, 0);
var file = File('/tmp/dart_control_resource.txt')
..writeAsStringSync('handle data');
var handle = ResourceHandle.fromFile(file.openSync());
var control = SocketControlMessage.fromHandles([handle]);
client.sendMessage([control], [1, 2, 3]);
await received.future;
await client.close();
await server.close();
}
We create a Unix domain socket server and client. The client attaches a file
handle to a control message and sends it together with the payload. The server
receives a SocketMessage; its controlMessages list
contains the control message. The level and type
values are platform-specific.
$ dart main.dart Received 1 control message(s) level: 1, type: 1
Working with Control Message Data
This example inspects the raw payload and control message data.
import 'dart:async';
import 'dart:io';
Future<void> main() async {
var path = '/tmp/dart_control_data.sock';
try {
File(path).deleteSync();
} catch (_) {}
var address = InternetAddress(path, type: InternetAddressType.unix);
var server = await RawServerSocket.bind(address, 0);
var received = Completer<void>();
server.listen((client) {
client.listen((event) {
if (event == RawSocketEvent.read) {
var message = client.readMessage();
if (message != null) {
print('Data bytes: ${message.data}');
for (var control in message.controlMessages) {
print('Control data bytes: ${control.data}');
}
received.complete();
client.close();
}
}
});
});
var client = await RawSocket.connect(address, 0);
var file = File('/tmp/dart_control_data.txt')
..writeAsStringSync('control data');
var handle = ResourceHandle.fromFile(file.openSync());
var control = SocketControlMessage.fromHandles([handle]);
client.sendMessage([control], [10, 20, 30]);
await received.future;
await client.close();
await server.close();
}
We inspect SocketMessage.data for the payload and
SocketControlMessage.data for the raw control bytes. The format of
the control data is defined by the operating system and protocol.
$ dart main.dart Data bytes: [10, 20, 30] Control data bytes: [13, 0, 0, 0]
Sending Control Messages
This example shows how to send packets with control messages attached.
import 'dart:async';
import 'dart:io';
Future<void> main() async {
var path = '/tmp/dart_control_send.sock';
try {
File(path).deleteSync();
} catch (_) {}
var address = InternetAddress(path, type: InternetAddressType.unix);
var server = await RawServerSocket.bind(address, 0);
var received = Completer<void>();
server.listen((client) {
client.listen((event) {
if (event == RawSocketEvent.read) {
var message = client.readMessage();
if (message != null) {
print('Received payload: ${message.data}');
for (var control in message.controlMessages) {
var handles = control.extractHandles();
for (var handle in handles) {
var file = handle.toFile();
print('Received handle content: '
'${String.fromCharCodes(file.readSync(32))}');
file.closeSync();
}
}
received.complete();
client.close();
}
}
});
});
var client = await RawSocket.connect(address, 0);
var file = File('/tmp/dart_control_send.txt')
..writeAsStringSync('hello handle');
var raf = file.openSync();
var control = SocketControlMessage.fromHandles([
ResourceHandle.fromFile(raf)
]);
client.sendMessage([control], 'payload'.codeUnits);
await received.future;
await client.close();
await server.close();
}
The receiver calls extractHandles and converts each
ResourceHandle to a RandomAccessFile with
toFile. This is how open file descriptors are passed between
processes over a Unix domain socket.
$ dart main.dart Received payload: [112, 97, 121, 108, 111, 97, 100] Received handle content: hello handle
Handling Multiple Control Messages
This example demonstrates processing different types of control messages.
import 'dart:async';
import 'dart:io';
Future<void> main() async {
var path = '/tmp/dart_control_multiple.sock';
try {
File(path).deleteSync();
} catch (_) {}
var address = InternetAddress(path, type: InternetAddressType.unix);
var server = await RawServerSocket.bind(address, 0);
var received = Completer<void>();
server.listen((client) {
client.listen((event) {
if (event == RawSocketEvent.read) {
var message = client.readMessage();
if (message != null) {
print('Control messages: ${message.controlMessages.length}');
for (var control in message.controlMessages) {
var handles = control.extractHandles();
for (var handle in handles) {
var file = handle.toFile();
print('Handle content: '
'${String.fromCharCodes(file.readSync(32))}');
file.closeSync();
}
}
received.complete();
client.close();
}
}
});
});
var client = await RawSocket.connect(address, 0);
var first = File('/tmp/dart_control_first.txt')
..writeAsStringSync('first');
var second = File('/tmp/dart_control_second.txt')
..writeAsStringSync('second');
var firstMessage = SocketControlMessage.fromHandles([
ResourceHandle.fromFile(first.openSync())
]);
var secondMessage = SocketControlMessage.fromHandles([
ResourceHandle.fromFile(second.openSync())
]);
client.sendMessage([firstMessage, secondMessage], [9, 8, 7]);
await received.future;
await client.close();
await server.close();
}
The sendMessage method accepts a list of control messages. The
receiver iterates over the received control messages and extracts the handles
from each one.
$ dart main.dart Control messages: 1 Handle content: first Handle content: second
Platform-Specific Control Messages
This example shows platform-specific handling of control messages.
import 'dart:async';
import 'dart:io';
Future<void> main() async {
var path = '/tmp/dart_control_platform.sock';
try {
File(path).deleteSync();
} catch (_) {}
var address = InternetAddress(path, type: InternetAddressType.unix);
var server = await RawServerSocket.bind(address, 0);
var received = Completer<void>();
server.listen((client) {
client.listen((event) {
if (event == RawSocketEvent.read) {
var message = client.readMessage();
if (message != null) {
print('Platform: ${Platform.operatingSystem}');
print('Payload: ${message.data}');
print('Control messages: ${message.controlMessages.length}');
for (var control in message.controlMessages) {
print('level=${control.level}, type=${control.type}');
}
received.complete();
client.close();
}
}
});
});
var client = await RawSocket.connect(address, 0);
if (Platform.isLinux || Platform.isMacOS) {
var file = File('/tmp/dart_control_platform.txt')
..writeAsStringSync('platform data');
var control = SocketControlMessage.fromHandles([
ResourceHandle.fromFile(file.openSync())
]);
client.sendMessage([control], [1, 2, 3]);
} else {
client.write([1, 2, 3]);
}
await received.future;
await client.close();
await server.close();
}
Control message support and the meaning of the level and
type values vary by operating system. This example passes a
handle on Linux and macOS and sends plain data elsewhere. Unix domain socket
support is also platform-dependent.
$ dart main.dart Platform: linux Payload: [1, 2, 3] Control messages: 1 level=1, type=1
Best Practices
- Check platform: Control message support and values vary by OS
- Use readMessage: Control messages arrive through
SocketMessage, notreceive - Handle null:
readMessagemay return null - Extract once: Call
extractHandlesonly once per received message - Cleanup: Close the files or sockets obtained from extracted handles
Source
Dart SocketControlMessage Documentation
This tutorial covered Dart's SocketControlMessage class with practical examples showing reception, sending, and platform-specific handling of control messages.
Author
List all Dart tutorials.