#!/usr/bin/env python3 """ hdmonServer.py A ZeroMQ broker that runs on well-known host:ports so that processes can dynamically connect as publishers and subscribers. This broker uses an XSUB/XPUB proxy to forward messages. The server uses configuration from either command-line arguments or the environment variable HDMON_UDL, which should have the format: ZMQ://host:pub-port:sub-port/hdmonctl If the local hostname (ignoring any domain part) does not match the host specified in the UDL, a warning is printed. """ import zmq import sys import argparse import os import socket import threading import time def parse_args(): parser = argparse.ArgumentParser( description="hdmonServer ZMQ broker with configurable host, publisher and subscriber ports." ) parser.add_argument('--host', type=str, help="Server host") parser.add_argument('--pub-port', type=int, help="Publisher port (XSUB socket)") parser.add_argument('--sub-port', type=int, help="Subscriber port (XPUB socket)") parser.add_argument('--topic', type=str, help="Topic (for consistency; not used by the server)") parser.add_argument("--verbose", action="store_true", help="Enable verbose mode") return parser.parse_args() def parse_udl(args): """ Determine host, pub_port, sub_port, and topic from command-line arguments and/or the environment variable HDMON_UDL. The expected format for HDMON_UDL is: ZMQ://host:pub-port:sub-port/hdmonctl Command-line arguments override the environment. Defaults: host: localhost pub_port: 11246 sub_port: 11247 topic: hdmonctl """ host = args.host pub_port = args.pub_port sub_port = args.sub_port topic = args.topic if not (host and pub_port and sub_port and topic): udl = os.environ.get("HDMON_UDL") if udl: try: # Expected format: "ZMQ://host:pub_port:sub_port/topic" prefix = "ZMQ://" if udl.startswith(prefix): # Remove the "ZMQ://" prefix. udl = udl[len(prefix):] # Split into address and topic parts. addr_part, topic_part = udl.split("/", 1) addr_fields = addr_part.split(":") if len(addr_fields) >= 3: if not host: host = addr_fields[0] if not pub_port: pub_port = int(addr_fields[1]) if not sub_port: sub_port = int(addr_fields[2]) if not topic: topic = topic_part except Exception as e: print("Error parsing HDMON_UDL:", e) if not host: host = "localhost" if not pub_port: pub_port = 11246 if not sub_port: sub_port = 11247 if not topic: topic = "hdmonctl" return host, pub_port, sub_port, topic def main(): args = parse_args() host, pub_port, sub_port, topic = parse_udl(args) # Get local hostname and compare (ignoring any domain part) local_hostname = socket.gethostname().split('.')[0] server_host = host.split('.')[0] if local_hostname.lower() != server_host.lower(): print(f"Warning: Local hostname ({local_hostname}) does not match UDL host ({server_host}).") # Create a new ZeroMQ context with a single I/O thread. context = zmq.Context(1) # Create an XSUB socket for incoming publisher messages. # Publishers should connect to this endpoint. frontend = context.socket(zmq.XSUB) frontend_endpoint = f"tcp://*:{pub_port}" frontend.bind(frontend_endpoint) # Create an XPUB socket for outgoing subscriber messages. # Subscribers should connect to this endpoint. backend = context.socket(zmq.XPUB) backend_endpoint = f"tcp://*:{sub_port}" backend.bind(backend_endpoint) print("hdmonServer is running...") print(f" - Publisher endpoint (XSUB): {frontend_endpoint}") print(f" - Subscriber endpoint (XPUB): {backend_endpoint}") print("Press Ctrl+C to exit.") if args.verbose: print("Verbose mode is enabled.") # Set up a capture socket to log all proxy messages. capture = context.socket(zmq.PAIR) capture.bind("inproc://capture") def capture_loop(): while True: try: msg = capture.recv_multipart() # Print each captured message. print("Capture:", [part.decode('utf-8', errors='replace') for part in msg]) except Exception as e: print("Capture error:", e) time.sleep(1) capture_thread = threading.Thread(target=capture_loop, daemon=True) capture_thread.start() try: zmq.proxy(frontend, backend, capture) except KeyboardInterrupt: print("\nhdmonServer interrupted. Shutting down.") except Exception as e: print("Error in proxy:", e, file=sys.stderr) else: try: zmq.proxy(frontend, backend) except KeyboardInterrupt: print("\nhdmonServer interrupted. Shutting down.") except Exception as e: print("Error in proxy:", e, file=sys.stderr) frontend.close() backend.close() context.term() if __name__ == '__main__': main()