1+ import socket
2+ import time
3+ import argparse
4+ import signal
5+ import sys
6+ from zeroconf import ServiceInfo , Zeroconf
7+ import netifaces # Used for more reliable IP address discovery
8+
9+ # --- Configuration ---
10+ DEFAULT_SERVICE_NAME = "AI Studio Chat UI" # User-friendly name shown in Bonjour browsers
11+ DEFAULT_DOMAIN_NAME = "chatui" # The base name, .local will be appended
12+ SERVICE_TYPE = "_http._tcp.local." # Standard for HTTP services
13+ PORT = 2048 # Port where server.py is running
14+
15+ # --- Global Variables ---
16+ zeroconf = None
17+ info = None
18+
19+ def get_lan_ip ():
20+ """
21+ Attempts to find the primary LAN IP address of the machine.
22+ Prioritizes the interface associated with the default gateway.
23+ """
24+ try :
25+ # Find the default gateway
26+ gateways = netifaces .gateways ()
27+ default_gateway_info = gateways .get ('default' , {}).get (netifaces .AF_INET )
28+
29+ if default_gateway_info :
30+ interface = default_gateway_info [1 ]
31+ addresses = netifaces .ifaddresses (interface )
32+ ipv4_info = addresses .get (netifaces .AF_INET )
33+ if ipv4_info :
34+ ip_address = ipv4_info [0 ]['addr' ]
35+ print (f" 发现默认网关接口 '{ interface } ' 的 IP: { ip_address } " ) # 中文
36+ return ip_address
37+ else :
38+ print (" 警告: 未找到默认网关信息。" ) # 中文
39+
40+ # Fallback: Iterate through all interfaces if default gateway method fails
41+ print (" 尝试遍历所有接口..." ) # 中文
42+ for interface in netifaces .interfaces ():
43+ addresses = netifaces .ifaddresses (interface )
44+ ipv4_info = addresses .get (netifaces .AF_INET )
45+ if ipv4_info :
46+ ip_address = ipv4_info [0 ]['addr' ]
47+ # Avoid loopback and obviously wrong addresses
48+ if not ip_address .startswith ("127." ) and ip_address != '0.0.0.0' :
49+ print (f" 使用接口 '{ interface } ' 的 IP: { ip_address } " ) # 中文
50+ return ip_address
51+
52+ except Exception as e :
53+ print (f" 获取 IP 地址时出错 (netifaces): { e } " ) # 中文
54+
55+ # Final fallback: Use standard socket method (less reliable)
56+ try :
57+ print (" 回退到标准 socket 方法获取 IP..." ) # 中文
58+ hostname = socket .gethostname ()
59+ ip_address = socket .gethostbyname (hostname )
60+ if not ip_address .startswith ("127." ):
61+ print (f" 使用 socket.gethostbyname 获取的 IP: { ip_address } " ) # 中文
62+ return ip_address
63+ # Try getting IP associated with default route socket connection
64+ s = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
65+ s .settimeout (0 )
66+ try :
67+ # Doesn't have to be reachable
68+ s .connect (('10.254.254.254' , 1 ))
69+ ip_address = s .getsockname ()[0 ]
70+ except Exception :
71+ ip_address = '127.0.0.1' # Default if connect fails
72+ finally :
73+ s .close ()
74+ if not ip_address .startswith ("127." ):
75+ print (f" 使用 socket 连接获取的 IP: { ip_address } " ) # 中文
76+ return ip_address
77+
78+ except socket .gaierror as e :
79+ print (f" 获取 IP 地址时出错 (socket): { e } " ) # 中文
80+
81+ print ("❌ 错误: 无法自动检测有效的局域网 IP 地址。" ) # 中文
82+ return None
83+
84+ def register_service (name : str ):
85+ global zeroconf , info
86+
87+ ip_address = get_lan_ip ()
88+ if not ip_address :
89+ print ("无法启动 mDNS 服务,因为未能确定 IP 地址。" ) # 中文
90+ sys .exit (1 )
91+
92+ hostname = f"{ name } .local." # Fully qualified domain name for mDNS
93+
94+ print (f"\n --- 正在注册 mDNS 服务 ---" ) # 中文
95+ print (f" 服务类型: { SERVICE_TYPE } " ) # 中文
96+ print (f" 服务名称: { DEFAULT_SERVICE_NAME } " ) # 中文
97+ print (f" 域名: { hostname .rstrip ('.' )} " ) # 中文
98+ print (f" IP 地址: { ip_address } " ) # 中文
99+ print (f" 端口: { PORT } " ) # 中文
100+
101+ # Construct the ServiceInfo object
102+ info = ServiceInfo (
103+ SERVICE_TYPE ,
104+ f"{ DEFAULT_SERVICE_NAME } .{ SERVICE_TYPE } " , # Unique instance name
105+ addresses = [socket .inet_aton (ip_address )],
106+ port = PORT ,
107+ properties = {}, # No specific properties needed for basic HTTP
108+ server = hostname , # This links the service to the desired hostname
109+ )
110+
111+ zeroconf = Zeroconf ()
112+ print (" 正在广播服务..." ) # 中文
113+ zeroconf .register_service (info )
114+ print (f"✅ 服务已注册。现在可以在局域网内尝试访问 http://{ hostname .rstrip ('.' )} :{ PORT } " ) # 中文
115+ print (" (按 Ctrl+C 停止广播)" ) # 中文
116+
117+ def unregister_service (signum , frame ):
118+ global zeroconf , info
119+ print ("\n --- 收到停止信号,正在注销 mDNS 服务 ---" ) # 中文
120+ if zeroconf and info :
121+ try :
122+ zeroconf .unregister_service (info )
123+ zeroconf .close ()
124+ print ("✅ 服务已注销。" ) # 中文
125+ except Exception as e :
126+ print (f" 注销服务时出错: { e } " ) # 中文
127+ sys .exit (0 )
128+
129+ if __name__ == "__main__" :
130+ parser = argparse .ArgumentParser (description = "使用 mDNS/Bonjour 在局域网内广播 AI Studio Proxy 服务。" ) # 中文
131+ parser .add_argument (
132+ "--name" ,
133+ type = str ,
134+ default = DEFAULT_DOMAIN_NAME ,
135+ help = f"要在局域网内广播的域名基础部分 (将附加 '.local')。默认为: '{ DEFAULT_DOMAIN_NAME } '" # 中文
136+ )
137+ args = parser .parse_args ()
138+
139+ # Setup signal handling for graceful shutdown
140+ signal .signal (signal .SIGINT , unregister_service ) # Handle Ctrl+C
141+ signal .signal (signal .SIGTERM , unregister_service ) # Handle termination signal
142+
143+ try :
144+ # Check dependencies
145+ try :
146+ import netifaces
147+ import zeroconf
148+ except ImportError as e :
149+ print (f"❌ 错误: 缺少依赖库 '{ e .name } '。" ) # 中文
150+ print (" 请先运行安装命令: pip install zeroconf netifaces" ) # 中文
151+ sys .exit (1 )
152+
153+ register_service (args .name )
154+ # Keep the script alive while zeroconf runs in background threads
155+ while True :
156+ time .sleep (1 )
157+ except Exception as e :
158+ print (f"\n ❌ 脚本运行时发生意外错误: { e } " ) # 中文
159+ # Ensure cleanup happens even on unexpected errors before exit
160+ unregister_service (None , None )
0 commit comments