NSD 服务发现
流程
mermaid
flowchart TD
subgraph Server[服务端流程]
S1[创建ServiceInfo对象] --> S2[初始化Zeroconf实例]
S2 --> S3[注册并发布服务]
S3 --> S4[注销服务]
S4 --> S5[关闭Zeroconf实例]
end
subgraph Client[客户端流程]
C1[初始化NsdManager] --> C2[启动服务发现]
C2 --> C3[发现服务,触发onServiceFound]
C3 --> C4[解析服务]
C4 --> C5[停止服务发现,释放资源]
end
%% 服务端-客户端交互
S3 --> |发布服务| C3服务端
zeroconf 库是 Python 中实现 mDNS/DNS-SD 的常用工具,可以通过 ServiceInfo 设置 TXT 记录
Python
from zeroconf import Zeroconf, ServiceInfo
import socket
# 准备服务属性 (TXT记录)
properties = {
b'version': b'1.2.3',
b'user': b'Alice',
b'mode': b'debug'
}
# 创建ServiceInfo对象
service_info = ServiceInfo(
type_="_test._tcp.local.", # 服务类型,必须与Android端发现类型匹配
name="MyService._test._tcp.local.", # 服务实例名称
addresses=[socket.inet_aton('0.0.0.0')], # 传入二进制IP地址的列表
port=8080, # 服务端口
weight=0,
priority=0,
properties=properties, # 对应Android端的Attributes
server="myhost.local." # 主机名
)
# 注册并发布服务
zeroconf = Zeroconf()
try:
zeroconf.register_service(service_info)
print("服务已发布")
input("按回车键退出...\n") # 保持程序运行
finally:
zeroconf.unregister_service(service_info)
zeroconf.close()客户端
Java
public class HomeAssistantSearcher implements NsdManager.DiscoveryListener, DefaultLifecycleObserver {
private static final String SERVICE_TYPE = "_test._tcp.";
private static final String TAG = "HomeAssistantSearcher";
private static final ReentrantLock lock = new ReentrantLock();
private final NsdManager nsdManager;
private final WifiManager wifiManager;
private final ValueCallback<String> valueCallback;
private boolean isSearching = false;
private WifiManager.MulticastLock multicastLock = null;
public HomeAssistantSearcher(NsdManager nsdManager, WifiManager wifiManager, ValueCallback<String> valueCallback) {
this.nsdManager = nsdManager;
this.wifiManager = wifiManager;
this.valueCallback = valueCallback;
}
private void beginSearch() {
if (isSearching) {
return;
}
isSearching = true;
try {
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, this);
} catch (Exception e) {
Timber.e(e, "Issue starting discover.");
isSearching = false;
return;
}
try {
if (wifiManager != null && multicastLock == null) {
multicastLock = wifiManager.createMulticastLock(TAG);
multicastLock.setReferenceCounted(true);
multicastLock.acquire();
}
} catch (Exception e) {
Timber.e(e, "Issue acquiring multicast lock");
// Discovery might still work so continue
}
}
// Stop search
public void stopSearch() {
if (!isSearching) {
return;
}
isSearching = false;
try {
nsdManager.stopServiceDiscovery(this);
if (multicastLock != null) {
multicastLock.release();
multicastLock = null;
}
} catch (Exception e) {
Timber.e(e, "Issue stopping discovery");
}
}
@Override
public void onResume(LifecycleOwner owner) {
beginSearch();
}
@Override
public void onPause(LifecycleOwner owner) {
stopSearch();
}
@Override
public void onDiscoveryStarted(String regType) {
Timber.d("Service discovery started");
}
@Override
public void onServiceFound(NsdServiceInfo foundService) {
Timber.i("Service discovery found: %s", foundService);
lock.lock();
nsdManager.resolveService(
foundService,
new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo failedService, int errorCode) {
Timber.w("Failed to resolve service: %s, error: %d", failedService, errorCode);
lock.unlock();
}
@Override
public void onServiceResolved(NsdServiceInfo resolvedService) {
Timber.i("Service resolved: %s", resolvedService);
if (resolvedService != null && resolvedService.getHost() != null) {
Map<String, byte[]> attributes = resolvedService.getAttributes();
valueCallback.onReceiveValue(resolvedService.toString().replace(", ", "\n"));
byte[] baseUrl = attributes.get("base_url");
byte[] versionAttr = attributes.get("version");
if (baseUrl != null && versionAttr != null) {
}
}
lock.unlock();
}
}
);
}
@Override
public void onServiceLost(NsdServiceInfo service) {
Timber.e("service lost: %s", service);
}
@Override
public void onDiscoveryStopped(String serviceType) {
Timber.i("Discovery stopped: %s", serviceType);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Timber.e("Discovery failed: Error code:%d", errorCode);
stopSearch();
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Timber.e("Discovery failed: Error code:%d", errorCode);
stopSearch();
}
}