38、深入理解 TLI 网络编程

深入理解 TLI 网络编程

1. 名称到地址转换

在网络编程中,名称到地址的转换是一个基础且重要的环节。有两个相关的函数值得关注:
- ND_CHECK_RESERVEDPORT :用于检查 argp 所指向的 struct netbuf 结构中的地址是否在保留端口上。
- ND_MERGEADDR :将“本地”地址转换为其他客户端可以使用的“真实”地址。 argp 参数应指向 struct nd_mergearg 类型的结构,其定义如下:

struct nd_mergearg {
    char    *s_uaddr;
    char    *c_uaddr;
    char    *m_uaddr;
};

其中, s_uaddr 指向服务器(本地机器)的地址, c_uaddr 指向客户端(远程机器)的地址。调用完成后, m_uaddr 将包含客户端可用于联系服务器的地址。不过,该选项的实际用途并不明确,因为这些信息可以通过其他方式获取。 netdir_options 函数成功时返回零,失败时返回非零值。

在 HP - UX 10.x 中,名称到地址的转换与其他系统有所不同。最初引入 TLI 的 SVR3 未提供网络传输,采用 SVR3 作为基础操作系统的供应商需将现有传输层“嫁接”到 TLI 上。大多数供应商采用类似方式,利用套接字接口提供的现有数据结构和库例程,仅做少量修改以支持套接字和 TLI 的差异。这种实现方式的可移植性较差,处理 32 位 TCP/IP 地址和 160 位 ISO 地址所需的数据结构不同,要使为一个传输提供者编写的程序在另一个传输提供者上运行,需要进行重大修改。但实际上,几乎所有联网系统都连接到 TCP/IP 网络,所以程序默认具有一定的可移植性。对于使用其他网络传输的程序,它们可能并不打算在本地环境之外具有可移植性。

2. TLI 实用函数

TLI 库中有三个常用的实用函数:

#include <tiuser.h>

void t_error(const char *errmsg);
char *t_alloc(int fd, int struct_type, int fields);
int t_free(char *ptr, int struct_type);
  • t_error :当 TLI 函数失败时,用于打印错误消息。TLI 函数会将外部整数 t_errno 设置为错误代码, t_error 会将 errmsg 中的字符串与描述错误的消息一起打印到标准错误输出。如果失败是由系统错误(而非库错误)引起的, t_error 还会打印系统错误消息。
  • t_alloc :用于为 TLI 库的其他部分分配结构。 fd 参数是传输端点, struct_type 参数指定要分配的结构类型,可选值如下:
    • T_BIND :分配 struct t_bind 结构。
    • T_CALL :分配 struct t_call 结构。
    • T_DIS :分配 struct t_discon 结构。
    • T_INFO :分配 struct t_info 结构。
    • T_OPTMGMT :分配 struct t_optmgmt 结构。
    • T_UDERROR :分配 struct t_uderror 结构。
    • T_UNITDATA :分配 struct t_unitdata 结构。

struct t_info 结构外,所有这些结构都包含一个或多个 struct netbuf 结构。 fields 参数用于指定是否分配这些缓冲区,可选值为:
- T_ADDR :分配 t_bind tcall t_unitdata t_uderr 结构的 addr 字段。
- T_OPT :分配 t_optmgmt t_call t_unitdata t_uderr 结构的 opt 字段。
- T_UDATA :分配 t_call t_discon t_unitdata 结构的 udata 字段。
- T_ALL :分配给定结构的所有相关字段。

t_alloc 会分配 struct netbuf 结构的 buf 部分,并适当设置 maxlen 字段,使应用程序无需了解特定用途所需的缓冲区大小。如果无法分配结构, t_alloc 返回 NULL ,否则返回指向分配结构的指针。
- t_free :释放 ptr 指向的结构,该结构应通过 t_alloc 分配。 struct_type 参数指定结构类型,与 t_alloc 中的描述相同。如果结构的某个字段为 NULL t_alloc 不会尝试释放它,这样可以释放部分分配的结构。

3. 传输端点管理

在套接字接口中,套接字用于表示通信通道的一端,它只是一个文件描述符,可以与 read write 以及专用网络函数一起使用。而在 TLI 中,通信通道的一端称为传输端点,它是一个文件描述符和一些相关的状态信息。在未进行特殊准备的情况下,传输端点不能与 read write 一起使用,必须通过 TLI 函数访问。

3.1 创建传输端点

使用 t_open 函数创建传输端点:

#include <tiuser.h>
#include <fcntl.h>

int t_open(const char *path, int oflag, struct t_info *info);

path 参数应为通信设备的路径,通常是 struct netconfig 结构的 nc_device 字段。 oflag 参数指定端点的打开方式,使用与 open 系统调用相同的标志,至少应包含 O_RDWR info 参数(如果非空)指向 struct t_info 类型的结构,用于存储底层传输协议的特性。成功时, t_open 返回有效的文件描述符;失败时,返回 -1,并将失败原因存储在 t_errno (可能还有 errno )中。

可以在创建传输端点时获取底层协议的特性信息,也可以在其他时间使用 t_getinfo 函数获取:

#include <tiuser.h>

int t_getinfo(int fd, struct t_info *info);

fd 参数应引用一个传输端点, info 应指向 struct t_info 类型的结构:

struct t_info {
    long    addr;
    long    options;
    long    tsdu;
    long    etsdu;
    long    connect;
    long    discon;
    long    servtype;
};

各字段含义如下:
| 字段 | 含义 |
| — | — |
| addr | 传输协议地址的最大大小;-1 表示无最大限制,-2 表示用户无法访问传输协议地址。 |
| options | 提供者支持的特定于协议的选项的最大字节数;-1 表示无最大限制,-2 表示传输提供者不支持用户可设置的选项。 |
| tsdu | 传输服务数据单元(TSDU)的最大大小。表示从一个传输端点到另一个传输端点消息边界得以保留的最大数据量。0 表示传输提供者不支持 TSDU 概念,但支持无逻辑边界的流数据传输;-1 表示无限制;-2 表示传输提供者不支持正常数据传输。 |
| etsdu | 加急传输服务数据单元(ETSDU)的最大大小,含义与 TSDU 相同。加急数据会立即传递,无需等待之前发送的正常数据。 |
| connect | 连接请求时可发送的最大数据量;-1 表示无限制,-2 表示连接建立函数不能发送数据。 |
| discon | 与 t_snddis t_rcvdis 函数关联的最大数据量;-1 表示无限制,-2 表示这些函数不能发送数据。 |
| servtype | 传输提供者支持的服务类型:
- T_COTS :面向连接的服务,但无有序释放。
- T_COTS_ORD :面向连接的服务,有有序释放。
- T_CLTS :无连接服务,对于此类服务, etdsu connect discon 将为 -2。 |

t_getinfo 成功时返回 0,失败时返回 -1,并设置 t_errno (可能还有 errno )以指示错误。

3.2 绑定地址到传输端点

在使用传输端点之前,必须将其绑定到一个地址。与套接字接口不同,TLI 要求客户端和服务器进程都将地址绑定到其传输端点。地址由 struct t_bind 类型的结构描述:

struct t_bind {
    struct netbuf    addr;
    unsigned int     qlen;
};

addr 字段包含要绑定的地址, qlen 字段指定服务器在端点上允许的最大未决连接请求数。使用 t_bind 函数将地址绑定到传输端点:

#include <tiuser.h>

int t_bind(int fd, struct t_bind *reqp, struct t_bind *retp);

fd 参数是传输端点, reqp 参数指定请求的地址, retp 参数(如果非空)指向存储实际绑定地址的位置。需要注意的是, t_bind 绑定的实际地址可能与请求的地址不同,当地址已被使用时会出现这种情况。对于通常需要固定地址的服务器来说,这种行为的好处并不明确,或许像套接字接口那样拒绝绑定地址并返回“地址已使用”错误更合理。无论如何,执行 t_bind 后,关心绑定地址的进程应检查 retp 中的地址是否与 reqp 中的地址相同。如果 reqp NULL ,系统将选择一个合适的地址,这通常是客户端程序(使用保留端口的程序除外)的情况。 t_bind 成功时返回 0,失败时返回 -1,并设置 t_errno (可能还有 errno )以指示错误。

3.3 关闭传输端点
  • t_unbind :禁用传输端点:
#include <tiuser.h>

int t_unbind(int fd);

返回后,端点不能再用于传输数据,但此时可以将其绑定到另一个地址。 t_unbind 成功时返回 0,失败时返回 -1。如果失败,错误指示将存储在 t_errno (可能还有 errno )中。
- t_close :关闭传输端点:

#include <tiuser.h>

int t_close(int fd);

该函数应在端点处于未绑定状态(调用 t_unbind 后)时调用,但也可以在端点处于任何状态时调用。它会释放端点使用的本地库资源并关闭文件描述符。 t_close 成功时返回 0,失败时返回 -1,并将失败原因存储在 t_errno (可能还有 errno )中。

3.4 传输端点选项

一些传输提供者允许用户控制某些协议选项,TLI 提供 t_optmgmt 函数来检查和更改这些选项:

#include <tiuser.h>

int t_optmgmt(int fd, const struct t_optmgmt *req, struct t_optmgmt *ret);

fd 参数是已绑定的传输端点, req ret 参数指向 struct t_optmgmt 类型的结构:

struct t_optmgmt {
    struct netbuf    opt;
    long             flags;
};

opt 字段包含选项(在 req 中, len 包含选项的字节数, buf 包含选项;在 ret 中, maxlen 包含 buf 的最大大小)。 flags 字段指定对选项采取的操作:
- T_NEGOTIATE :与传输提供者协商 req 中指定的选项值,提供者将检查选项并协商值,通过 ret 返回协商后的值。
- T_CHECK :检查传输提供者是否支持 req 中指定的选项,返回时, ret flags 字段将包含 T_SUCCESS T_FAILURE
- T_DEFAULT :将传输提供者支持的默认选项检索到 ret 中,调用时, req 中的 len 字段必须为零。

选项的实际结构和内容由传输提供者规定。 t_optmgmt 成功时返回零,失败时返回 -1,并将错误代码存储在 t_errno (可能还有 errno )中。

下面是创建和管理传输端点的流程图:

graph TD;
    A[开始] --> B[创建传输端点 t_open];
    B --> C{是否成功};
    C -- 是 --> D[获取协议特性 t_getinfo];
    C -- 否 --> E[处理错误];
    D --> F[绑定地址 t_bind];
    F --> G{是否成功};
    G -- 是 --> H[使用传输端点];
    G -- 否 --> E;
    H --> I[禁用端点 t_unbind];
    I --> J{是否成功};
    J -- 是 --> K[关闭端点 t_close];
    J -- 否 --> E;
    K --> L[结束];
    E --> M[程序终止];
4. 无连接服务

无连接(数据报)服务是使用 TLI 进行通信的两种类型中最简单的一种。客户端和服务器创建传输端点并绑定地址后,可以使用 t_sndudata t_rcvudata 函数交换数据:

#include <tiuser.h>

int t_sndudata(int fd, struct t_unitdata *data);
int t_rcvudata(int fd, struct t_unitdata *data, int *flags);

两个函数中, fd 是传输端点, data 指向 struct t_unitdata 类型的结构:

struct t_unitdata {
    struct netbuf    addr;
    struct netbuf    opt;
    struct netbuf    udata;
};

其中, addr 是数据要发送到的地址或接收数据的地址, opt 包含与数据关联的特定于协议的选项, udata 包含传输的数据。调用 t_rcvudata 之前,必须设置这三个结构的 maxlen 字段。 t_rcvudata flags 参数应指向可设置标志的区域,该区域应初始化为零。目前定义的唯一标志是 T_MORE ,如果 udata 缓冲区大小不足以检索所有可用数据,该标志将被设置,后续调用 t_rcvudata 可用于检索剩余数据。 t_sndudata t_rcvudata 成功时返回零,失败时返回 -1。如果失败,错误代码将存储在 t_errno (可能还有 errno )中。

接收数据时,可能会出现错误,导致在处理错误之前无法接收更多数据。在无连接模式下,这种错误唯一可能的情况是之前使用 t_sndudata 发送数据失败。如果 t_rcvudata 失败并将 t_errno 设置为 TLOOK ,应用程序必须调用 t_rcvuderr 清除错误:

#include <tiuser.h>

int t_rcvuderr(int fd, struct t_uderr *uderr);

struct t_uderr 结构定义如下:

struct t_uderr {
    struct netbuf    addr;
    struct netbuf    opt;
    long             error;
};

调用前必须设置 addr opt maxlen 字段。返回时, addr 将包含失败传输的地址, opt 将包含与传输关联的任何选项, error 将包含与实现相关的错误代码。有人质疑,在使用本质上不可靠的服务(数据报可能丢失或丢弃)时,TLI 的设计者为何决定告知用户这种特定的错误情况(而不是其他情况),因为几乎无法采取有效措施(由于未提供哪个数据报失败的指示,无法进行重传),这只会使无连接服务的实现更加复杂。

以下是一个使用 SVR4 TLI 实现连接“daytime”服务的示例代码:

#include <netconfig.h>
#include <netdir.h>
#include <tiuser.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>

#define SERVICENAME "daytime"

extern int t_errno;

int
main(int argc, char **argv)
{
    int fd, flags;
    struct netconfig *ncp;
    struct nd_hostserv ndh;
    struct t_unitdata *udp;
    struct nd_addrlist *nal;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s hostname [hostname...]\n", *argv);
        exit(1);
    }

    /*
     * Select the UDP transport provider.
     */
    if ((ncp = getnetconfigent("udp")) == NULL) {
        nc_perror("udp");
        exit(1);
    }

    while (--argc) {
        ndh.h_host = *++argv;
        ndh.h_serv = SERVICENAME;

        /*
         * Get a host and service address for this host.
         */
        if (netdir_getbyname(ncp, &ndh, &nal) != 0) {
            netdir_perror(*argv);
            exit(1);
        }

        /*
         * Create a transport endpoint.
         */
        if ((fd = t_open(ncp->nc_device, O_RDWR, NULL)) < 0) {
            t_error("t_open");
            exit(1);
        }

        /*
         * Bind an arbitrary address to the transport
         * endpoint.
         */
        if (t_bind(fd, NULL, NULL) < 0) {
            t_error("t_bind");
            exit(1);
        }

        /*
         * Allocate a datagram.
         */
        udp = (struct t_unitdata *) t_alloc(fd, T_UNITDATA, T_ALL);

        if (udp == NULL) {
            t_error("t_alloc");
            exit(1);
        }

        /*
         * Construct the datagram.
         */
        memcpy(&udp->addr, &nal->n_addrs[0], sizeof(struct netbuf));
        udp->udata.len = 1;

        /*
         * Send a packet to the server.
         */
        if (t_sndudata(fd, udp) < 0) {
            t_error("t_sndudata");
            exit(1);
        }

        /*
         * Receive a packet back.
         */
        if (t_rcvudata(fd, udp, &flags) < 0) {
            if (t_errno == TLOOK) {
                if (t_rcvuderr(fd, NULL) < 0) {
                    t_error("t_rcvuderr");
                    exit(1);
                }
            }
            else {
                t_error("t_rcvudata");
                exit(1);
            }
        }

        /*
         * Print the packet.
         */
        udp->udata.buf[udp->udata.len] = '\0';
        printf("%s: %s", *argv, udp->udata.buf);

        /*
         * Shut down the connection.
         */
        t_unbind(fd);
        t_close(fd);
    }

    exit(0);
}

在 HP - UX 10.x 中实现相同功能的程序与上述示例有一些主要差异:
1. 不使用 netdir_getbyname 获取主机/服务地址,而是使用 getservbyname 获取服务地址(端口号),使用 gethostbyname 获取主机地址。
2. 不使用 getnetconfigent 获取适合 t_open 使用的网络设备名称,而是直接编译设备名称,如 /dev/inet_clts 提供使用 Internet 协议套件(TCP/IP)的无连接传输服务。
3. 不使用与传输无关的 struct nd_addrlist 结构处理网络地址,而是使用特定于 Internet 协议域的 struct sockaddr_in 结构,创建主机和服务地址的方式与使用套接字接口时类似。

以下是 HP - UX 10.x 中的示例代码:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <tiuser.h>
#include <string.h>
#include <netdb.h>
#include <fcntl.h>
#include <stdio.h>

#define SERVICENAME "daytime"

extern int t_errno;

int
main(int argc, char **argv)
{
    int fd, flags;
    struct hostent *hp;
    struct servent *sp;
    struct t_unitdata *udp;
    struct nd_addrlist *nal;
    struct sockaddr_in rem_addr;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s hostname [hostname...]\n", *argv);
        exit(1);
    }

    if ((sp = getservbyname(SERVICENAME, "udp")) == NULL) {
        fprintf(stderr, "%s/udp: unknown service\n", SERVICENAME);
        exit(1);
    }

    while (--argc) {
        if ((hp = gethostbyname(*++argv)) == NULL) {
            fprintf(stderr, "%s: unknown host\n", *argv);
            continue;
        }

        /*
         * Create a transport endpoint.
         */
        if ((fd = t_open("/dev/inet_clts", O_RDWR, NULL)) < 0) {
            t_error("t_open");
            exit(1);
        }

        /*
         * Bind an arbitrary address to the transport
         * endpoint.
         */
        if (t_bind(fd, NULL, NULL) < 0) {
            t_error("t_bind");
            exit(1);
        }

        /*
         * Allocate a datagram.
         */
        udp = (struct t_unitdata *) t_alloc(fd, T_UNITDATA, T_ALL);

        if (udp == NULL) {
            t_error("t_alloc");
            exit(1);
        }

        /*
         * Create a host and service address for our host.
         */
        memset((char *) &rem_addr, 0, sizeof(struct sockaddr_in));
        memcpy((char *) &rem_addr.sin_addr.s_addr, (char *) hp->h_addr,
               hp->h_length);
        rem_addr.sin_port = sp->s_port;
        rem_addr.sin_family = AF_INET;

        /*
         * Construct the datagram.
         */
        udp->addr.maxlen = sizeof(struct sockaddr_in);
        udp->addr.len = sizeof(struct sockaddr_in);
        udp->addr.buf = (char *) &rem_addr;
        udp->opt.buf = (char *) 0;
        udp->opt.maxlen = 0;
        udp->opt.len = 0;
        udp->udata.len = 1;

        /*
         * Send a packet to the server.
         */
        if (t_sndudata(fd, udp) < 0) {
            t_error("t_sndudata");
            exit(1);
        }

        /*
         * Receive a packet back.
         */
        if (t_rcvudata(fd, udp, &flags) < 0) {
            if (t_errno == TLOOK) {
                if (t_rcvuderr(fd, NULL) < 0) {
                    t_error("t_rcvuderr");
                    exit(1);
                }
            }
            else {
                t_error("t_rcvudata");
                exit(1);
            }
        }

        /*
         * Print the packet.
         */
        udp->udata.buf[udp->udata.len] = '\0';
        printf("%s: %s", *argv, udp->udata.buf);

        /*
         * Shut down the connection.
         */
        t_unbind(fd);
        t_close(fd);
    }

    exit(0);
}

通过以上内容,我们详细了解了 TLI 网络编程的各个方面,包括名称到地址转换、实用函数、传输端点管理和无连接服务等,同时通过示例代码展示了如何在不同系统中使用 TLI 实现具体的网络通信功能。

深入理解 TLI 网络编程

5. 代码示例分析

前面给出了使用 SVR4 TLI 和 HP - UX 10.x 实现连接“daytime”服务的代码示例,下面对这些代码进行详细分析。

5.1 SVR4 TLI 示例代码分析

以下是 SVR4 TLI 实现“daytime”服务连接的代码:

#include <netconfig.h>
#include <netdir.h>
#include <tiuser.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>

#define SERVICENAME "daytime"

extern int t_errno;

int
main(int argc, char **argv)
{
    int fd, flags;
    struct netconfig *ncp;
    struct nd_hostserv ndh;
    struct t_unitdata *udp;
    struct nd_addrlist *nal;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s hostname [hostname...]\n", *argv);
        exit(1);
    }

    /*
     * Select the UDP transport provider.
     */
    if ((ncp = getnetconfigent("udp")) == NULL) {
        nc_perror("udp");
        exit(1);
    }

    while (--argc) {
        ndh.h_host = *++argv;
        ndh.h_serv = SERVICENAME;

        /*
         * Get a host and service address for this host.
         */
        if (netdir_getbyname(ncp, &ndh, &nal) != 0) {
            netdir_perror(*argv);
            exit(1);
        }

        /*
         * Create a transport endpoint.
         */
        if ((fd = t_open(ncp->nc_device, O_RDWR, NULL)) < 0) {
            t_error("t_open");
            exit(1);
        }

        /*
         * Bind an arbitrary address to the transport
         * endpoint.
         */
        if (t_bind(fd, NULL, NULL) < 0) {
            t_error("t_bind");
            exit(1);
        }

        /*
         * Allocate a datagram.
         */
        udp = (struct t_unitdata *) t_alloc(fd, T_UNITDATA, T_ALL);

        if (udp == NULL) {
            t_error("t_alloc");
            exit(1);
        }

        /*
         * Construct the datagram.
         */
        memcpy(&udp->addr, &nal->n_addrs[0], sizeof(struct netbuf));
        udp->udata.len = 1;

        /*
         * Send a packet to the server.
         */
        if (t_sndudata(fd, udp) < 0) {
            t_error("t_sndudata");
            exit(1);
        }

        /*
         * Receive a packet back.
         */
        if (t_rcvudata(fd, udp, &flags) < 0) {
            if (t_errno == TLOOK) {
                if (t_rcvuderr(fd, NULL) < 0) {
                    t_error("t_rcvuderr");
                    exit(1);
                }
            }
            else {
                t_error("t_rcvudata");
                exit(1);
            }
        }

        /*
         * Print the packet.
         */
        udp->udata.buf[udp->udata.len] = '\0';
        printf("%s: %s", *argv, udp->udata.buf);

        /*
         * Shut down the connection.
         */
        t_unbind(fd);
        t_close(fd);
    }

    exit(0);
}
  • 输入检查 :程序首先检查命令行参数,如果参数数量小于 2,则输出使用说明并退出。
  • 选择传输提供者 :使用 getnetconfigent 函数选择 UDP 传输提供者。
  • 获取地址 :通过 netdir_getbyname 函数获取主机和服务的地址。
  • 创建传输端点 :使用 t_open 函数创建传输端点。
  • 绑定地址 :使用 t_bind 函数将任意地址绑定到传输端点。
  • 分配数据报 :使用 t_alloc 函数分配 struct t_unitdata 结构。
  • 构造数据报 :将获取的地址复制到数据报的 addr 字段,并设置 udata 的长度。
  • 发送和接收数据 :使用 t_sndudata 发送数据,使用 t_rcvudata 接收数据。如果接收时出现 TLOOK 错误,调用 t_rcvuderr 清除错误。
  • 输出结果 :将接收到的数据作为字符串输出。
  • 关闭连接 :使用 t_unbind t_close 关闭传输端点。
5.2 HP - UX 10.x 示例代码分析

以下是 HP - UX 10.x 实现“daytime”服务连接的代码:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <tiuser.h>
#include <string.h>
#include <netdb.h>
#include <fcntl.h>
#include <stdio.h>

#define SERVICENAME "daytime"

extern int t_errno;

int
main(int argc, char **argv)
{
    int fd, flags;
    struct hostent *hp;
    struct servent *sp;
    struct t_unitdata *udp;
    struct nd_addrlist *nal;
    struct sockaddr_in rem_addr;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s hostname [hostname...]\n", *argv);
        exit(1);
    }

    if ((sp = getservbyname(SERVICENAME, "udp")) == NULL) {
        fprintf(stderr, "%s/udp: unknown service\n", SERVICENAME);
        exit(1);
    }

    while (--argc) {
        if ((hp = gethostbyname(*++argv)) == NULL) {
            fprintf(stderr, "%s: unknown host\n", *argv);
            continue;
        }

        /*
         * Create a transport endpoint.
         */
        if ((fd = t_open("/dev/inet_clts", O_RDWR, NULL)) < 0) {
            t_error("t_open");
            exit(1);
        }

        /*
         * Bind an arbitrary address to the transport
         * endpoint.
         */
        if (t_bind(fd, NULL, NULL) < 0) {
            t_error("t_bind");
            exit(1);
        }

        /*
         * Allocate a datagram.
         */
        udp = (struct t_unitdata *) t_alloc(fd, T_UNITDATA, T_ALL);

        if (udp == NULL) {
            t_error("t_alloc");
            exit(1);
        }

        /*
         * Create a host and service address for our host.
         */
        memset((char *) &rem_addr, 0, sizeof(struct sockaddr_in));
        memcpy((char *) &rem_addr.sin_addr.s_addr, (char *) hp->h_addr,
               hp->h_length);
        rem_addr.sin_port = sp->s_port;
        rem_addr.sin_family = AF_INET;

        /*
         * Construct the datagram.
         */
        udp->addr.maxlen = sizeof(struct sockaddr_in);
        udp->addr.len = sizeof(struct sockaddr_in);
        udp->addr.buf = (char *) &rem_addr;
        udp->opt.buf = (char *) 0;
        udp->opt.maxlen = 0;
        udp->opt.len = 0;
        udp->udata.len = 1;

        /*
         * Send a packet to the server.
         */
        if (t_sndudata(fd, udp) < 0) {
            t_error("t_sndudata");
            exit(1);
        }

        /*
         * Receive a packet back.
         */
        if (t_rcvudata(fd, udp, &flags) < 0) {
            if (t_errno == TLOOK) {
                if (t_rcvuderr(fd, NULL) < 0) {
                    t_error("t_rcvuderr");
                    exit(1);
                }
            }
            else {
                t_error("t_rcvudata");
                exit(1);
            }
        }

        /*
         * Print the packet.
         */
        udp->udata.buf[udp->udata.len] = '\0';
        printf("%s: %s", *argv, udp->udata.buf);

        /*
         * Shut down the connection.
         */
        t_unbind(fd);
        t_close(fd);
    }

    exit(0);
}
  • 输入检查 :与 SVR4 TLI 示例相同,检查命令行参数。
  • 获取服务和主机信息 :使用 getservbyname 获取服务地址(端口号),使用 gethostbyname 获取主机地址。
  • 创建传输端点 :使用 t_open 函数打开 /dev/inet_clts 设备创建传输端点。
  • 绑定地址 :使用 t_bind 函数将任意地址绑定到传输端点。
  • 分配数据报 :使用 t_alloc 函数分配 struct t_unitdata 结构。
  • 构造数据报 :创建 struct sockaddr_in 结构表示远程地址,并将其复制到数据报的 addr 字段。
  • 发送和接收数据 :与 SVR4 TLI 示例相同,使用 t_sndudata 发送数据,使用 t_rcvudata 接收数据,处理 TLOOK 错误。
  • 输出结果 :将接收到的数据作为字符串输出。
  • 关闭连接 :使用 t_unbind t_close 关闭传输端点。
6. TLI 网络编程的优缺点
6.1 优点
  • 灵活性 :TLI 提供了丰富的函数和数据结构,允许开发者根据不同的需求进行定制。例如,在传输端点管理方面,可以根据具体情况选择不同的选项和操作。
  • 可扩展性 :可以通过 t_optmgmt 函数与传输提供者协商协议选项,适应不同的网络环境和需求。
  • 支持多种服务类型 :支持面向连接和无连接两种服务类型,满足不同的通信需求。
6.2 缺点
  • 可移植性问题 :不同系统对 TLI 的实现可能存在差异,如 HP - UX 10.x 与其他系统在名称到地址转换和网络设备选择上有所不同,这增加了代码移植的难度。
  • 复杂性 :TLI 的函数和数据结构较多,使用起来相对复杂。例如,在处理无连接服务时,需要处理 TLOOK 错误等特殊情况,增加了开发的难度和代码的复杂度。
  • 错误处理 :TLI 函数通过 t_errno errno 来指示错误,错误处理相对复杂,需要开发者仔细处理各种错误情况。
7. 总结与建议

通过对 TLI 网络编程的深入学习,我们了解了其名称到地址转换、实用函数、传输端点管理和无连接服务等方面的知识。在实际应用中,可以根据以下建议进行开发:
- 选择合适的系统和传输提供者 :根据具体的应用场景和需求,选择合适的操作系统和传输提供者。例如,如果需要在 HP - UX 10.x 系统上开发,需要注意其与其他系统的差异。
- 注意可移植性 :在编写代码时,尽量考虑代码的可移植性。可以通过使用通用的函数和数据结构,避免使用特定系统的特性,减少代码移植的难度。
- 仔细处理错误 :TLI 编程中错误处理较为复杂,需要仔细处理各种错误情况。在调用 TLI 函数后,及时检查返回值和 t_errno errno 的值,进行相应的错误处理。
- 参考示例代码 :通过参考示例代码,可以更快地掌握 TLI 编程的方法和技巧。在实际开发中,可以根据示例代码进行修改和扩展,实现具体的功能。

总之,TLI 网络编程是一种强大的网络编程方式,但也存在一些挑战。开发者需要深入理解其原理和使用方法,结合实际需求,才能编写出高效、稳定的网络应用程序。

以下是 TLI 网络编程的主要步骤总结表格:
| 步骤 | 操作 | 函数 |
| — | — | — |
| 名称到地址转换 | 获取主机和服务地址 | netdir_getbyname getservbyname gethostbyname |
| 创建传输端点 | 打开通信设备 | t_open |
| 绑定地址 | 将地址绑定到传输端点 | t_bind |
| 分配结构 | 分配数据报等结构 | t_alloc |
| 发送和接收数据 | 交换数据 | t_sndudata t_rcvudata |
| 错误处理 | 处理接收数据时的错误 | t_rcvuderr |
| 关闭连接 | 禁用和关闭传输端点 | t_unbind t_close |

下面是 TLI 无连接服务通信的流程图:

graph TD;
    A[开始] --> B[选择传输提供者];
    B --> C[获取主机和服务地址];
    C --> D[创建传输端点];
    D --> E[绑定地址];
    E --> F[分配数据报];
    F --> G[构造数据报];
    G --> H[发送数据 t_sndudata];
    H --> I{是否成功};
    I -- 是 --> J[接收数据 t_rcvudata];
    I -- 否 --> K[处理错误];
    J --> L{是否成功};
    L -- 是 --> M[输出结果];
    L -- 否 --> N{是否 TLOOK 错误};
    N -- 是 --> O[清除错误 t_rcvuderr];
    N -- 否 --> K;
    O --> J;
    M --> P[关闭连接 t_unbind 和 t_close];
    P --> Q[结束];
    K --> R[程序终止];

通过以上内容,我们对 TLI 网络编程有了全面的了解,希望这些知识能帮助开发者更好地进行网络编程。

考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化与经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本与能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参与调度等方面的有效性,为低碳能源系统的设计与运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模与优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建与求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发与仿真验证。
【顶级EI复现】【最新EI论文】低温环境下考虑电池寿命的微电网优化调度(Matlab代码实现)内容概要:本文介绍了名为《【顶级EI复现】【最新EI论文】低温环境下考虑电池寿命的微电网优化调度(Matlab代码实现)》的技术文档,重点围绕在低温环境下,结合电池寿命衰减因素对微电网系统进行优化调度的研究。该研究通过建立数学模型,综合考虑风光储、柴油、燃气等多种能源形式以及电网交互关系,利用Matlab编程实现优化算法(如内点法、多目标粒子群算法等),完成对微电网运行成本、能源效率与电池使用寿命之间的多目标协同优化。文中强调了实际寒潮场景下的V2G调度数据应用,并提供了完整的仿真代码与数据集支持,具有较强的工程复现价值。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事微电网、储能系统优化等相关领域的工程技术人员;尤其适合希望复现高水平EI论文成果的用户; 使用场景及目标:①用于低温环境下微电网能量管理系统的建模与仿真;②支撑考虑电池老化机制的储能优化调度研究;③服务于学术论文复现、课题项目开发及智能电网优化算法验证; 阅读建议:建议结合提供的网盘资源(包括YALMIP工具包、完整代码与数据集)进行实践操作,重点关注目标函数构建、约束条件设置及多目标优化求解过程,建议在Matlab环境中调试代码以深入理解算法实现细节与系统响应特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值