使用 Service Location Protocol 自动化客户机的管理

帮助网络客户机实现自治

Service Location Protocol(服务位置协议,SLP)是一个 Internet 标准 RFC,也是一套软件框架,它允许网络应用程序发现并配置网络服务。您可以使用 SLP 来开发零配置的应用程序,并简化网络互连设备的管理。本文首先简要介绍 SLP 及其架构的内容,然后使用一个开放源码实现来展示该协议的用法。

M. Tim Jones ([email protected]), 资深首席软件工程师, Emulex

2006 年 4 月 03 日

  • expand内容

服务发现(service discovery) 是在网络环境中发现必须使用的服务的能力。例如,如果要在网络上新安装一个桌面系统,应该如何对这个系统配置邮件服务器、缺省网关(用来对本机之外的连接进行路由)或可用打印机呢?通常,这些都是信息都要向系统管理员进行咨询,然后在每台机器上手工进行配置 —— 考虑到网络中的情况不可避免地会发生变化,这一耗时的过程需要反复多次进行维护。

因此,当网络上的服务被删除或位置发生变化时,如果可以自动发现这些服务,您就可以看到这样做的价值有多大。在本文中,我将向您介绍一种专门用于简化该过程的协议 —— Service Location Protocol(SLP)—— 并深入介绍这种协议用来进行服务广播和发现所采用的架构。

发现的历史

服务发现是现代网络中一个有趣的概念;如果仔细了解一下,您会发现这个协议现已投入实际应用。Dynamic Host Configuration Protocol(动态主机配置协议,DHCP)提供了第一级的服务发现功能,它不但可以提供 IP 地址租借功能,还可以进行 Domain Name Service(域名服务,DNS)标识,从而使域名等 URL 可以成功得到解析。

RFC 2782 对 DNS 进行了扩展,它允许对服务或协议进行查询,并返回地址记录(就是所发现的服务的地址)。这个 RFC 通常就称作 DNS SRV(或 DNS for Service Discovery —— DNS SD)。与传统的 DNS 将完整的域名解析成 IP 地址一样,DNS SRV 也可以将服务/协议解析成 IP 地址。例如,下面的代码:

_mail._tcp.mtjones.com
_ntp._udp.mtjones.com

所请求的就是域 mtjones.com 中基于 TCP 的 SMTP 邮件服务器的 IP 地址和基于 UDP(用户数据报协议)的 NTP 时间服务器的 IP 地址。

您会发现有几个以某种形式提供服务发现功能的协议。表 1 列出了几种竞争技术。正如您可以看到的一样,这个领域中有几种竞争标准。有些是补充性的(例如当今非常常用的 DHCP 和 DNS ),另外一些是竞争性的(例如 SLP 和 UPnP(Universal Plug and Play))。

表 1. 可以提供某种形式的服务发现功能的协议
协议 说明
DHCP 动态主机配置协议(Dynamic Host Configuration Protocol,IETF 标准)
DNS 域名服务(Domain Name Service,IETF 标准)
ZeroConf 零配置 IP 网络(Zero Configuration IP Networking,IETF 标准)
SSDP 简单服务发现协议(Simple Service Discovery Protocol,IETF 标准,在 Microsoft® UPnP 中使用)
LDAP 轻量级目录访问协议(Lightweight Directory Access Protocol,IETF 标准)
NIS 网络信息服务(Network Information Service)
Bonjour Apple® 计算机用于 ZeroConf 的名字(之前称为 Rendezvous
Jini Java™ 开放架构,其中包括动态发现功能(Sun Microsystems)
Salutation 服务发现协议(Salutation Consortium)
SLP 服务位置协议(Service Location Protocol,IETF 标准)

对于这些竞争标准了解这么多已足够。现在,让我们开始深入介绍 SLP 的内容,了解一下它可以实现什么功能,如何实现这些功能,以及如何应用到应用程序中。

SLP 架构

SLP 是应用程序在局域网(LAN)和广域网(WAN)中使用的一种动态配置协议。作为客户机,SLP 可以用来使用服务类型和属性(例如 printer 类型和 color 属性)来寻找服务。作为服务提供者,SLP 可以用来广播服务以及具有某种特定属性的可用服务。

SLP 也可以在很多架构中使用,包括集中架构和分散架构,前者使用一个中心服务器来缓存服务广播,后者广播需要对每个查询进行响应。

SLP 角色

下面让我们首先了解一下启用了 SLP 的网络中的角色。SLP 的角色有三种:

  • 用户代理(User-Agent,UA)
  • 服务代理(Service-Agent,SA)
  • 可选的 目录代理(Directory-Agent,DA)

尽管此处给出了这些区别,但重要的是要了解一个应用程序既可以发出请求,也可以广播服务,因此可以同时成为 UA 和 SA。

UA 在 SLP 中是客户机。UA 代表应用程序进行操作,用来标识网络上的给定服务。UA 通过与 DA(如果可用)进行通信或直接与 SA 进行通信来实现这种功能。在探讨 SLP 的组织时,我们将更详细地介绍这个概念。

SA 代表在网络上提供服务的应用程序进行操作。如果存在 DA,SA 就与这些可用服务进行通信,并将自己的位置告诉 DA。否则在生成请求时,SA 直接对 UA 进行响应。

最后,DA 在使用中心点进行广播和查询的 SLP 中是一个可选角色。如果存在 DA,SA 发送的服务广播就在 DA 中进行缓存。然后当 UA 对服务进行查询时,DA 就直接进行响应。拥有 DA 就意味着不必每个服务提供者都需要广播服务。一个实体可以注册服务来代表这些服务,从而最小化它们的负载。

SLP 组织

由于 DA 是 SLP 中的一个可选元素,因此 SLP 角色用来进行通信的方法可能会有很大的不同。但是首先,SA 和 UA 如何判断 DA 是否真正存在呢?

发现网络中的 DA 在 SLP 中可以通过两种方法实现:

  • 第一种方法称为 主动 DA 发现(Active DA Discovery),即使用 SLP 自己来发现一个 DA 服务。UA 发出一个多播请求 service:directory-agent。接收到这个请求的 DA 直接使用自己的地址对 UA 进行响应。
  • DA 发现使用的第二种方法称为 被动 DA 发现(Passive DA Discovery)。在这种模式中,DA 周期性地发送 DA 广播,从而使 UA 和 SA 了解 DA 的存在。如果对主动 DA 发现的 UDP 响应丢失,由被动 DA 发现所发出的周期性广播即可自动解决这个问题。

虽然这种架构为 SLP 增加了一点复杂性,但是如果不存在 DA,就可能会增加 SA 的负担。

单播、广播和多播 UDP

单播通信 发生在两个端点之间(一点与另外一点之间)。广播通信 描述的是一个端点到 LAN 中其他所有端点的通信。多播通信 则是指组内的端点之间的通信。

无 DA 的服务发现

下面让我们从 SLP 的分散组织开始介绍。在这种技术中,网络上没有 DA;因此,UA 会直接与 SA 进行通信并处理请求。图形化的描述请参见图 1。由于不存在 DA,因此 SA 就不能缓存自己的服务,从而必须直接响应 UA 的请求。

图 1. 无 DA 的服务发现

从图 1 中可以看出,UA 发送一个服务请求来标识所需要的服务。由于不存在 DA,因此这个请求是以多播的形式发出的。任何提供所请求的服务的 SA 都需要以单播响应的方式对 UA 进行响应。响应中标识了服务及其位置(地址和端口)。

有 DA 的服务发现

当可以在网络上使用 DA 时,UA 和 SA 都直接与 DA 进行通信,发送自己的服务请求和广播信息。DA 是通过被动或主动 DA 发现的方式来发现的。在如图 2 所示的例子中,UA 和 SA 都是通过被动发现来寻找 DA 的(通过来 DA 发出的多播)。这种发现是以 DA 发出的一条 DAAdvert 消息进行通信的。

图 2. 有 DA 的服务发现

SA 将自己的服务广播给 DA 所采用的方式如下:SA 向 DA 发送一条 SAAdvert 消息,DA 使用一条 SrvAck 消息进行响应,表示对接收到广播进行应答。当 SA 知道 DA 的地址之后,SA 就可以采用 UDP 帧的形式将自己的请求通过单播通信发送给 DA。

UA 使用 SrvRqst 消息向 DA 请求某个服务的位置。DA 使用一个单播 UDP SrvRply 消息进行响应,其中包含了服务的地址和端口。

在大型的网络中,使用 DA 是值得的,因为这样可以减少在网络上发送的多播消息的数量。如果不使用 DA,所有的 DA 和 UA 都需要接收所有的多播消息;而大部分消息都是被忽略掉的(因为自己并不是这条消息所发送的目标),因此主机的处理器资源就在很大程度上被浪费了。

定义服务

SLP 中的服务是通过服务 URL 来定义的。URL 的形式如下:

service:://

其中  是正在广播的服务的类型,而  则是服务的位置(域名或 IP 地址和端口号)。例如,下面这个例子就广播一个邮件服务器的地址:mail.com,端口是 25:

service:mail://mail.com:25

如果 UA 请求了 "service:mail" 位置,就会返回定义这个位置的服务 URL。注意这可能会返回多个服务,UA 负责确定使用哪一个。如果您只希望返回一个服务,那么 SLP 提供了一种方法对搜索进行限制。这就是 范围(scope),它允许您为特定的用户而对服务进行分段。

当 SA 广播新服务时,可同时可选地在一个范围中进行广播。范围只是一个用来标识特定区域的字符串。例如,如果您有两个打印机,一个在一层,另外一个在二层,就可以将范围定义为 first_floor 和 second_floor。当 UA 请求 service:printer 时,就会应用特定楼层的范围,从而将搜索结果限制为特定楼层上(即在某个范围内)的打印机。

您可以使用范围来定义服务的近似性(例如同一层)或服务的管理所有权(例如 财会 或 销售)。如果一个服务是共享的,那么这个服务可能会使用不同的范围被广播多次。

SLP 消息

SLP 实现了很多消息,其中一些我们已经在图 1 和图 2 中展示过。除注册和查找服务之外,SLP 还提供了一些消息用来注销那些不在需要的服务,搜索所有基于某种类型的服务,以及请求某个特定服务的属性。

表 2 定义了在 SLP 中使用的消息。我在这儿列出这些消息仅仅是为了给出一个完整的列表;并不打算深入介绍它们。如果您使用带有 SLP 插件的 Ethereal 协议分析器,就会看到这些消息及其解析的数据。

表 2. SLP 中的消息通信
消息 说明
SrvReg SA DA 注册(广播)服务
SrvDeReg SA DA 注销服务
SrvAck DA SA 对 SrvReg 和 SrvDereg 消息进行响应
SrvRqst UA SA/DA 请求服务的位置
SrvRply SA/DA UA 响应对某个服务位置的 SrvRqst 请求
SrvTypeRqst UA SA/DA 根据已定义的类型来请求服务的位置
SrvTypeReply SA/DA UA 响应对某个服务位置的 SrvTypeRqst 请求
AttrRqst UA SA/DA 请求给定服务的属性
AttrRply SA/DA UA 响应对某个具有某种属性的服务 AttrRqst 请求
DAAdvert DA UA/SA DA 发出用来通知 UA 和 SA 它们的存在和位置的消息
SAAdvert SA UA SA 发出用来通知 UA 自己的存在和位置的消息

一个 SLP 实现:OpenSLP

OpenSLP 是 SLP 的一个开放源码实现。其中包含了您可以用来编写 SA 和 UA 使用的 API 库。它还提供了一个 DA,名为 slpd,这可以使用其配置文件 /etc/slp.conf 进行配置。

OpenSLP API 使用了一种回调架构。您可以使用一个 API 函数来生成请求;在 API 函数的上下文中,可以使用响应数据或状态调用一个回调函数。表 3 给出了 OpenSLP 中提供的 API 函数及其用途。SLPRegSLPDereg 和 SLPFind* 函数都使用一个回调函数来返回自己的状态和可选数据。

表 3. OpenSLP 库的主要函数
函数 说明
SLPOpen 打开一个 OpenSLP API 实例
SLPClose 关闭一个 OpenSLP API 实例
SLPReg 注册一个服务和 URL
SLPDereg 为服务注销一个 URL
SLPFindSrvs 查找已注册的给定服务类型的服务
SLPFindSrvTypes 查找为某个范围注册的所有服务类型

除了 API 库之外,OpenSLP 还提供了 slptool,使用这个工具可以交互地执行大部分 SLP 的功能。这个工具对于注册或查找服务来说非常有用。例如,下面的代码就展示了如何查找一个日期查询服务:

slptool findsrvs service:daytime
service:daytime://www.mtjones.com:45667,65535
$

参考资料 部分给出了更多有关 OpenSLP API 及其相关工具的资料的链接。

样例用法

现在,让我们来看几个使用 OpenSLP API 的服务器和客户机应用程序的例子。在这个例子中,我们创建了一个日期查询套接字服务器:当客户机连接到这个服务器上时,就打印一个字符串说明现在的时间。首先我们来讨论应用程序的源代码,然后再来看一下这两个程序执行的情况。

启用 SLP 的日期查询服务器

清单 1 给出了启用 SLP 的日期查询服务器。我将源代码分成 3 部分,这样可以更简单地介绍它们的功能:

  • 第 1 部分是一个简单的套接字设置。
  • 第 2 部分执行服务注册。
  • 第 3 部分实现了日期查询服务。

第 1 部分用来建立日期查询服务器使用的套接字。我创建了一个套接字,使用 setsockopt 为这个套接字启用地址重用功能,将一个地址和端口绑定到这个套接字上,最后通过调用 listen(这会将 TCP 服务器套接字设置成监听状态)来允许到达连接。

第 2 部分使用 OpenSLP API 来执行服务注册功能。首先,我们使用 SLPOpen 创建一个新的 SLP 句柄,然后使用 SLPReg 来注册这个服务。回想一下服务 URL 中包含了服务名(daytime),服务的位置(www.mtjones.com)以及访问服务所使用的 TCP 端口号(45667)。在调用 SLPReg 回调函数(slpRegCallback)之后,SLPReg 函数就结束了,我们使用 SLPClose 来关闭 SLP 句柄。现在我们的服务已经在 DA 中注册了。

服务注册完成之后,我们接下来就要开始看第 3 部分的内容了,它实现了日期查询服务器的功能。我们使用 accept 等待新连接到达;在接收到新客户机连接时,我们获得当前时间,将其转换成一个字符串,并使用 write API 函数将其写入客户机套接字。客户机关闭套接字,而服务器则继续等待新的客户机连接。

清单 1. 启用 SLP 的 daytime 服务器
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MAX_BUFFER      80
void slpRegCallback( SLPHandle hslp, SLPError errcode, void *cookie )
{
  if (errcode == SLP_OK) {
    printf("Service registered\n");
  }
  *(SLPError *)cookie = errcode;
  return;
}
int main()
{
  SLPError err, callback_err;
  SLPHandle hslp;
  int servsock, clisock, on = 1;
  struct sockaddr_in sa;
  time_t t;
  char timeBuffer[MAX_BUFFER+1];
  /* --------------------------------------
   * Section 1 -- Daytime Server Setup
   * -------------------------------------*/
  /* Create a new socket */
  if ((servsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return servsock;
  }
  /* Enable address reuse */
  if ((setsockopt( servsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) {
    perror("setsockopt");
    return -1;
  }
  /* Bind our socket to port 45667 and all interfaces */
  memset( &sa, 0, sizeof(sa) );
  sa.sin_family = AF_INET;
  sa.sin_port = htons(45667);
  sa.sin_addr.s_addr = htonl(INADDR_ANY);
  if (bind(servsock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
    perror("bind");
    return -1;
  }
  /* Place the socket into the listening state (able to accept new
   * connections).
   */
  listen( servsock, 5 );
  /* -------------------------------------------
   * Section 2 -- SLP Service Registration
   * ------------------------------------------*/
  /* Open an SLP API instance */
  err = SLPOpen( "en", SLP_FALSE, &hslp );
  if (err != SLP_OK) {
    printf("SLPOpen failed %d\n", err);
    return err;
  }
  /* Register the service with SLP */
  err = SLPReg( hslp, "service:daytime://www.mtjones.com:45667",
                 SLP_LIFETIME_MAXIMUM, 0, "", SLP_TRUE,
                 slpRegCallback, &callback_err );
  if ((err != SLP_OK) || (callback_err != SLP_OK)) {
    printf("SLPReg failed %d/%d\n", err, callback_err);
    return err;
  }
  /* Registration is complete, close the SLP instance */
  SLPClose( hslp );
  /* -------------------------------------
   * Section 3 -- Daytime Server Loop
   * ------------------------------------*/
  /* Service was successfully registered, start the daytime server.  */
  while (1) {
    /* Await a client connection */
    if ((clisock = accept( servsock, (struct sockaddr *)NULL, NULL)) < 0) {
      perror("accept");
      close(servsock);
      return clisock;
    }
    /* Get the time and send it to the client */
    t = time(NULL);
    snprintf( timeBuffer, MAX_BUFFER, "%s\n", ctime(&t) );
    write( clisock, timeBuffer, strlen(timeBuffer) );
    close( clisock );
  }
  close( servsock );
  return 0;
}

启用 SLP 的日期查询客户机

我已经将启用 SLP 的日期查询客户机划分成了两个函数(请参看清单 2)。第一个函数是 SLP 回调函数(用于 SLPFindSrvs 函数)第二个函数是实现日期查询客户机的 main 函数。

我们不会分别介绍这两个函数,而是按照它们的流程来进行讨论。main 函数被划分成两部分。main 函数的第 1 部分确定了 SLP 服务位置。在从 SLPOpen 中获得新 SLP 句柄之后,我们会调用 SLPFindSrvs 来查找服务 service:daytime(使用范围 default)。对这个 SLP 调用的回调函数是 slpSrvURLCallback。这个函数会对每个返回的服务都进行调用。在回调函数内部,我们对 SLPPL 返回的服务 URL 进行解析,并存储这个服务的 IP 地址和端口号供以后使用。注意这里使用的 slpSrvURLCallback 函数:本文附带的 .ZIP 文件中可找到该函数,考虑到篇幅,此处不再给出详细代码。它惟一的用途就是将字符串域名解析为 32 位 IP 地址。

在所有服务全部返回之后,控制从 SLPFindSrvs 返回给 main,并使用 SLPClose 关闭 SLP 句柄。然后检查服务是否找到(使用上一次发现的服务);如果没有找到服务,就退出。

最后一部分,即第 2 部分,是 daytime socket 的客户机。使用通过 SLP 找到的 IP 地址和端口号,我新创建了一个 socket,并连接到服务器上。在连接之后,从 socket 中读取信息,然后打印响应信息(当前时间)。

就是这样!客户机和服务器的 SLP 部分都非常小,在这个示例中几乎是微不足道的。

清单 2. 启用 SLP 的日期查询客户机
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MAX_BUFFER      80
int found = 0;
struct sockaddr_in sa;
int    port;
SLPBoolean slpSrvURLCallback( SLPHandle hslp, const char *srvurl,
                              unsigned short lifetime, SLPError errcode,
                              void *cookie )
{
  SLPSrvURL *parsedURL;
  SLPError  err;
  /* Check the callback for success */
  if (errcode == SLP_OK) {
    printf("srvurl = %s\n", srvurl);
    /* Parse the service string into its basic elements */
    err = SLPParseSrvURL( srvurl, &parsedURL );
    /* If the parse was successful, grab the necessary elements and
     * cache them for the application. */
    if (err == SLP_OK) {
      printf("Found %s\n", parsedURL->s_pcSrvType);
      printf("at host %s\n", parsedURL->s_pcHost);
      printf("port number %d\n", parsedURL->s_iPort);
      found = 1;
      /* Resolve the fully qualified domain name to an IP address */
      printf("Resolving host to IP address\n");
      if (resolve_name( &sa, parsedURL->s_pcHost ) == 0) {
        printf("Resolved to %s\n", inet_ntoa(sa.sin_addr));
        port = parsedURL->s_iPort;
      }
      SLPFree( (void *)parsedURL );
    }
    *(SLPError *)cookie = SLP_OK;
  } else if (errcode == SLP_LAST_CALL) {
    /* no action */
    printf("Final call -- slp find done.\n");
  } else {
    *(SLPError *)cookie = errcode;
  }
  return SLP_TRUE;
}
int main()
{
  SLPError err, callback_err;
  SLPHandle hslp;
  char timeBuffer[MAX_BUFFER+1];
  int sock, in;
  /* -------------------------------------
   * Section 1 -- SLP Service Request
   * ------------------------------------*/
  /* Open a new SLP API instance */
  err = SLPOpen( "en", SLP_FALSE, &hslp );
  if (err != SLP_OK) {
    printf("SLPOpen failed %d\n", err);
    return err;
  }
  /* Try to find the desired service */
  err = SLPFindSrvs( hslp, "service:daytime", "default", 0,
                      slpSrvURLCallback, &callback_err );
  if ((err != SLP_OK) || (callback_err != SLP_OK)) {
    printf("SLPFind failed %d/%d\n", err, callback_err);
    return err;
  }
  /* Close this SLP API instance */
  SLPClose( hslp );
  /* If the service wasn't found, exit now */
  if (!found) {
    close(sock);
    printf("Service not found.\n");
    return -1;
  }
  /* --------------------------------------
   * Section 2 -- Daytime Client Setup
   * -------------------------------------*/
  /* Create a new socket */
  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return sock;
  }
  /* Bind the socket to the discovered service (address and port) */
  memset( &sa, 0, sizeof(sa) );
  sa.sin_family = AF_INET;
  sa.sin_port = htons(port);
  printf("Connecting to service\n");
  if (connect( sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
    close(sock);
    perror("connect");
    return -1;
  }
  /* Read the time from the daytime server */
  in = read(sock, timeBuffer, MAX_BUFFER);
  timeBuffer[in] = 0;
  printf("Client received: %s\n", timeBuffer);
  close(sock);
  return 0;
}

展示服务器和客户机的用法

清单 3 给出了服务器和客户机运行时的情况。我们使用 make 工具来编译 SLP 服务器和客户机。接下来首先启动 slpd 工具(SLP DA),然后启动服务器(slpreg)。现在服务器正在运行,大概已经注册了,我们使用 slptool 作为一个可选步骤来查看 DA 现在是否知道这个服务的存在。从 slptool 的返回值中,我们知道注册已经成功了,因此就尝试启动客户机 slpfind。这个应用程序用来查找服务,然后在处理所生成的服务 URL 之后,连接到服务器上,并返回当前时间。

您可以从下面的 下载 部分中下载 slpregslpfind 和 makefile。

清单 3. 展示 SLP 服务器和客户机的用法
# make
gcc -Wall -o slpreg slpreg.c -lslp
gcc -Wall -o slpfind slpfind.c -lslp
# slpd
# ./slpreg &
[1] 9275
Service registered
# slptool findsrvs service:daytime
service:daytime://www.mtjones.com:45667,65535
# ./slpfind
srvurl = service:daytime://www.mtjones.com:45667
Found service:daytime
at host www.mtjones.com
port number 45667
Resolving host to IP address
Resolved to 66.54.202.174
Final call -- slp find done.
Connecting to service
Client received: Sat Apr 16 14:04:26 2005
#

SLP 的将来

SLP 在很多公司的很多产品中都已经采用了。部分公司和产品如表 4 所示。

表 4. 集成 SLP 的产品
公司 产品
Axis Communication 网络打印机和照相机
GroupLogic ExtremeZ-IP 文件 和打印机共享产品
IBM Communications Server;TotalStorage SAN Volume Controller;TotalStorage Multiple Device Manager (MDM);TN3270 Terminal
Open Door Networks Shareway IP 文件共享产品
Symantec Norton Personal Firewall for Macintosh
WBEM Solutions J WBEM Server

很多现代操作系统都包括了对 SLP 的支持,包括 GNU/Linux®、IBM AIX、HP-UX、NetBSD、Sun Solaris 8、Mac OS X 和 Novell SuSE Enterprise Server 9。现在您已经可以不仅仅是用 C 来为 SLP 编写应用程序了。SLP 已经绑定了几种语言的支持,包括 C/C++、Java™ 和 Python。

最后,SLP 也可以在众多推荐使用它的 RFC 中找到(例如 iSCSI 和 FCIP)。不论它集成到多少操作系统、产品中,可以支持多少种语言,都不代表 SLP 可以得到了更为广泛的接受。可以用来进行服务发现的协议的数量很大,但是现在尚没有哪一个已经达到事实上的标准的状态。

下载

描述 名字 大小
Demonstration SLP client and server l-slp-demo.zip 3KB

参考资料



from:https://www.ibm.com/developerworks/cn/linux/l-slp/

你可能感兴趣的:(开源工具库)