37、深入理解TLI网络编程

深入理解TLI网络编程

1. 引言

在网络编程领域,套接字(socket)接口简单且流行,但存在设计缺陷,即它并非协议无关的。虽然套接字可用于多种协议,如UNIX IPC、TCP/IP、ISO/OSI和XNS等,但为某一协议编写的套接字程序若要使用其他协议,就必须修改源代码。而传输层接口(Transport Layer Interface,TLI)则试图解决这一问题。

2. TLI概述

TLI是一个函数库,允许两个程序通过传输提供者进行通信。传输提供者可以是设备驱动程序或其他提供通信支持的操作系统接口。例如,TCP/IP协议支持是一种传输提供者,Novell IPX协议支持则是另一种。TLI设计的关键在于,如果程序员小心避免采取任何依赖协议的操作,那么为TLI编写的单个程序可以在不更改源代码的情况下,在任意数量的不同传输提供者上运行。实际上,当添加新的传输提供者时,程序甚至无需重新编译。

TLI库最早在System V Release 3中引入,但AT&T在开发该接口时,没有在SVR3中包含传输提供者,这意味着如果不购买第三方产品,TLI就没有可通信的对象。因此,在包含TCP/IP传输提供者的SVR4发布之前,套接字一直是编写网络程序的唯一可行接口,TLI几乎被弃用。不过,对于支持或维护System V系统的人来说,TLI仍然值得学习。

在SVR3和SVR4之间,TLI库进行了许多改进,其中大部分更改涉及添加一种独立于网络的方法来处理主机和服务地址。这些更改被Sun和Silicon Graphics采用,并包含在Solaris 2.x和IRIX 5.x中。而Hewlett - Packard出于与早期版本向后兼容的原因,没有采用这些新功能。HP - UX 10.x中的TLI库更类似于最初随SVR3提供的TLI库。

所有使用TLI的程序在Solaris 2.x和IRIX 5.x上必须与 -lnsl 库链接,在HP - UX 10.x上必须与 -lnsl_s 库链接。

3. netbuf结构

由于TLI是协议无关的,因此各种TLI函数使用的数据结构相同,无论使用的网络协议是什么。然而,在传输提供者接口处,没有数据格式的标准,不同的传输提供者使用不同的格式。例如,对于主机地址的表示没有标准,TCP/IP使用32位值,而ISO/OSI使用160位值。

TLI函数需要处理这些不同的数据格式,但必须以不影响函数的方式进行。在套接字接口中,这是通过使用通用的 struct sockaddr 数据类型,并将依赖协议的数据结构(如 struct sockaddr_un struct sockaddr_in 等)强制转换为该通用类型来处理的。在TLI中,这是通过 struct netbuf 结构来处理的,该结构在头文件 tiuser.h 中定义:

struct netbuf {
    unsigned int     maxlen;
    unsigned int     len;
    char            *buf;
}

该结构的 buf 元素包含数据(如网络地址等), len 元素表示 buf 的字节长度。当TLI函数填充用户提供的 buf 时, maxlen 元素表示缓冲区的大小,以防止函数溢出。

struct netbuf 结构在SVR4 TLI库中被广泛使用,但在HP - UX 10.x中不可用。

4. 网络选择

TLI的优势在于它能够在不同的传输提供者(网络协议)上无需更改即可工作。例如,一个需要虚拟电路连接的程序并不关心该连接是通过TCP/IP还是ISO/OSI建立的,只要能完成任务即可。当程序员使用套接字编写程序时,他必须决定要使用的协议,并相应地编写程序。而当程序员使用TLI编写程序时,她只需决定所需的服务类型(如虚拟电路、数据报等),程序就可以在任何提供该类型服务的传输提供者的系统上运行。

TLI中的网络选择功能由 /etc/netconfig 文件驱动,该文件的示例如下:
| NetID | Semantics | Flags | Proto Family | Proto | Network Device | Directory Lookup |
| — | — | — | — | — | — | — |
| udp | tpi_clts | v | inet | udp | /dev/udp | switch.so,tcpip.so |
| tcp | tpi_cots_ord | v | inet | tcp | /dev/tcp | switch.so,tcpip.so |
| rawip | tpi_raw | - | inet | - | /dev/rawip | switch.so,tcpip.so |
| ticlts | tpi_clts | v | loopback | - | /dev/ticlts | straddr.so |
| ticotsord | tpi_cots_ord | v | loopback | - | /dev/ticotsord | straddr.so |
| ticots | tpi_cots | v | loopback | - | /dev/ticots | straddr.so |

该文件为系统上安装的每个网络协议包含一个条目,每个条目有七个字段:
- NetID :网络的唯一名称。
- Semantics :描述网络提供的服务类型,目前有四个合法值:
- tpi_clts :无连接传输服务(数据报)。
- tpi_cots :面向连接的传输服务(虚拟电路)。
- tpi_cots_ord :具有有序释放的面向连接的传输服务。
- tpi_raw :网络协议的“原始”(低级)接口。
- Flags :目前仅定义了 v 标志,表示该条目对NETPATH例程可见,使用短横线可使网络对这些例程暂时(或永久)不可见。
- Proto Family :协议族的名称,例如所有Internet协议都归为“inet”。
- Proto :协议本身的名称,如果协议没有名称,可以使用短横线。
- Network Device :访问网络和协议时使用的设备的路径名。
- Directory Lookup :包含网络协议名称到地址转换函数的共享库的逗号分隔列表。

有两组函数用于读取 /etc/netconfig 文件,它们都使用 struct netconfig 结构来描述一个条目:

#include <netconfig.h>

struct netconfig {
    char              *nc_netid;
    unsigned long      nc_semantics;
    unsigned long      nc_flag;
    char              *nc_protofmly;
    char              *nc_proto;
    char              *nc_device;
    unsigned long      nc_nlookups;
    char             **nc_lookups;
};

该结构的 nc_netid nc_protofmly nc_proto nc_device 元素分别包含网络标识符、协议族、协议名称和网络设备名称。 nc_lookups 元素包含名称到地址转换库的名称, nc_nlookups 表示这些库的数量。 nc_semantics 字段包含 NC_TPI_CLTS NC_TPI_COTS NC_TPI_COTS_ORD NC_TPI_RAW 之一, nc_flag 元素将包含 NC_NOFLAG NC_VISIBLE

下面是网络选择相关函数的介绍:
- 网络配置库
- 读取 /etc/netconfig 文件最简单的方法是一次读取一个条目,或者通过网络标识符查找特定条目。相关函数包含在网络配置库中:

#include <netconfig.h>

void *setnetconfig(void);
int endnetconfig(void *handlep);
struct netconfig *getnetconfig(void *handlep);
struct netconfig *getnetconfigent(const char *netid);
void freenetconfigent(struct netconfig *netconfigp);
void nc_perror(const char *msg);
char *nc_sperror(void);
  • setnetconfig 函数打开或倒回 /etc/netconfig 文件,返回一个指向“句柄”的指针,该指针必须与其他一些函数一起使用。在调用 getnetconfig 之前必须调用 setnetconfig ,但在调用 getnetconfigent 之前不必调用。 endnetconfig 函数关闭网络配置数据库, handlep 应该是调用 setnetconfig 返回的值。
  • getnetconfig 函数接受一个参数 handlep ,它返回网络配置数据库中的下一个条目,当没有更多条目可读时返回 NULL getnetconfigent 函数返回网络标识符等于 netid 的条目,如果未找到则返回 NULL
  • getnetconfig getnetconfigent 返回的内存是动态分配的,可以调用 freenetconfigent 函数来释放该内存。注意,调用 endnetconfig 也会释放这些函数分配的内存,在程序使用完这些信息之前不应调用。
  • nc_perror 函数可在库中的其他函数返回错误时调用,它会在标准错误输出上打印 msg 字符串,后跟描述发生的错误的消息。 nc_sperror 函数将返回错误消息字符串而不打印它。

为了使TLI程序具有可移植性,可以反复调用 getnetconfig 来查找具有所需语义的任何网络。例如,一个数据报应用程序可能会这样调用:

void *handlep;
struct netconfig *ncp;

handlep = setnetconfig();
while ((ncp = getnetconfig(handlep)) != NULL) {
    if (ncp->nc_semantics == NC_TPI_CLTS)
        break;
}

if (ncp == NULL) {
    fprintf(stderr, "cannot find acceptable transport provider.\n");
    exit(1);
}

/* use the network described by ncp */

而使用 getnetconfigent 的程序在定义上不能跨不同的传输提供者移植,因为它请求的是特定的传输提供者。

下面是网络选择的mermaid流程图:

graph TD;
    A[开始] --> B[调用setnetconfig];
    B --> C{是否有更多条目};
    C -- 是 --> D[调用getnetconfig];
    D --> E{是否满足语义要求};
    E -- 是 --> F[使用该网络];
    E -- 否 --> C;
    C -- 否 --> G[输出错误信息并退出];
  • NETPATH库
  • NETPATH库提供了另一种读取 /etc/netconfig 文件的方法,该方法允许用户对选择的网络表达一些控制(偏好)。用户可以将 NETPATH 环境变量设置为他愿意使用的网络标识符的冒号分隔列表,并按他偏好的顺序排列。例如,如果用户更喜欢TCP而不是ISO TP4,但更喜欢ISO TP0而不是UDP,她可以将 NETPATH 环境变量设置为:
NETPATH=tcp:iso_tp4:iso_tp0:udp
  • NETPATH库中有三个函数:
#include <netconfig.h>

void *setnetpath(void);
int endnetpath(void *handlep);
struct netconfig *getnetpath(void *handlep);
  • setnetpath 函数打开或倒回 /etc/netconfig 文件,并返回一个指向描述该文件的“句柄”的指针。在调用 getnetpath 之前必须调用它。 endnetpath 函数关闭文件并释放例程返回的所有分配资源。
  • getnetpath 函数读取由 handlep 描述的网络配置文件,它不按顺序读取文件,而是返回 NETPATH 环境变量中包含的下一个有效网络标识符的条目。因此,无论网络在文件中列出的顺序如何, getnetpath 总是按环境变量指定的顺序返回它们。 getnetpath 会默默地忽略 NETPATH 中包含的无效或不存在的网络标识符,当 NETPATH 条目用完时返回 NULL
  • 如果未设置 NETPATH 变量,则 getnetpath 返回“默认”网络列表,即网络配置文件中列为“可见”的网络,这些网络将按列出的顺序返回。
  • getnetpath 函数的使用与 getnetconfig 基本相同,程序反复调用 getnetpath ,直到找到具有所需语义的网络。通过对 NETPATH 环境变量中的值进行排序,用户可以在存在多个具有相同语义的网络时对选择哪个网络施加一些控制。
4. HP - UX 10.x中的网络选择

HP - UX 10.x中的网络传输选择是在编译时进行的,而不是在运行时进行的。没有函数库可以让程序员根据服务类型要求选择网络,程序员必须确切知道她想要什么,并将网络设备的名称直接编码到她的程序中。因此,一个编写为使用TCP作为其面向连接的传输服务的程序如果要使用ISO TP4,则必须进行修改。

从技术角度来看,SVR4提供的解决方案更好,它更具可移植性,可以在具有不同网络服务的系统之间无修改地移动。但从实际角度来看,这可能并不重要。几乎所有连接到网络的系统都连接到TCP/IP网络,因此程序“默认”具有可移植性。对于那些使用其他网络传输的程序,它们可能本来就不打算在其本地环境之外进行移植。

深入理解TLI网络编程

5. 名称到地址的转换

在网络编程中,人们通常使用主机名来指代主机,但网络协议更倾向于使用地址。因此,和套接字接口一样,TLI也必须提供一种在主机和地址、端口名称和端口号之间进行转换的方法。相关函数定义在 netdir.h 头文件中:

#include <netdir.h>

int netdir_getbyname(const struct netconfig *config,
        const struct nd_hostserv *service,
        struct nd_addrlist **addrs);

int netdir_getbyaddr(const struct netconfig *config,
        struct nd_hostservlist **service,
        const struct netbuf *netaddr);

int netdir_options(const struct netconfig *netconfig,
        const int opt, const int fd, char *argp);

void netdir_free(void *ptr, const int struct_type);

void netdir_perror(char *s);

char *netdir_sperror(void);

与套接字接口独立处理主机地址和服务(端口号)不同,TLI将它们视为一个整体。因此,一个地址是一个由(主机地址,端口号)组成的元组。

netdir_getbyname 函数用于查找 service 参数中给定的主机名和服务名。 service 是一个指向 struct nd_hostserv 类型的指针,该结构体定义如下:

struct nd_hostserv {
    char    *h_host;
    char    *h_serv;
};
  • h_host 字段包含主机名。
  • h_serv 字段包含服务名。对于没有名称的服务(例如,任意选择的端口号), h_serv 应指向端口号的字符串表示。 h_host 元素可以包含一些特殊值,而不是主机名,具体如下:
  • HOST_SELF :代表本地程序可以用来引用本地主机的地址,该地址在本地主机之外没有意义。
  • HOST_ANY :代表此传输提供者可访问的任何主机,相当于套接字接口中的 INADDR_ANY 值。
  • HOST_SELF_CONNECT :代表可用于连接到本地主机的主机地址。
  • HOST_BROADCAST :代表此传输提供者可到达的所有主机的地址,向该地址发送的网络请求将被发送到网络上的所有机器。

netdir_getbyname 函数会在 addrs 参数中返回主机和服务的所有有效地址列表。 addrs 是一个指向 struct nd_addrlist 类型数组的指针,该结构体定义如下:

struct nd_addrlist {
    int               n_cnt;
    struct netbuf    *n_addrs;
};

n_addrs 的每个元素包含一个地址, n_cnt 元素指示有多少个地址。

netdir_getbyaddr 函数根据 netaddr 中给定的主机地址和端口号进行查找,并在 service 中返回主机和服务名的列表。 service 是一个指向 struct nd_hostservlist 类型数组的指针,该结构体定义如下:

struct nd_hostservlist {
    int                    h_cnt;
    struct nd_hostserv    *h_hostservs;
};

netdir_getbyname netdir_getbyaddr 函数在成功时返回零,失败时返回非零值。如果它们失败,可以使用 netdir_perror netdir_sperror 函数来了解失败原因。

这些函数使用的内存可以通过调用 netdir_free 函数释放。第一个参数是指向内存的指针,第二个参数是一个常量,指示要释放的结构体类型,具体如下表所示:
| 常量 | 释放的结构体类型 |
| — | — |
| ND_ADDR | struct netbuf 结构体 |
| ND_ADDRLIST | struct nd_addrlist 结构体 |
| ND_HOSTSERV | struct hostserv 结构体 |
| ND_HOSTSERVLIST | struct nd_hostservlist 结构体 |

netdir_options 函数允许程序员在他选择的地址上设置或检查各种选项。 fd 参数是传输端点(稍后定义), opt 参数指定选项,可能是以下值之一:
- ND_SET_BROADCAST :如果传输提供者支持广播,则将程序设置为发送广播数据包, argp 参数将被忽略。
- ND_SET_RESERVEDPORT :如果传输提供者存在保留端口的概念,则允许调用者绑定一个保留端口。如果 argp NULL ,将选择一个任意保留端口;如果 argp 指向一个 struct netbuf 结构体,将尝试绑定到它描述的保留端口。

下面是名称到地址转换的mermaid流程图:

graph TD;
    A[开始] --> B{选择转换方式};
    B -- 按名称转换 --> C[调用netdir_getbyname];
    C --> D{是否成功};
    D -- 是 --> E[获取地址列表];
    D -- 否 --> F[使用netdir_perror或netdir_sperror获取错误信息];
    B -- 按地址转换 --> G[调用netdir_getbyaddr];
    G --> H{是否成功};
    H -- 是 --> I[获取主机和服务名列表];
    H -- 否 --> F;
    E --> J[使用完地址列表后调用netdir_free释放内存];
    I --> J;
6. 总结

TLI作为一种网络编程接口,旨在解决套接字接口协议相关性的问题,提供了协议无关的编程能力。通过使用 struct netbuf 结构处理不同协议的数据格式,利用 /etc/netconfig 文件和相关函数进行网络选择,以及提供名称到地址的转换功能,TLI为开发者提供了一种灵活且可移植的网络编程解决方案。

虽然TLI在实际应用中使用较少,但对于支持或维护System V系统的开发者来说,它仍然是一项重要的技术。此外,TLI的设计思想和实现方式也为我们理解网络编程中的协议无关性和可移植性提供了有益的参考。

在不同的操作系统中,TLI的实现可能会有所不同,如HP - UX 10.x在网络选择上采用编译时选择的方式,而SVR4则提供了更灵活的运行时选择机制。开发者在使用TLI时,需要根据具体的操作系统和需求选择合适的方法和函数。

通过深入学习TLI,开发者可以更好地掌握网络编程的核心概念和技术,提高程序的可移植性和灵活性,为开发高质量的网络应用程序打下坚实的基础。

考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化与经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本与能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参与调度等方面的有效性,为低碳能源系统的设计与运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模与优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建与求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发与仿真验证。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值