深入理解 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 网络编程有了全面的了解,希望这些知识能帮助开发者更好地进行网络编程。
超级会员免费看
5

被折叠的 条评论
为什么被折叠?



