0
# Server Implementations
1
2
Complete OSC server implementations supporting UDP and TCP protocols with multiple concurrency models (blocking, threading, forking, asyncio), flexible message dispatching, and comprehensive handler management for building robust OSC services.
3
4
## Capabilities
5
6
### Message Dispatching
7
8
Core dispatcher system for routing OSC messages to handler functions based on address patterns.
9
10
```python { .api }
11
class Dispatcher:
12
"""Maps OSC addresses to handler functions and dispatches messages."""
13
14
def __init__(self):
15
"""Initialize empty dispatcher."""
16
17
def map(self, address: str, handler: Callable, *args, needs_reply_address: bool = False):
18
"""Map OSC address pattern to handler function.
19
20
Parameters:
21
- address: OSC address pattern (supports wildcards like /synth/*)
22
- handler: Function to call when address matches
23
- args: Fixed arguments to pass to handler before OSC arguments
24
- needs_reply_address: Whether to pass client address to handler
25
"""
26
27
def unmap(self, address: str, handler: Callable, *args, needs_reply_address: bool = False):
28
"""Remove mapped handler from address pattern.
29
30
Parameters:
31
- address: OSC address pattern to unmap
32
- handler: Handler function to remove
33
- args: Fixed arguments that were mapped
34
- needs_reply_address: Must match original mapping
35
"""
36
37
def handlers_for_address(self, address_pattern: str) -> Generator[Handler, None, None]:
38
"""Get handlers matching address pattern.
39
40
Parameters:
41
- address_pattern: OSC address to match against
42
43
Yields:
44
Handler objects that match the address pattern
45
"""
46
47
def call_handlers_for_packet(self, data: bytes, client_address: Tuple[str, int]) -> List:
48
"""Invoke handlers for all messages in OSC packet.
49
50
Parameters:
51
- data: Raw OSC packet datagram
52
- client_address: Client IP address and port tuple
53
54
Returns:
55
List of handler return values for response messages
56
"""
57
58
async def async_call_handlers_for_packet(self, data: bytes, client_address: Tuple[str, int]) -> List:
59
"""Asynchronously invoke handlers for all messages in OSC packet.
60
61
Parameters:
62
- data: Raw OSC packet datagram
63
- client_address: Client IP address and port tuple
64
65
Returns:
66
List of handler return values for response messages
67
"""
68
69
def set_default_handler(self, handler: Callable, needs_reply_address: bool = False):
70
"""Set default handler for unmatched addresses.
71
72
Parameters:
73
- handler: Function to call for unmatched addresses
74
- needs_reply_address: Whether to pass client address to handler
75
"""
76
77
class Handler:
78
"""Wrapper for callback functions mapped to OSC addresses."""
79
80
def __init__(self, callback: Callable, args: Union[Any, List[Any]],
81
needs_reply_address: bool = False):
82
"""Initialize handler wrapper.
83
84
Parameters:
85
- callback: Function to call when invoked
86
- args: Fixed arguments to pass to callback
87
- needs_reply_address: Whether to pass client address
88
"""
89
90
def invoke(self, client_address: Tuple[str, int], message: OscMessage) -> Union[None, str, Tuple]:
91
"""Invoke the handler callback.
92
93
Parameters:
94
- client_address: Client IP and port
95
- message: OSC message that triggered invocation
96
97
Returns:
98
None, OSC address string, or (address, args) tuple for responses
99
"""
100
```
101
102
### UDP Servers
103
104
UDP server implementations with multiple concurrency models for different performance requirements.
105
106
```python { .api }
107
class OSCUDPServer:
108
"""Base UDP server class."""
109
110
def __init__(self, server_address: Tuple[str, int], dispatcher: Dispatcher,
111
bind_and_activate: bool = True):
112
"""Initialize UDP server.
113
114
Parameters:
115
- server_address: (IP, port) tuple to bind to
116
- dispatcher: Dispatcher for routing messages
117
- bind_and_activate: Whether to bind and activate immediately
118
"""
119
120
@property
121
def dispatcher(self) -> Dispatcher:
122
"""Access to message dispatcher."""
123
124
def verify_request(self, request, client_address) -> bool:
125
"""Validate incoming requests.
126
127
Parameters:
128
- request: Raw request data
129
- client_address: Client address tuple
130
131
Returns:
132
True if request should be processed
133
"""
134
135
class BlockingOSCUDPServer(OSCUDPServer):
136
"""Sequential UDP server handling one message at a time."""
137
138
def serve_forever(self):
139
"""Start server and handle requests sequentially."""
140
141
class ThreadingOSCUDPServer(OSCUDPServer):
142
"""UDP server spawning thread per message for concurrent handling."""
143
144
def serve_forever(self):
145
"""Start server with thread-per-message handling."""
146
147
class ForkingOSCUDPServer(OSCUDPServer):
148
"""UDP server spawning process per message (Unix only)."""
149
150
def serve_forever(self):
151
"""Start server with process-per-message handling."""
152
153
class AsyncIOOSCUDPServer:
154
"""Asynchronous UDP server using asyncio event loop."""
155
156
def __init__(self, server_address: Tuple[str, int], dispatcher: Dispatcher, loop: asyncio.BaseEventLoop):
157
"""Initialize async UDP server.
158
159
Parameters:
160
- server_address: (IP, port) tuple to bind to
161
- dispatcher: Dispatcher for routing messages
162
- loop: asyncio event loop to use for the server
163
"""
164
165
@property
166
def dispatcher(self) -> Dispatcher:
167
"""Access to message dispatcher."""
168
169
def serve(self):
170
"""Start server and run forever asynchronously."""
171
172
def create_serve_endpoint(self):
173
"""Create datagram endpoint coroutine for integration."""
174
```
175
176
### TCP Servers
177
178
TCP server implementations supporting OSC 1.0 and 1.1 protocols with connection-based handling.
179
180
```python { .api }
181
class OSCTCPServer:
182
"""Base TCP server class supporting OSC 1.0 and 1.1 protocols."""
183
184
def __init__(self, server_address: Tuple[str, int], dispatcher: Dispatcher, mode: str = "1.1"):
185
"""Initialize TCP server.
186
187
Parameters:
188
- server_address: (IP, port) tuple to bind to
189
- dispatcher: Dispatcher for routing messages
190
- mode: OSC protocol mode ("1.0" or "1.1")
191
"""
192
193
@property
194
def dispatcher(self) -> Dispatcher:
195
"""Access to message dispatcher."""
196
197
class BlockingOSCTCPServer(OSCTCPServer):
198
"""Sequential TCP server handling one connection at a time."""
199
200
def serve_forever(self):
201
"""Start server and handle connections sequentially."""
202
203
class ThreadingOSCTCPServer(OSCTCPServer):
204
"""TCP server spawning thread per connection for concurrent handling."""
205
206
def serve_forever(self):
207
"""Start server with thread-per-connection handling."""
208
209
class ForkingOSCTCPServer(OSCTCPServer):
210
"""TCP server spawning process per connection (Unix only)."""
211
212
def serve_forever(self):
213
"""Start server with process-per-connection handling."""
214
215
class AsyncOSCTCPServer:
216
"""Asynchronous TCP server with asyncio integration."""
217
218
def __init__(self, server_address: str, port: int, dispatcher: Dispatcher, mode: str = "1.1"):
219
"""Initialize async TCP server.
220
221
Parameters:
222
- server_address: IP address to bind to
223
- port: TCP port to bind to
224
- dispatcher: Dispatcher for routing messages
225
- mode: OSC protocol mode ("1.0" or "1.1")
226
"""
227
228
@property
229
def dispatcher(self) -> Dispatcher:
230
"""Access to message dispatcher."""
231
232
async def start(self):
233
"""Start server coroutine."""
234
235
async def stop(self):
236
"""Stop server and close connections."""
237
238
async def handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
239
"""Handle individual client connections.
240
241
Parameters:
242
- reader: asyncio stream reader for receiving data
243
- writer: asyncio stream writer for sending responses
244
"""
245
```
246
247
## Usage Examples
248
249
### Basic UDP Server
250
251
```python
252
from pythonosc.dispatcher import Dispatcher
253
from pythonosc import osc_server
254
import threading
255
256
# Define message handlers
257
def volume_handler(address, *args):
258
print(f"Volume: {args[0]}")
259
260
def filter_handler(address, *args):
261
print(f"Filter: {args[0]}")
262
263
def default_handler(address, *args):
264
print(f"Unhandled: {address} {args}")
265
266
# Set up dispatcher
267
dispatcher = Dispatcher()
268
dispatcher.map("/volume", volume_handler)
269
dispatcher.map("/filter", filter_handler)
270
dispatcher.set_default_handler(default_handler)
271
272
# Create and start server
273
server = osc_server.ThreadingOSCUDPServer(("127.0.0.1", 5005), dispatcher)
274
print(f"OSC Server running on {server.server_address}")
275
276
# Run server in background thread
277
server_thread = threading.Thread(target=server.serve_forever)
278
server_thread.daemon = True
279
server_thread.start()
280
281
# Server runs until program exits
282
```
283
284
### Advanced Handler Patterns
285
286
```python
287
from pythonosc.dispatcher import Dispatcher
288
from pythonosc import osc_server
289
290
# Handler with fixed arguments
291
def parametric_handler(address, channel, *osc_args):
292
print(f"Channel {channel}: {address} -> {osc_args}")
293
294
# Handler needing client address for responses
295
def response_handler(client_address, address, *args):
296
client_ip, client_port = client_address
297
print(f"Request from {client_ip}:{client_port}: {address} {args}")
298
return ("/ack", f"processed_{args[0]}") # Return response
299
300
# Wildcard pattern handler
301
def synth_handler(address, *args):
302
# Handle all /synth/* addresses
303
param = address.split('/')[-1] # Get last part of address
304
print(f"Synth parameter {param}: {args}")
305
306
# Set up complex dispatcher
307
dispatcher = Dispatcher()
308
309
# Map with fixed arguments
310
dispatcher.map("/mixer/channel/1", parametric_handler, "Channel 1")
311
dispatcher.map("/mixer/channel/2", parametric_handler, "Channel 2")
312
313
# Map handler that needs client address
314
dispatcher.map("/request/*", response_handler, needs_reply_address=True)
315
316
# Map wildcard patterns
317
dispatcher.map("/synth/*", synth_handler)
318
319
# Create server
320
server = osc_server.ThreadingOSCUDPServer(("0.0.0.0", 8000), dispatcher)
321
server.serve_forever()
322
```
323
324
### TCP Server with Protocol Modes
325
326
```python
327
from pythonosc.dispatcher import Dispatcher
328
from pythonosc import osc_tcp_server
329
import threading
330
331
def message_handler(address, *args):
332
print(f"TCP: {address} -> {args}")
333
334
# Set up dispatcher
335
dispatcher = Dispatcher()
336
dispatcher.map("/*", message_handler) # Handle all messages
337
338
# OSC 1.1 server (SLIP-encoded, default)
339
server_11 = osc_tcp_server.ThreadingOSCTCPServer(("127.0.0.1", 9000), dispatcher, mode="1.1")
340
thread_11 = threading.Thread(target=server_11.serve_forever)
341
thread_11.daemon = True
342
thread_11.start()
343
344
# OSC 1.0 server (size-prefixed)
345
server_10 = osc_tcp_server.ThreadingOSCTCPServer(("127.0.0.1", 9001), dispatcher, mode="1.0")
346
thread_10 = threading.Thread(target=server_10.serve_forever)
347
thread_10.daemon = True
348
thread_10.start()
349
350
print("TCP servers running on ports 9000 (OSC 1.1) and 9001 (OSC 1.0)")
351
```
352
353
### Asyncio UDP Server
354
355
```python
356
import asyncio
357
from pythonosc.dispatcher import Dispatcher
358
from pythonosc import osc_server
359
360
async def async_handler(address, *args):
361
print(f"Async handler: {address} -> {args}")
362
# Can perform async operations here
363
await asyncio.sleep(0.1)
364
365
# Set up dispatcher with async handler
366
dispatcher = Dispatcher()
367
dispatcher.map("/async/*", async_handler)
368
369
async def run_async_server():
370
# Get current event loop
371
loop = asyncio.get_running_loop()
372
373
# Create async UDP server
374
server = osc_server.AsyncIOOSCUDPServer(("127.0.0.1", 6000), dispatcher, loop)
375
376
# Start server
377
await server.serve()
378
379
# Run async server
380
asyncio.run(run_async_server())
381
```
382
383
### Asyncio TCP Server
384
385
```python
386
import asyncio
387
from pythonosc.dispatcher import Dispatcher
388
from pythonosc import osc_tcp_server
389
390
async def async_tcp_handler(address, *args):
391
print(f"Async TCP: {address} -> {args}")
392
return ("/response", f"processed_{len(args)}_args")
393
394
async def run_async_tcp_server():
395
# Set up dispatcher
396
dispatcher = Dispatcher()
397
dispatcher.map("/*", async_tcp_handler)
398
399
# Create and start async TCP server
400
server = osc_tcp_server.AsyncOSCTCPServer("127.0.0.1", 9002, dispatcher)
401
await server.start()
402
403
print("Async TCP server started on port 9002")
404
405
# Keep server running
406
try:
407
while True:
408
await asyncio.sleep(1)
409
except KeyboardInterrupt:
410
await server.stop()
411
print("Server stopped")
412
413
asyncio.run(run_async_tcp_server())
414
```
415
416
### Multi-Protocol Server Setup
417
418
```python
419
from pythonosc.dispatcher import Dispatcher
420
from pythonosc import osc_server, osc_tcp_server
421
import threading
422
import time
423
424
# Shared message handler
425
def unified_handler(address, *args):
426
print(f"Received: {address} -> {args}")
427
428
# Create shared dispatcher
429
dispatcher = Dispatcher()
430
dispatcher.map("/*", unified_handler)
431
432
# Start UDP server
433
udp_server = osc_server.ThreadingOSCUDPServer(("0.0.0.0", 5005), dispatcher)
434
udp_thread = threading.Thread(target=udp_server.serve_forever)
435
udp_thread.daemon = True
436
udp_thread.start()
437
438
# Start TCP servers
439
tcp_server_10 = osc_tcp_server.ThreadingOSCTCPServer(("0.0.0.0", 8000), dispatcher, mode="1.0")
440
tcp_thread_10 = threading.Thread(target=tcp_server_10.serve_forever)
441
tcp_thread_10.daemon = True
442
tcp_thread_10.start()
443
444
tcp_server_11 = osc_tcp_server.ThreadingOSCTCPServer(("0.0.0.0", 8001), dispatcher, mode="1.1")
445
tcp_thread_11 = threading.Thread(target=tcp_server_11.serve_forever)
446
tcp_thread_11.daemon = True
447
tcp_thread_11.start()
448
449
print("Multi-protocol server running:")
450
print(" UDP on port 5005")
451
print(" TCP OSC 1.0 on port 8000")
452
print(" TCP OSC 1.1 on port 8001")
453
454
# Keep main thread alive
455
try:
456
while True:
457
time.sleep(1)
458
except KeyboardInterrupt:
459
print("Shutting down servers...")
460
```
461
462
### Server with Response Messages
463
464
```python
465
from pythonosc.dispatcher import Dispatcher
466
from pythonosc import osc_server
467
468
def request_handler(client_address, address, *args):
469
"""Handler that sends responses back to client."""
470
client_ip, client_port = client_address
471
472
if address == "/get/status":
473
return ("/status", "running", 1.0)
474
elif address == "/get/info":
475
return ("/info", ["server", "v1.0", client_ip])
476
elif address == "/echo":
477
return ("/echo_response", *args)
478
else:
479
return ("/error", f"unknown_request: {address}")
480
481
# Set up dispatcher with response handler
482
dispatcher = Dispatcher()
483
dispatcher.map("/get/*", request_handler, needs_reply_address=True)
484
dispatcher.map("/echo", request_handler, needs_reply_address=True)
485
486
# Create server that can send responses
487
server = osc_server.ThreadingOSCUDPServer(("127.0.0.1", 7000), dispatcher)
488
print("Response server running on port 7000")
489
server.serve_forever()
490
```
491
492
### Server Performance Monitoring
493
494
```python
495
from pythonosc.dispatcher import Dispatcher
496
from pythonosc import osc_server
497
import time
498
import threading
499
500
class MonitoringHandler:
501
def __init__(self):
502
self.message_count = 0
503
self.start_time = time.time()
504
505
def handle_message(self, address, *args):
506
self.message_count += 1
507
if self.message_count % 1000 == 0:
508
elapsed = time.time() - self.start_time
509
rate = self.message_count / elapsed
510
print(f"Processed {self.message_count} messages ({rate:.1f} msg/sec)")
511
512
def get_stats(self, address, *args):
513
elapsed = time.time() - self.start_time
514
rate = self.message_count / elapsed if elapsed > 0 else 0
515
return ("/stats", self.message_count, elapsed, rate)
516
517
# Set up monitoring
518
monitor = MonitoringHandler()
519
dispatcher = Dispatcher()
520
dispatcher.map("/*", monitor.handle_message)
521
dispatcher.map("/get/stats", monitor.get_stats, needs_reply_address=True)
522
523
# High-performance server
524
server = osc_server.ThreadingOSCUDPServer(("0.0.0.0", 5555), dispatcher)
525
print("Monitoring server running on port 5555")
526
print("Send messages to any address, or /get/stats for statistics")
527
server.serve_forever()
528
```
529
530
## Types and Imports
531
532
```python { .api }
533
from typing import Callable, Union, List, Tuple, Generator, Any
534
import threading
535
import asyncio
536
537
from pythonosc.osc_message import OscMessage
538
from pythonosc.dispatcher import Dispatcher, Handler
539
```