问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

浅谈micro中的默认服务发现:mdns

发布网友 发布时间:2024-09-25 19:07

我来回答

1个回答

热心网友 时间:2024-10-04 10:31

我们定义好的服务,如何进行方便的使用,而不是直接指定特定的地址进行调用。这就需要使用额外的服务发现组件,在产线上,可能大家比较熟悉的有etcd,consul,zookeeper,这些都是功能比较完备的服务发现,各有各的优势和特点,但是在micro默认的服务发现中,通常使用的是mdns.大家可以从在上一篇文章中讲的microserver命令的输出中,看到它

2020-09-2702:52:28file=grpc/grpc.go:732level=infoRegistry[mdns]Registeringnode:server-bf8d0391-bd46-4aec-8455-3ea686463aee

那么什么是mdns?以及他是如何被作为服务发现的呢?

在计算机网络中,多播DNS(mDNS)协议在不包括本地名称服务器的小网络中将主机名解析为IP地址。它是一个零配置服务,使用与单播域名系统(DNS)基本相同的编程接口、包格式和操作语义。虽然将mDNS设计成一个独立的协议,但它可以与标准的DNS服务器协同工作。

mDNS协议发布为RFC6762,使用IP多播用户数据报协议(UDP)包,由AppleBonjour和开源的Avahi软件包实现,大多数Linux发行版都包含这些软件包。在Windows10中也实现了mDNS,最初仅限于发现联网打印机,后来也能够解析主机名。

协议简介:当mDNS客户机需要解析主机名时,它发送一个IP多播查询消息,要求具有该名称的主机标识自己。目标机器然后多播包含其IP地址的消息。然后,该子网中的所有机器都可以使用这些信息来更新它们的mDNS缓存。任何主机都可以通过发送一个活时间(TTL)等于零的响应包来放弃对某个名称的声明。默认情况下,mDNS只解析以.local顶级域名结尾的主机名。

如果.local包含不实现mDNS但可以通过传统的单播DNS服务器找到的主机,则会导致问题。解决此类冲突需要更改网络配置,而mDNS旨在避免这种更改。

包结构:mDNS消息是使用以下地址发送的多播UDP包

IPv4address224.0.0.251orIPv6addressff02::fb

UDPport5353

当使用以太网帧时,标准的IP多播MAC地址01:00:5E:00:00:FB(IPv4)或33:33:00:00:FB(IPv6)

有效负载结构基于单播DNS包格式,由头和数据两部分组成。

header与单播DNS中的相同,数据部分中的子部分也是如此:查询、答案,权威名称服务器和其他记录。每个子节中的记录数与标题中对应的*COUNT字段的值相匹配。

queries:

查询部分中记录的有线格式比单播DNS中的略微修改,增加了UNICAST-RESPONSE字段。

|Field|Description|Lengthbits||-----|---------|--------|-----||QNAME|Nameofthenodetowhichthequerypertains|Variable||QTYPE|Thetypeofthequery,i.e.thetypeofRRwhichshouldbereturnedinresponses.|16|UNICAST-RESPONSE|Booleanflagindicatingwhetheraunicast-responseisdesired|1|QCLASS|Classcode,1a.k.a."IN"fortheInternetandIPnetworks|15

与单播DNS中一样,QNAME字段由一系列称为“标签”的长度/值子字段组成。每个标签代表完全限定域名(FQDN)中点分隔的子字符串之一。该列表以单个空字节终止,表示DNS的“根”。UNICAST-RESPONSE字段用于最小化网络上不必要的广播.如果设置了位,应答者应该直接向查询节点发送定向单播响应,而不是向整个网络广播响应。QCLASS字段与单播dns中的相同。

答案,权威名称服务器和其他记录部分中的所有记录都具有相同的格式,统称为资源记录(RR)。与单播DNS相比,mDNS中的资源记录的一般格式也略有修改

|Field|Description|Lengthbits||-----|---------|--------|-----||RRNAME|Nameofthenodetowhichtherecordpertains|Variable||RRTYPE|ThetypeoftheResourceRecord|16||CACHE-FLUSH|Booleanflagindicatingwhetheroutdatedcachedrecordsshouldbepurged|1||RRCLASS|Classcode,1a.k.a."IN"fortheInternetandIPnetworks|15||TTL|Timeinterval(inseconds)thattheRRshouldbecached|32|RDLENGTH|Integerrepresentingthelength(inoctets)oftheRDATAfield|16|RDATA|Resourcedata;internalstructurevariesbyRRTYPE|Variable|

CACHE-FLUSH位用于指示邻居节点记录应覆盖此RRNAME和RRTYPE的任何现有缓存条目,而不是附加到该条目。RDATA字段的格式与单播DNS中的格式相同。然而,DNS服务发现(dn-sd),mDNS最常见的用例,指定了对其某些格式(特别是TXT记录)的轻微修改。

go-micro包中,提供了一个套默认的mDNS实现,

//Zone是一个接口,用于与服务器集成并动态提供记录的接口typeZoneinterface{//Records在一个DNS请求中返回DNS记录Records(qdns.Question)[]dns.RR}//MDNSService通过实现Zone接口用于导出命名服务typeMDNSServicestruct{Instancestring//实例名称(e.g."hostServicename")Servicestring//服务名称(e.g."_http._tcp.")Domainstring//如果为空,假设为"local"HostNamestring//主机的DNS名(e.g."mymachine.net.")Portint//服务端口IPs[]net.IP//服务的主机IP地址列表TXT[]string//ServiceTXTrecordsTTLuint32serviceAddrstring//完全限定的服务地址instanceAddrstring//完全限定的实例地址enumAddrstring//_services._dns-sd._udp.<domain>}

接着看看DNSSDService,DNSSDService是一项符合DNS-SD(RFC6762)和MDNS(RFC6762)规范的服务,用于基于本地、多播DNS的发现。DNSSDService实现了Zone接口并包裹了一个MDNSService实例。

typeDNSSDServicestruct{MDNSService*MDNSService}//Records返回一个DNS记录用于响应DNS请求。这个函数返回底层MDNSService实例的DNS响应。//在请求`_services._dns-sd._udp.<Domain>`时他还返回一个PTR记录,用于浏览底层的MDNSService实例func(s*DNSSDService)Records(qdns.Question)[]dns.RR{varrecs[]dns.RRifq.Name=="_services._dns-sd._udp."+s.MDNSService.Domain+"."{recs=s.dnssdMetaQueryRecords(q)}returnappend(recs,s.MDNSService.Records(q)...)}//dnssdMetaQueryRecords返回DNS记录在一次meta-query查询中,一个meta-query查询形如`_services._dns-sd._udp.<Domain>`func(s*DNSSDService)dnssdMetaQueryRecords(qdns.Question)[]dns.RR{//预期行为,就像RFC文档中描述的那样://...对于网络管理员来说,在网络上查找已播发服务类型的列表可能会很有用,//即使这些服务名称只是不透明的标识符,并且孤立地提供的信息也不多。////为了这个目的,一个指定的元查询据定义了,一个DNS查询PTR记录通常有一个名字//"_services._dns-sd._udp.<Domain>"将返回一系列PTR记录。//wheretherdataofeachPTRrecordisthetwo-abel//每个PTR记录的rdata都是一个双标签<Service>名称,加上相同的domain.例如`_http._tcp.<Domain>`//在PTRrdata中包含域可以在单播DNS响应中更好地进行名称压缩,//但是对于服务类型枚举而言,只有前两个标签是相关的。//然后,可以使用这两个标签的服务类型在此<Domain>或其他域中构造后续的服务实例枚举PTR查询,以发现该服务类型的实例。return[]dns.RR{&dns.PTR{Hdr:dns.RR_Header{Name:q.Name,Rrtype:dns.TypePTR,Class:dns.ClassINET,Ttl:defaultTTL,},Ptr:s.MDNSService.serviceAddr,},}}

为了部署一个符合DNS-SD的mDNS服务,建议只注册被包裹的实例,例如

service:=&mdns.DNSSDService{MDNSService:&mdns.MDNSService{Instance:"MyFoobarService",Service:"_foobar._tcp",Port:8000,}}server,err:=mdns.NewServer(&mdns.Config{Zone:service})iferr!=nil{log.Fatalf("Errorcreatingserver:%v",err)}deferserver.Shutdown()

再来看看如何实现一个mDNS服务器的

//Config用于配置mDNS服务器。typeConfigstruct{//Zone必须提供支持查询响应ZoneZone//Iface如果提供了,绑定多播监听。如果没提供,使用系统默认的多播接口Iface*net.Interface//Port如果不是0,使用它代替5353Portint//GetMachineIP是一个函数,用于返回本地机器的IP地址GetMachineIPGetMachineIP//LocalhostChecking如果设置了,请求服务,并发送响应到0.0.0.0,如果目标IP是这个主机//如果机器位于VPN上,该VPN阻止非标准端口上的通信,则很有用LocalhostCheckingbool}//Server是一个mDNS服务器,用于监听mDNS查询并响应如果有一个存在的本地记录。typeServerstruct{config*Configipv4List*net.UDPConnipv6List*net.UDPConnshutdownboolshutdownChchanstruct{}shutdownLocksync.Mutexwgsync.WaitGroupoutboundIPnet.IP}//NewServer用于根据一个config创建一个mDNS服务器,然后再goroutine中开启监听funcNewServer(config*Config)(*Server,error){setCustomPort(config.Port)//Createthelisteners//Createwildcardconnections(because:5353canbealreadytakenbyotherapps)ipv4List,_:=net.ListenUDP("udp4",mdnsWildcardAddrIPv4)ipv6List,_:=net.ListenUDP("udp6",mdnsWildcardAddrIPv6)ifipv4List==nil&&ipv6List==nil{returnnil,fmt.Errorf("[ERR]mdns:Failedtobindtoanyudpport!")}ifipv4List==nil{ipv4List=&net.UDPConn{}}ifipv6List==nil{ipv6List=&net.UDPConn{}}//Joinmulticastgroupstoreceiveannouncementsp1:=ipv4.NewPacketConn(ipv4List)p2:=ipv6.NewPacketConn(ipv6List)p1.SetMulticastLoopback(true)p2.SetMulticastLoopback(true)ifconfig.Iface!=nil{iferr:=p1.JoinGroup(config.Iface,&net.UDPAddr{IP:mdnsGroupIPv4});err!=nil{returnnil,err}iferr:=p2.JoinGroup(config.Iface,&net.UDPAddr{IP:mdnsGroupIPv6});err!=nil{returnnil,err}}else{ifaces,err:=net.Interfaces()iferr!=nil{returnnil,err}errCount1,errCount2:=0,0for_,iface:=rangeifaces{iferr:=p1.JoinGroup(&iface,&net.UDPAddr{IP:mdnsGroupIPv4});err!=nil{errCount1++}iferr:=p2.JoinGroup(&iface,&net.UDPAddr{IP:mdnsGroupIPv6});err!=nil{errCount2++}}iflen(ifaces)==errCount1&&len(ifaces)==errCount2{returnnil,fmt.Errorf("Failedtojoinmulticastgrouponallinterfaces!")}}ipFunc:=getOutboundIPifconfig.GetMachineIP!=nil{ipFunc=config.GetMachineIP}s:=&Server{config:config,ipv4List:ipv4List,ipv6List:ipv6List,shutdownCh:make(chanstruct{}),outboundIP:ipFunc(),}gos.recv(s.ipv4List)gos.recv(s.ipv6List)s.wg.Add(1)//probe广播或者单播返回响应gos.probe()returns,nil}

实现了一个mDNS服务器之后,就可以进行服务查询了。那么mdns又是如何作为一个注册中心来作为服务发现使用的呢?

packageregistryimport("errors")const(//WildcardDomainindicatesanydomainWildcardDomain="*"//DefaultDomaintouseifnonewasprovidedinoptionsDefaultDomain="micro")var(//NotfounderrorwhenGetServiceiscalledErrNotFound=errors.New("servicenotfound")//WatcherstoppederrorwhenwatcherisstoppedErrWatcherStopped=errors.New("watcherstopped"))//注册中心提供了一个接口用于服务发现,以及给广泛实现({consul,etcd,zookeeper,...})提供了一个抽象typeRegistryinterface{Init(...Option)errorOptions()OptionsRegister(*Service,...RegisterOption)errorDeregister(*Service,...DeregisterOption)errorGetService(string,...GetOption)([]*Service,error)ListServices(...ListOption)([]*Service,error)Watch(...WatchOption)(Watcher,error)String()string}typeServicestruct{Namestring`json:"name"`Versionstring`json:"version"`Metadatamap[string]string`json:"metadata"`Endpoints[]*Endpoint`json:"endpoints"`Nodes[]*Node`json:"nodes"`}typeNodestruct{Idstring`json:"id"`Addressstring`json:"address"`Metadatamap[string]string`json:"metadata"`}typeEndpointstruct{Namestring`json:"name"`Request*Value`json:"request"`Response*Value`json:"response"`Metadatamap[string]string`json:"metadata"`}typeValuestruct{Namestring`json:"name"`Typestring`json:"type"`Values[]*Value`json:"values"`}typeOptionfunc(*Options)typeRegisterOptionfunc(*RegisterOptions)typeWatchOptionfunc(*WatchOptions)typeDeregisterOptionfunc(*DeregisterOptions)typeGetOptionfunc(*GetOptions)typeListOptionfunc(*ListOptions)

监控功能接口

packageregistryimport"time"//Watcher是一个接口类型,可以返回注册中心中服务的变更typeWatcherinterface{//Next是一个阻塞调用Next()(*Result,error)Stop()}//Result是调用watcher上Next方法的返回值。action可以是创建,更新,删除typeResultstruct{ActionstringService*Service}//EventType定义了注册中心的事件类型typeEventTypeintconst(//Create事件是在注册新服务时发生的CreateEventType=iota//Delete事件是在取消注册时发生的Delete//Update事件是在服务发生变更时发生的Update)//String返回事件类型的字符串形式func(tEventType)String()string{switcht{caseCreate:return"create"caseDelete:return"delete"caseUpdate:return"update"default:return"unknown"}}//Event是注册中心事件typeEventstruct{//IdisregistryidIdstring//TypedefinestypeofeventTypeEventType//TimestampiseventtimestampTimestamptime.Time//ServiceisregistryserviceService*Service}

我们看看,microserver是如何一步步的使用mdns的作为注册中心的。我们知道,microserver默认使用的服务器是grpcServer.

funcnewGRPCServer(opts...server.Option)server.Server{options:=newOptions(opts...)//createagrpcserversrv:=&grpcServer{opts:options,rpc:&rServer{serviceMap:make(map[string]*service),},handlers:make(map[string]server.Handler),subscribers:make(map[*subscriber][]broker.Subscriber),exit:make(chanchanerror),wg:wait(options.Context),}//configurethegrpcserversrv.configure()returnsrv}

而grpcserver在创建Server时,创建的选项就默认使用了我们的mdns.

|Field|Description|Lengthbits||-----|---------|--------|-----||QNAME|Nameofthenodetowhichthequerypertains|Variable||QTYPE|Thetypeofthequery,i.e.thetypeofRRwhichshouldbereturnedinresponses.|16|UNICAST-RESPONSE|Booleanflagindicatingwh

热心网友 时间:2024-10-04 10:34

我们定义好的服务,如何进行方便的使用,而不是直接指定特定的地址进行调用。这就需要使用额外的服务发现组件,在产线上,可能大家比较熟悉的有etcd,consul,zookeeper,这些都是功能比较完备的服务发现,各有各的优势和特点,但是在micro默认的服务发现中,通常使用的是mdns.大家可以从在上一篇文章中讲的microserver命令的输出中,看到它

2020-09-2702:52:28file=grpc/grpc.go:732level=infoRegistry[mdns]Registeringnode:server-bf8d0391-bd46-4aec-8455-3ea686463aee

那么什么是mdns?以及他是如何被作为服务发现的呢?

在计算机网络中,多播DNS(mDNS)协议在不包括本地名称服务器的小网络中将主机名解析为IP地址。它是一个零配置服务,使用与单播域名系统(DNS)基本相同的编程接口、包格式和操作语义。虽然将mDNS设计成一个独立的协议,但它可以与标准的DNS服务器协同工作。

mDNS协议发布为RFC6762,使用IP多播用户数据报协议(UDP)包,由AppleBonjour和开源的Avahi软件包实现,大多数Linux发行版都包含这些软件包。在Windows10中也实现了mDNS,最初仅限于发现联网打印机,后来也能够解析主机名。

协议简介:当mDNS客户机需要解析主机名时,它发送一个IP多播查询消息,要求具有该名称的主机标识自己。目标机器然后多播包含其IP地址的消息。然后,该子网中的所有机器都可以使用这些信息来更新它们的mDNS缓存。任何主机都可以通过发送一个活时间(TTL)等于零的响应包来放弃对某个名称的声明。默认情况下,mDNS只解析以.local顶级域名结尾的主机名。

如果.local包含不实现mDNS但可以通过传统的单播DNS服务器找到的主机,则会导致问题。解决此类冲突需要更改网络配置,而mDNS旨在避免这种更改。

包结构:mDNS消息是使用以下地址发送的多播UDP包

IPv4address224.0.0.251orIPv6addressff02::fb

UDPport5353

当使用以太网帧时,标准的IP多播MAC地址01:00:5E:00:00:FB(IPv4)或33:33:00:00:FB(IPv6)

有效负载结构基于单播DNS包格式,由头和数据两部分组成。

header与单播DNS中的相同,数据部分中的子部分也是如此:查询、答案,权威名称服务器和其他记录。每个子节中的记录数与标题中对应的*COUNT字段的值相匹配。

queries:

查询部分中记录的有线格式比单播DNS中的略微修改,增加了UNICAST-RESPONSE字段。

|Field|Description|Lengthbits||-----|---------|--------|-----||QNAME|Nameofthenodetowhichthequerypertains|Variable||QTYPE|Thetypeofthequery,i.e.thetypeofRRwhichshouldbereturnedinresponses.|16|UNICAST-RESPONSE|Booleanflagindicatingwhetheraunicast-responseisdesired|1|QCLASS|Classcode,1a.k.a."IN"fortheInternetandIPnetworks|15

与单播DNS中一样,QNAME字段由一系列称为“标签”的长度/值子字段组成。每个标签代表完全限定域名(FQDN)中点分隔的子字符串之一。该列表以单个空字节终止,表示DNS的“根”。UNICAST-RESPONSE字段用于最小化网络上不必要的广播.如果设置了位,应答者应该直接向查询节点发送定向单播响应,而不是向整个网络广播响应。QCLASS字段与单播dns中的相同。

答案,权威名称服务器和其他记录部分中的所有记录都具有相同的格式,统称为资源记录(RR)。与单播DNS相比,mDNS中的资源记录的一般格式也略有修改

|Field|Description|Lengthbits||-----|---------|--------|-----||RRNAME|Nameofthenodetowhichtherecordpertains|Variable||RRTYPE|ThetypeoftheResourceRecord|16||CACHE-FLUSH|Booleanflagindicatingwhetheroutdatedcachedrecordsshouldbepurged|1||RRCLASS|Classcode,1a.k.a."IN"fortheInternetandIPnetworks|15||TTL|Timeinterval(inseconds)thattheRRshouldbecached|32|RDLENGTH|Integerrepresentingthelength(inoctets)oftheRDATAfield|16|RDATA|Resourcedata;internalstructurevariesbyRRTYPE|Variable|

CACHE-FLUSH位用于指示邻居节点记录应覆盖此RRNAME和RRTYPE的任何现有缓存条目,而不是附加到该条目。RDATA字段的格式与单播DNS中的格式相同。然而,DNS服务发现(dn-sd),mDNS最常见的用例,指定了对其某些格式(特别是TXT记录)的轻微修改。

go-micro包中,提供了一个套默认的mDNS实现,

//Zone是一个接口,用于与服务器集成并动态提供记录的接口typeZoneinterface{//Records在一个DNS请求中返回DNS记录Records(qdns.Question)[]dns.RR}//MDNSService通过实现Zone接口用于导出命名服务typeMDNSServicestruct{Instancestring//实例名称(e.g."hostServicename")Servicestring//服务名称(e.g."_http._tcp.")Domainstring//如果为空,假设为"local"HostNamestring//主机的DNS名(e.g."mymachine.net.")Portint//服务端口IPs[]net.IP//服务的主机IP地址列表TXT[]string//ServiceTXTrecordsTTLuint32serviceAddrstring//完全限定的服务地址instanceAddrstring//完全限定的实例地址enumAddrstring//_services._dns-sd._udp.<domain>}

接着看看DNSSDService,DNSSDService是一项符合DNS-SD(RFC6762)和MDNS(RFC6762)规范的服务,用于基于本地、多播DNS的发现。DNSSDService实现了Zone接口并包裹了一个MDNSService实例。

typeDNSSDServicestruct{MDNSService*MDNSService}//Records返回一个DNS记录用于响应DNS请求。这个函数返回底层MDNSService实例的DNS响应。//在请求`_services._dns-sd._udp.<Domain>`时他还返回一个PTR记录,用于浏览底层的MDNSService实例func(s*DNSSDService)Records(qdns.Question)[]dns.RR{varrecs[]dns.RRifq.Name=="_services._dns-sd._udp."+s.MDNSService.Domain+"."{recs=s.dnssdMetaQueryRecords(q)}returnappend(recs,s.MDNSService.Records(q)...)}//dnssdMetaQueryRecords返回DNS记录在一次meta-query查询中,一个meta-query查询形如`_services._dns-sd._udp.<Domain>`func(s*DNSSDService)dnssdMetaQueryRecords(qdns.Question)[]dns.RR{//预期行为,就像RFC文档中描述的那样://...对于网络管理员来说,在网络上查找已播发服务类型的列表可能会很有用,//即使这些服务名称只是不透明的标识符,并且孤立地提供的信息也不多。////为了这个目的,一个指定的元查询据定义了,一个DNS查询PTR记录通常有一个名字//"_services._dns-sd._udp.<Domain>"将返回一系列PTR记录。//wheretherdataofeachPTRrecordisthetwo-abel//每个PTR记录的rdata都是一个双标签<Service>名称,加上相同的domain.例如`_http._tcp.<Domain>`//在PTRrdata中包含域可以在单播DNS响应中更好地进行名称压缩,//但是对于服务类型枚举而言,只有前两个标签是相关的。//然后,可以使用这两个标签的服务类型在此<Domain>或其他域中构造后续的服务实例枚举PTR查询,以发现该服务类型的实例。return[]dns.RR{&dns.PTR{Hdr:dns.RR_Header{Name:q.Name,Rrtype:dns.TypePTR,Class:dns.ClassINET,Ttl:defaultTTL,},Ptr:s.MDNSService.serviceAddr,},}}

为了部署一个符合DNS-SD的mDNS服务,建议只注册被包裹的实例,例如

service:=&mdns.DNSSDService{MDNSService:&mdns.MDNSService{Instance:"MyFoobarService",Service:"_foobar._tcp",Port:8000,}}server,err:=mdns.NewServer(&mdns.Config{Zone:service})iferr!=nil{log.Fatalf("Errorcreatingserver:%v",err)}deferserver.Shutdown()

再来看看如何实现一个mDNS服务器的

//Config用于配置mDNS服务器。typeConfigstruct{//Zone必须提供支持查询响应ZoneZone//Iface如果提供了,绑定多播监听。如果没提供,使用系统默认的多播接口Iface*net.Interface//Port如果不是0,使用它代替5353Portint//GetMachineIP是一个函数,用于返回本地机器的IP地址GetMachineIPGetMachineIP//LocalhostChecking如果设置了,请求服务,并发送响应到0.0.0.0,如果目标IP是这个主机//如果机器位于VPN上,该VPN阻止非标准端口上的通信,则很有用LocalhostCheckingbool}//Server是一个mDNS服务器,用于监听mDNS查询并响应如果有一个存在的本地记录。typeServerstruct{config*Configipv4List*net.UDPConnipv6List*net.UDPConnshutdownboolshutdownChchanstruct{}shutdownLocksync.Mutexwgsync.WaitGroupoutboundIPnet.IP}//NewServer用于根据一个config创建一个mDNS服务器,然后再goroutine中开启监听funcNewServer(config*Config)(*Server,error){setCustomPort(config.Port)//Createthelisteners//Createwildcardconnections(because:5353canbealreadytakenbyotherapps)ipv4List,_:=net.ListenUDP("udp4",mdnsWildcardAddrIPv4)ipv6List,_:=net.ListenUDP("udp6",mdnsWildcardAddrIPv6)ifipv4List==nil&&ipv6List==nil{returnnil,fmt.Errorf("[ERR]mdns:Failedtobindtoanyudpport!")}ifipv4List==nil{ipv4List=&net.UDPConn{}}ifipv6List==nil{ipv6List=&net.UDPConn{}}//Joinmulticastgroupstoreceiveannouncementsp1:=ipv4.NewPacketConn(ipv4List)p2:=ipv6.NewPacketConn(ipv6List)p1.SetMulticastLoopback(true)p2.SetMulticastLoopback(true)ifconfig.Iface!=nil{iferr:=p1.JoinGroup(config.Iface,&net.UDPAddr{IP:mdnsGroupIPv4});err!=nil{returnnil,err}iferr:=p2.JoinGroup(config.Iface,&net.UDPAddr{IP:mdnsGroupIPv6});err!=nil{returnnil,err}}else{ifaces,err:=net.Interfaces()iferr!=nil{returnnil,err}errCount1,errCount2:=0,0for_,iface:=rangeifaces{iferr:=p1.JoinGroup(&iface,&net.UDPAddr{IP:mdnsGroupIPv4});err!=nil{errCount1++}iferr:=p2.JoinGroup(&iface,&net.UDPAddr{IP:mdnsGroupIPv6});err!=nil{errCount2++}}iflen(ifaces)==errCount1&&len(ifaces)==errCount2{returnnil,fmt.Errorf("Failedtojoinmulticastgrouponallinterfaces!")}}ipFunc:=getOutboundIPifconfig.GetMachineIP!=nil{ipFunc=config.GetMachineIP}s:=&Server{config:config,ipv4List:ipv4List,ipv6List:ipv6List,shutdownCh:make(chanstruct{}),outboundIP:ipFunc(),}gos.recv(s.ipv4List)gos.recv(s.ipv6List)s.wg.Add(1)//probe广播或者单播返回响应gos.probe()returns,nil}

实现了一个mDNS服务器之后,就可以进行服务查询了。那么mdns又是如何作为一个注册中心来作为服务发现使用的呢?

packageregistryimport("errors")const(//WildcardDomainindicatesanydomainWildcardDomain="*"//DefaultDomaintouseifnonewasprovidedinoptionsDefaultDomain="micro")var(//NotfounderrorwhenGetServiceiscalledErrNotFound=errors.New("servicenotfound")//WatcherstoppederrorwhenwatcherisstoppedErrWatcherStopped=errors.New("watcherstopped"))//注册中心提供了一个接口用于服务发现,以及给广泛实现({consul,etcd,zookeeper,...})提供了一个抽象typeRegistryinterface{Init(...Option)errorOptions()OptionsRegister(*Service,...RegisterOption)errorDeregister(*Service,...DeregisterOption)errorGetService(string,...GetOption)([]*Service,error)ListServices(...ListOption)([]*Service,error)Watch(...WatchOption)(Watcher,error)String()string}typeServicestruct{Namestring`json:"name"`Versionstring`json:"version"`Metadatamap[string]string`json:"metadata"`Endpoints[]*Endpoint`json:"endpoints"`Nodes[]*Node`json:"nodes"`}typeNodestruct{Idstring`json:"id"`Addressstring`json:"address"`Metadatamap[string]string`json:"metadata"`}typeEndpointstruct{Namestring`json:"name"`Request*Value`json:"request"`Response*Value`json:"response"`Metadatamap[string]string`json:"metadata"`}typeValuestruct{Namestring`json:"name"`Typestring`json:"type"`Values[]*Value`json:"values"`}typeOptionfunc(*Options)typeRegisterOptionfunc(*RegisterOptions)typeWatchOptionfunc(*WatchOptions)typeDeregisterOptionfunc(*DeregisterOptions)typeGetOptionfunc(*GetOptions)typeListOptionfunc(*ListOptions)

监控功能接口

packageregistryimport"time"//Watcher是一个接口类型,可以返回注册中心中服务的变更typeWatcherinterface{//Next是一个阻塞调用Next()(*Result,error)Stop()}//Result是调用watcher上Next方法的返回值。action可以是创建,更新,删除typeResultstruct{ActionstringService*Service}//EventType定义了注册中心的事件类型typeEventTypeintconst(//Create事件是在注册新服务时发生的CreateEventType=iota//Delete事件是在取消注册时发生的Delete//Update事件是在服务发生变更时发生的Update)//String返回事件类型的字符串形式func(tEventType)String()string{switcht{caseCreate:return"create"caseDelete:return"delete"caseUpdate:return"update"default:return"unknown"}}//Event是注册中心事件typeEventstruct{//IdisregistryidIdstring//TypedefinestypeofeventTypeEventType//TimestampiseventtimestampTimestamptime.Time//ServiceisregistryserviceService*Service}

我们看看,microserver是如何一步步的使用mdns的作为注册中心的。我们知道,microserver默认使用的服务器是grpcServer.

funcnewGRPCServer(opts...server.Option)server.Server{options:=newOptions(opts...)//createagrpcserversrv:=&grpcServer{opts:options,rpc:&rServer{serviceMap:make(map[string]*service),},handlers:make(map[string]server.Handler),subscribers:make(map[*subscriber][]broker.Subscriber),exit:make(chanchanerror),wg:wait(options.Context),}//configurethegrpcserversrv.configure()returnsrv}

而grpcserver在创建Server时,创建的选项就默认使用了我们的mdns.

|Field|Description|Lengthbits||-----|---------|--------|-----||QNAME|Nameofthenodetowhichthequerypertains|Variable||QTYPE|Thetypeofthequery,i.e.thetypeofRRwhichshouldbereturnedinresponses.|16|UNICAST-RESPONSE|Booleanflagindicatingwh
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
牛窝骨怎么做好吃又简单 亲人必知:几种安全有效的避孕方法 最舒服又安全避孕的方法 有哪些避孕方法最好 事后避孕方法除了吃药还有什么? 不吃避孕药的最有效避孕方法 除了吃避孕药还有什么办法避孕 Cle de Peau BEAUTE 肌肤之钥 金致奢华至尊抗衰老面霜 50ml-详细... CPB金致乳霜怎么样 面霜推荐-Cle de Peau BEAUTE 肌肤之钥 金致奢华至尊抗衰老面霜... 三星c5显示两个相同的app ,怎么去掉一个,如果卸载一个就同时卸载了 三星盖乐世C5出来两个联系人图标,怎么删除一个啊??? 身高173.5体重123斤应该穿什么尺码的衣服和裤子 三星C5N符号怎么去掉? 身高175,体重123斤左右,穿多少码衣服? ...他是把我当孩子看吗,他说这个我怎么不懂呢,他为什么叫我小朋友... 中华民国25年20分的硬币是没有A的现在是什么价格! 身高174,体重123斤,穿多大的衣服?是xl还是xxl还是xxxl? 为什么让我遇见,性格完全跟我一样女孩 比我大三岁生日就差一天,爱好... 体重123身高1.73应该选什么型号的衣服 男生170体重123左右,该穿多大码的毛衣,,速求,真实回答? 中国百强城市评选指标构成是怎样的? 2023年中国百强城市榜发布:深圳超广州,杭州力压成都领衔新一线城市 内蒙古乌兰察布市丰镇市2016年精准扶贫信息怎么查询,到那里查询,有可以... 我今天早上梦到自己牙掉了了没出血,这是不好的预兆吗? 、乌兰察布、丰镇市在2021年10月份办的退休现在医保忐么办 口罩的防护效果和颜色有关吗? ...一年利率为2.25%,一年利息是多少,一共取多少钱? 做梦梦到自己牙掉了,不疼也没流血,这意味着什么?谢谢 惠普1020如何添加碳粉 可爱多专业青少年家具公司简介 可爱多的家具质量怎么样? 捡了个手机怎么解锁 例假前胸疼和怀孕胸疼有什么区别 捡了一个华为手机,怎么解锁? 来例假前胸疼是怎么回事 捡了一个华为手机怎么解锁 捡到一个手机,怎么才能解开屏幕密码 捡了个手机,怎么解锁 与智者同行后面完整的句子 例假来前几天胸部胀痛正常吗 与志者同行下一句 怎么样把自己喜欢的歌下载到手机上啊 一分钟 | 读懂中国职业经理人的发展史 如何在苹果手机上下载MP3歌曲? 木命佩戴什么最旺属牛 魅力让女人疯狂情商最高最浪漫的星座男 2024年真爱相伴这些星座将收获幸福 女孩喜欢星座男生排行是那些呢? 为什麼我到我的电脑的地址栏那裏输入网址会弹出个框框