UNIX网络编程卷一 学习笔记 第三十一章 流

在大多数源自SVR 4的内核中,X/Open传输接口(X/Open Transport Interface,XTI,是独立于套接字API的另一个网络编程API)和网络协议通常就像终端IO系统那样也使用流系统(STREAMS system)实现。

我们将使用传输提供者接口(Transport Provider Interface,TPI)开发一个简单的TCP客户程序,TPI是在基于流的系统上,XTI和套接字通常使用的传输层访问接口。

流由Dennis Ritchie设计,并于1986年随SVR 3首次广泛提供支持。POSIX规范将流定义为一个选项组(option group),意味着POSIX兼容系统可以不实现流,但如果实现,则必须符合POSIX规范。基本流函数包括getmsg、getpmsg、putmsg、putpmsg、fattach、所有流ioctl命令。XTI往往使用流实现。所有源自System V的系统都应提供流(SVR即System V Release),但各个4.x BSD版本不提供流。

流(STREAMS)这个名字尽管全是大写字母,但不是一个首字母缩写词,因此改用全小写字母可能更合理。我们需要区分本章讲解的流IO系统(streams IO system)和标准IO流(与标准IO库相关)。

流在进程和驱动程序之间提供全双工的连接:
在这里插入图片描述
驱动程序不必与某个硬件设备相关联,它可以是一个伪设备驱动程序(即软件驱动程序)。

流头(stream head)由一个内核例程构成,应用进程针对流描述符执行系统调用(如read、putmsg、ioctl等)时这些内核例程将被激活。

进程可以在流头和驱动程序之间动态增加或删除中间处理模块(processing module),这些模块对顺着一个流上行或下行的消息施行某种类型的过滤:
在这里插入图片描述
往一个流中可以推入(pushing)任意数量的模块,推入指的是每个新模块都被插入到流头的紧下方。

多路复选器(multiplexor)是一种特殊类型的伪设备驱动程序,它从多个源接受数据,例如,可在SVR 4上找到的TCP/IP协议族基于流的某个实现如下图所示,其中就有多路复选器:在这里插入图片描述
上图中:
1.在创建一个套接字时,套接字函数库把模块sockmod推入流中,向应用进程提供套接字API的是套接字函数库和sockmod流模块两者的组合。

2.创建一个XTI端点时,XTI函数库把模块timod推入流中,向应用进程提供XTI API的是XTI函数库和timod流模块两者的组合。XTI API的端点相当于套接字API的套接字。

本书早先版本详细叙述了XTI API,但它已不被广泛使用,甚至POSIX规范也不再涵盖它,因此就不讲述了。

3.为了针对XTI端点使用read和write访问网络数据,通常必须把模块tirdwr推入流中,推入该模块后该进程可能不会再使用XTI了,因此上图中我们没有显示XTI库。

4.各种各样的服务接口定义了网络消息在流中上行和下行交换的格式。最常见的3个服务接口为:
(1)传输提供者接口(TPI)定义了传输层提供者(如TCP和UDP)向它上方的模块提供的接口。

(2)网络提供者接口(NPI,Network Provider Interface)定义了网络层提供者(如IP)向它上方的模块提供的接口。

(3)数据链路提供者接口(DLPI)。

一个流中的每个部件(流头、所有处理模块、驱动程序)都包含至少一对队列,即一个写队列和一个读队列:
在这里插入图片描述
流消息可分为高优先级(high priority)、优先级带(priority band)、普通(normal)三类。优先级共有256带,在0~255之间取值,其中普通消息位于带0,流消息的优先级用于排队和流量控制,按约定高优先级消息不受流量控制影响。下图是一个给定队列中消息的出现顺序:
在这里插入图片描述
虽然流系统支持256个不同的优先级带,网络协议往往只用经加速数据的带1和代表普通数据的带0。

TPI不认为TCP带外数据是真正的经加速数据,事实上TCP的普通数据和带外数据都使用带0。只有那些让经加速数据先于普通数据发送的协议才使用带1发送经加速数据。

在SVR 4之前的版本中没有优先级带的概念,只有普通消息和优先级消息,SVR 4实现了优先级带,并提供了getpmsg和putpmsg函数,较早的优先级消息于是被重命名为高优先级,常用的术语定义[ Rago 1993 ]称高优先级外的消息为普通优先级(normal priority)消息,然后把这些普通优先级消息细分到各个优先级带中。普通消息一词指处于带0的消息。

普通优先级消息和高优先级消息这两大类中分别约有12种和18种,从应用进程和getmsg、putmsg函数角度看,我们仅关注3种不同类型的消息:M_DATA、M_PROTO、M_PCPROTO(PC表示priority control,优先级控制,隐指高优先级消息)。下图说明了这三种消息类型是如何使用write和putmsg函数产生的:
在这里插入图片描述
沿着流上行和下行的数据由消息构成,且每个消息含有控制或数据,或两者都有。如果在流上使用read和write函数,那么所传送的仅仅是数据,为了让进程能读写数据和控制两部分信息,流系统增加了以下函数:
在这里插入图片描述
消息的控制和数据两部分各自由一个strbuf结构说明:
在这里插入图片描述
注意strbuf结构和XTI API所用的netbuf结构之间的相似性,它们由3个同名成员构成,但netbuf结构的两个长度成员是无符号整数,而strbuf结构的两个长度成员是普通整数。原因在于有些流函数使用值为-1的len或maxlen成员表示特殊的含义。

使用putmsg可以单纯发送控制信息或数据,也可同时发送两者。为了指示不发送控制信息,可把ctlptr参数指定为空指针,也可把ctlptr->len设为-1。同样的手段设置dataptr参数用于指示不发送数据信息。

如果不发送控制信息,putmsg函数将产生一个M_DATA消息,否则根据flags参数产生一个M_PROTO或M_PCPROTO消息,flags参数为0表示普通消息,为RS_HIPRI表示高优先级消息。

getmsg函数的最后一个参数是一个值-结果参数,如果调用时指定的flagsp参数指向的整数值为0,则返回的是流中第一个消息(既可能是普通消息,也可能是高优先级消息),如果该整数值为RS_HIPRI,那就等待一个高优先级消息到达流头,无论哪种情况,存放到flagsp参数指向的整数中的值根据所返回消息的类型或为0,或为RS_HIPRI。

假设传给getmsg函数的ctlptr和dataptr参数都是非空指针,如果没有控制信息待返回(也就是即将返回一个M_DATA消息),getmsg函数就在返回时把ctlptr->len设为-1作为指示,类似地,没有数据待返回时就把dataptr->len设为-1。

putmsg函数在成功时返回0,在出错时返回-1。但getmsg函数仅在整个消息完整返回给调用者时才返回0,如果控制缓冲区不足以容纳完整的控制信息,就返回非负的MORECTL,类似地,如果数据缓冲区太小,就返回MOREDATA,如果两个缓冲区都太小,就返回这两个标志的逻辑或。

对不同优先级带的支持随SVR 4被增加到流系统时,以下两个getmsg和putmsg函数的变体函数也被同时引入:
在这里插入图片描述
putpmsg函数的band参数必须在0~255之间(含),如果flags参数为MSG_BAND,就产生一个所指定优先级带的消息,把flags参数置为MSG_BAND且把band参数置为0等效于调用putmsg,如果flags参数为MSG_HIPRI,band参数就必须为0,所产生的的是一个高优先级消息(而putmsg函数使用的标志为RS_HIPRI)。

getpmsg的bandp和flagsp参数是值-结果参数,flagsp参数指向的整数可以取值MSG_HIPRI(用来读入一个高优先级消息)、MSG_BAND(用来读入一个优先级至少为bandp参数指向的整数值消息)、MSG_ANY(用来读入任一消息)。函数返回时,bandp参数指向的整数含有所读入消息的优先级带,flagsp参数指向的整数可能是MSG_HIPRI(如果读入的是一个高优先级消息)或MSG_BAND(如果读入的是其他类型消息)。

在流系统中我们会再次使用ioctl函数:
在这里插入图片描述
此处的ioctl函数的与第十七章中的相比,唯一的变化就是处理流时所包含的头文件不同。

大约30个ioctl请求影响流头,每个请求都以I_打头,它们的具体说明通常在streamio手册页面给出。

在图31-3中,我们把TPI表示为传输层向它上方的模块提供的服务接口。在流环境中,套接字和XTI都使用TPI。在图31-3中,应用进程跟TCP和UDP交换TPI消息通过的是套接字函数库和sockmod的组合或XTI函数库和timod的组合。

TPI是一个基于消息的接口,它定义了在应用进程(如XTI函数库或套接字函数库)和传输层之间沿着流上行和下行交换的消息,包括消息的格式和每个消息执行的操作。例如,应用进程向提供者发送一个请求(如bind一个本地地址),提供者则发回一个响应(成功或出错)。一些事件在提供者异步地发生(对某个服务器来说,连接请求的到达),它们导致沿着流向上发送的消息或信号。

我们可以绕过XTI和套接字直接使用TPI,我们将改用TPI取代套接字重新编写我们的时间获取客户程序。用编程语言进行类比,使用套接字或XTI好比使用诸如C或Pascal等高级语言编程,而使用TPI好比使用汇编语言编程。我们不提倡在现实应用程序中使用TPI,但查看TPI如何工作并开发本例有助于我们更好地理解在流环境中套接字函数库和XTI函数库的工作原理。

以下是tpi_daytime.h头文件:

#include "unpxti.h"
// 头文件sys/steam.h中包含了所有TPI消息的结构定义
#include <sys/stream.h>
#include <sys/tihdr.h>void tpi_bind(int, const void *, size_t);
void tpi_connect(int, const void *, size_t);
ssize_t tpi_read(int, void *, size_t);
void tpi_close(int);

以下是时间获取客户程序的main函数:

#include "tpi_daytime.h"int main(int argc, char **argv) {int fd, n;char recvline[MAXLINE + 1];struct sockaddr_in myaddr, servaddr;if (argc != 2) {err_quit("usage: tpi_daytime <IPaddress>");}// 打开与传输提供者TCP对应的设备(通常为/dev/tcp)fd = Open(XTI_TCP, O_RDWR, 0);/* bind any local address */bzero(&myaddr, sizeof(myaddr));myaddr.sin_family = AF_INET;// 以INADDR_ANY和端口0填写一个网际网套接字地址结构,告知TCP捆绑任一本地地址和本地端点,由tpi_bind函数完成捆绑myaddr.sin_addr.s_addr = htonl(INADDR_ANY);myaddr.sin_port = htons(0);tpi_bind(fd, &myaddr, sizeof(struct sockaddr_in));/* fill in server's address */bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(13);    /* daytime server */Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);// tpi_connect函数建立与服务器的连接tpi_connect(fd, &servaddr, sizeof(struct sockaddr_in));for (; ; ) {if ((n = tpi_read(fd, recvline, MAXLINE)) <= 0) {if (n == 0) {break;} eles {err_sys("tpi_read error");}}recvline[n] = 0;    /* null terminate */fputs(recvline, stdout);}tpi_close(fd);exit(0);
}

以下是tpi_bind函数:

#include "tpi_daytime.h"void tpi_bind(int fd, const void *addr, size_t addrlen) {struct {// T_bind_req结构介绍在代码下面// 所有TPI请求都定义成以一个长整数类型字段开头的某个结构,如此处的T_bind_req结构,后跟一个缓冲区// TPI对该缓冲区中内容未做任何规定,由具体的提供者定义,TCP提供者期待该缓冲区中有一个sockaddr_in结构struct T_bind_req msg_hdr;char addr[128];} bind_req;struct {struct T_bind_ack msg_hdr;char addr[128];} bind_ack;struct strbuf ctlbuf;struct T_error_ack *error_ack;int flags;bind_req.msg_hdr.PRIM_type = T_BIND_REQ;bind_req.msg_hdr.ADDR_length = addrlen;    // addrlen对于网际网套接字地址结构为16字节// 设置套接字地址结构的偏移量bind_req.msg_hdr.ADDR_offset = sizeof(struct T_bind_req);// 由于我们是客户,因此将CONIND_number设为0bind_req.msg_hdr.CONIND_number = 0;// 我们直接用memcpy函数而非赋值运算等方式将sockaddr_in结构复制到bind_req结构中// 因此这难以保证sockaddr_in结构是适当对齐的memcpy(bind_req.addr, addr, addrlen);    /* sockaddr_in{} */ctlbuf.len = sizeof(struct T_bind_req) + addrlen;ctlbuf.buf = (char *)&bind_req;// TPI要求我们把刚构造的结构作为一个M_PROTO消息传给提供者// 于是我们将这个bind_req结构指定为控制信息,且指定没有数据信息,且指定标志为0Putmsg(fd, &ctlbuf, NULL, 0);ctlbuf.maxlen = sizeof(bind_ack);ctlbuf.len = 0;ctlbuf.buf = (char *)&bind_ack;// 对T_BIND_REQ请求的响应或者是T_BIND_ACK消息,或者是T_ERROR_ACK消息,这些确认消息是高优先级消息// 于是我们指定RS_HIPRI读入它,既然该应该是高优先级消息,它将绕过流中任意普通优先级消息// 可能的应答消息的结构介绍在代码下面// 两个应答消息都以PRIM_type成员打头,因此我们可以假设它是一个T_BIND_ACK消息读入应答// 查看类型值后再相应地处理该消息flags = RS_HIPRI;// 我们不期望提供者的任何数据,因此getmsg函数的第三个参数为空指针Getmsg(fd, &ctlbuf, NULL, &flags);// 在验证所返回的控制信息量至少是一个长整数的大小时,我们需要把sizeof的返回值强转为一个int// 因为sizeof返回的是一个无符号整型,而getmsg函数返回的strbuf.len可能是-1// 如果不进行转换,比较运算符一边是一个有符号值,一边是一个无符号值,C编译器会将有符号值转换为无符号值// -1转换为有符号值非常大,导致-1大于4(假设一个长整数占4个字节)if (ctlbuf.len < (int)sizeof(long)) {err_quit("bad length from getmsg");}switch (bind_ack.msg_hdr.PRIM_type) {// 如果应答是T_BIND_ACK消息,那么捆绑成功,直接返回,捆绑的实际地址由bind_ack结构的addr成员返回case T_BIND_ACK:return;// 如果应答是T_ERROR_ACKcase T_ERROR_ACK;// 验证所收到的是完整的消息if (ctlbuf.len < (int)sizeof(struct T_error_ack)) {err_quit("bad length for T_ERROR_ACK");}error_ack = (struct T_error_ack *)&bind_ack.msg_hdr;// 如果发生错误直接终止,不再返回到调用者err_quit("T_ERROR_ACK from bind (%d, %d)", error_ack->TLI_error, error_ack->UNIX_error);default:err_quit("unexpected message type: %d", bind_ackmsg_hdr.PRIM_type);}
}

以上函数中,T_bind_req结构在头文件sys/tihdr.h中定义如下:
在这里插入图片描述
以上函数中,对于T_BIND_REQ请求的响应或者是T_BIND_ACK消息,或者是T_ERROR_ACK消息,这两个应答消息的结构定义如下:
在这里插入图片描述
对于以上函数,我们尝试捆绑端口1,这需要超级用户权限,因为它是1024以内的端口,我们会得到以下输出:
在这里插入图片描述
该系统上EACCES的值为3。如果我们尝试捆绑一个1023以上,但被另一TCP端点使用中的端口,我们会得到以下输出:
在这里插入图片描述
该系统上EADDRBUSY的值为23,这个错误是TPI为了支持XTI而引入的,支持TLI的较早版本TPI在请求捆绑一个已使用的端口时将另行捆绑一个未使用的端口,这意味着捆绑众所周知端口的服务器不得不比较返回的地址(返回的地址出自t_bind函数的第3个指针参数返回的T_bind_ack消息)和请求的地址,如果不一致就放弃。

以下是tpi_connect函数,它建立与服务器的连接:

#include "tpi_daytime.h"void tpi_connect(int fd, const void *addr, size_t addrlen) {// 就像tpi_bind函数一样,此处也自定义一个名为conn_req的结构,它包含一个T_conn_req结构和用于存放协议地址的空间struct {// T_conn_req结构介绍在代码下面struct T_conn_req msg_hdr;char addr[128];} conn_req;struct {struct T_conn_con msg_hdr;char addr[128];} conn_con;struct strbuf ctlbuf;union T_primitives rcvbuf;struct T_error_ack *error_ack;struct T_discon_ind *discon_ind;int flags;conn_req.msg_hdr.PRIM_type = T_CONN_REQ;conn_req.msg_hdr.DEST_length = addrlen;conn_req.msg_hdr.DEST_offset = sizeof(struct T_conn_req);conn_req.msg_hdr.OPT_length = 0;conn_req.msg_hdr.OPT_offset = 0;memcpy(conn_req.addr, addr, addrlen);    /* sockaddr_in{} */ctlbuf.len = sizeof(struct T_conn_req) + addrlen;ctlbuf.buf = (char *)&conn_req;// 单纯指定控制信息调用putmsg,同时把标志指定为0,以顺着流下行发送一个M_PROTO消息Putmsg(fd, &ctlbuf, NULL, 0);ctlbuf.maxlen = sizeof(union T_primitives);ctlbuf.len = 0;ctlbuf.buf = (char *)&rcvbuf;flags = RS_HIPRI;// 调用getmsg期待接受T_OK_ACK消息(表示连接建立已启动,T_ok_ack结构介绍在代码下面)// 或可能接收到T_ERROR_ACK消息(上面已给出)// 既然不知道会接收到什么类型的消息,我们于是定义一个名为T_primitives的由所有可能的请求和应答组成的联合// 该联合用作控制消息的输入缓冲区Getmsg(fd, &ctlbuf, NULL, &flags);if (ctlbuf.len < (int)sizeof(long)) {err_quit("tpi_connect: bad length from getmsg");}switch (rcvbuf.type) {// 表示成功的T_OK_ACK消息只是告诉我们连接建立已启动,现在还要等待T_CONN_CON消息以获悉对端是否已接受该连接case T_OK_ACK:break;case T_ERROR_ACK:if (ctlbuf.len < (int)sizeof(struct T_error_ack)) {err_quit("tpi_connect: bad length for T_ERROR_ACK");}error_ack = (struct T_error_ack *)&rcvbuf;err_quit("tpi_connect: T_ERROR_ACK from conn (%d, %d)", error_ack->TLI_error, error_ack->UNIX_error);default:err_quit("tpi_connect: unexpected message type: %d", rcvbuf.type);}ctlbuf.maxlen = sizeof(conn_con);ctlbuf.len = 0;ctlbuf.buf = (char *)&conn_con;flags = 0;// 再次调用getmsg,但所期待的消息是一个M_PROTO消息而非M_PCPROTO消息,于是把标志设为0// 如果收到一个T_CONN_CON消息(T_conn_con结构介绍在代码下面),则连接建立完毕 // 如果连接未能建立(对端进程不在运行、超时等原因),会返回一个T_DISCON_IND消息(T_discon_ind结构介绍在代码下面)Getmsg(fd, &ctlbuf, NULL, &flags);if (ctlbuf.len < (int)sizeof(long)) {err_quit("tpi_connect2: bad length from getmsg");}switch(conn_con.msg_hdr.PRIM_type) {case T_CONN_CON:break;case T_DISCON_IND:if (ctlbuf.len < (int)sizeof(struct T_discon_ind)) {err_quit("tpi_connect2: bad length for T_DISCON_IND");}discon_ind = (struct T_discon_ind *)&conn_con.msg_hdr;err_quit("tpi_connect2: T_DISCON_IND from conn (%d)", discon_ind->DISCON_reason);default:err_quit("tpi_connect2: unexpected message type: %d", conn_con.msg_hdr.PRIM_type);}
}

TPI定义了一个T_conn_req结构,用于存放连接的协议地址和选项:
在这里插入图片描述
连接建立已启动时,返回的T_ok_ack结构如下:
在这里插入图片描述
连接成功建立时,返回的T_conn_con结构如下:
在这里插入图片描述
连接建立失败时,返回的T_conn_ind结构如下:
在这里插入图片描述
对于以上函数,我们先查看提供者返回的各种错误,如果我们指定连接到一个没有运行标准daytime服务器的主机:
在这里插入图片描述
错误146表示ECONNREFUSED。接着指定一个未接入因特网的IP地址:
在这里插入图片描述
错误145表示ETIMEDOUT,再次对该IP运行本程序,我们得到另一个错误:
在这里插入图片描述
错误148表示EHOSTUNREACH,这两个结果的区别在于,第一次没有ICMP主机不可达错误的返送,而第二次有。

函数tpi_read从一个流中读入数据:

#include "tpi_daytime.h"ssize_t tpi_read(int fd, void *buf, size_t len) {struct strbuf ctlbuf;struct strbuf datbuf;union T_primitives rcvbuf;int flags;ctlbuf.maxlen = sizeof(union T_primitives);ctlbuf.buf = (char *)&rcvbuf;datbuf.maxlen = len;datbuf.buf = buf;datbuf.len = 0;flags = 0;// 同时读入控制信息和数据,用于返回数据的strbuf结构指向调用者指定的缓冲区,在读入时流上可能有4种情形:// 1.数据以一个M_DATA消息到来,这由返回的控制信息长度为-1指示//   消息会被拷贝到调用者指定的缓冲区,此时本函数返回getmsg函数返回的数据长度// 2.数据以一个T_DATA_IND消息到来,此时,控制消息是一个T_data_ind结构(此结构的介绍在代码下面)//   如果返回了此消息,我们就忽略MORE_flag成员(对于TCP这样的字节流协议,该成员不会被设置)//   此时本函数返回getmsg函数返回的数据长度// 3.到达一个T_DISCON_IND消息,表示收到一个断连请求,对于TCP提供者,本情形发生在某连接上收到RST后//   此简单的例子中,我们不处理该情形// 4.到达一个T_ORDREL_IND消息,表示TCP提供者已收取的所有分节均已被消费,且返回的是FIN//   T_ordrel_ind结构的介绍在代码下面,本函数此时返回0,以向调用者指示已在连接上遇到EOF//   这是TCP的有序释放(orderly release),即三次挥手Getmsg(fd, &ctlbuf, &datbuf, &flags);if (ctlbuf.len >= (int)sizeof(long)) {if (rcvbuf.type == T_DATA_IND) {return datbuf.len;} else if (rcvbuf.type == T_ORDREL_IND) {return 0;} else {err_quit("tpi_read: unexpected type %d", rcvbuf.type);}} else if (ctlbuf.len == -1) {return datbuf.len;} else {err_quit("tpi_read: bad length from getmsg");}
}

T_data_ind结构如下:
在这里插入图片描述
当收取FIN时,getmsg函数会返回一个T_ordrel_ind结构:
在这里插入图片描述
tpi_close函数:

#include "tpi_daytime.h"// 本函数相当于XTI的t_sndrel函数
void tpi_close(int fd) {struct T_ordrel_req ordrel_req;struct strbuf ctlbuf;ordrel_req.PRIM_type = T_ORDREL_REQ;ctlbuf.len = sizeof(struct T_ordrel_req);ctlbuf.buf = (char *)&ordrel_req;// 构造一个T_ordrel_req结构(此结构的介绍在代码下面)并调用putmsg将其作为一个M_PROTO消息发送出去Putmsg(fd, &ctlbuf, NULL, 0);Close(fd);
}

用于主动结束TCP连接的T_ordrel_req结构:
在这里插入图片描述
tpi_close函数中,我们调用putmsg沿着流下行发送一个顺序释放请求,然后立即关闭该流,如果关闭流期间,我们的有序释放请求被流子系统丢弃,此时流关闭时的默认处理就是有序释放,这对TCP来说没问题。

本例子展示了TPI的风格,应用进程沿着流下行向提供者发送消息(请求),提供者则沿着流上行发回消息(应答)。一些消息交换是简单的请求-应答情形(如捆绑一个本地地址),另外一些消息交换则需要耗费一段时间(如建立一个连接),并允许我们在等待应答期间做其他事而非空等。本例选择编写使用TPI的TCP客户程序而非服务器程序是为了简单,编写使用TPI的服务器程序并合理地处理连接要困难得多。

从XTI到TPI的函数映射比较接近,而从套接字从TPI的映射不那么接近,但这两个函数库都处理了TPI所需的大量细节,从而简化了应用程序的编写。

我们比较一下使用TPI完成网络操作与套接字实现于内核中的系统上完成同样操作所需的系统调用个数,TPI情形捆绑一个本地地址需要2个系统调用,而内核套接字情形只需要1个;TPI情形在一个阻塞式描述符上建立一个连接需要3个系统调用,而内核套接字情形只需要1个。

XTI一般使用流来实现,为访问流子系统而提供的4个新函数时getmsg、getpmsg、putmsg、putpmsg,已有的ioctl函数也被流子系统频繁使用。

TPI是从上层进入传输层的SVR 4流接口。XTI和套接字均使用TPI。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/127389.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Selenium 隐藏浏览器指纹特征的几种方式

我们使用 Selenium 对网页进行爬虫时&#xff0c;如果不做任何处理直接进行爬取&#xff0c;会导致很多特征是暴露的 对一些做了反爬的网站&#xff0c;做了特征检测&#xff0c;用来阻止一些恶意爬虫 本篇文章将介绍几种常用的隐藏浏览器指纹特征的方式 1. 直接爬取 目标对…

基于微服务+Java+Spring Cloud +UniApp +MySql开发的智慧工地源码(物联网、人工智能、AI识别、危大工程)

智慧工地系统利用物联网、人工智能、云计算、大数据、移动互联网等新一代信息技术&#xff0c;通过工地中台、三维建模服务、视频AI分析服务等技术支撑&#xff0c;实现智慧工地高精度动态仿真&#xff0c;趋势分析、预测、模拟&#xff0c;建设智能化、标准化的智慧工地综合业…

vue3+ts项目打包后的本地访问

注意&#xff1a;打包之后不可直接点击html访问&#xff0c;需要给项目安装本地服务&#xff01; 1、安装servenpm i -g serve 2、打包项目npm run build 生成dist文件夹 3、本地访问serve dist 运行service dist之后的控制台 可复制下方的地址运行打包后的项目&#xff0c;运行…

强大的JTAG边界扫描(5):FPGA边界扫描应用

文章目录 1. 获取芯片的BSDL文件2. 硬件连接3. 边界扫描测试4. 总结 上一篇文章&#xff0c;介绍了基于STM32F103的JTAG边界扫描应用&#xff0c;演示了TopJTAG Probe软件的应用&#xff0c;以及边界扫描的基本功能。本文介绍基于Xilinx FPGA的边界扫描应用&#xff0c;两者几乎…

opencv识别一张图片的多个红框,并截取红框的内容

需求 需要获取图片的红框的内容&#xff0c;实体的图片我就不放了 获取红框 先截取获得图片的多个轮廓 import cv2 import numpy as np # 加载图像并转换为灰度图像 image cv2.imread(image6.jpg) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 应用高斯模糊以减…

保姆级-微信小程序开发教程

一&#xff0c;注册微信小程序 如果你还没有微信公众平台的账号&#xff0c;请先进入微信公众平台首页&#xff0c;点击 “立即注册” 按钮进行注册。注册的账号类型可以是订阅号、服务号、小程序以及企业微信&#xff0c;我们选择 “小程序” 即可。 接着填写账号信息&#x…

文件操作(个人学习笔记黑马学习)

C中对文件操作需要包含头文件<fstream > 文件类型分为两种: 1.文本文件&#xff1a;文件以文本的ASCII码形式存储在计算机中 2.二进制文件&#xff1a;文件以文本的二进制形式存储在计算机中&#xff0c;用户一般不能直接读懂它们 操作文件的三大类: 1.ofstream: 写操作 …

生成多样、真实的评论(2019 IEEE International Conference on Big Data )

论文题目&#xff08;Title&#xff09;&#xff1a;Learning to Generate Diverse and Authentic Reviews via an Encoder-Decoder Model with Transformer and GRU 研究问题&#xff08;Question&#xff09;&#xff1a;评论生成&#xff0c;由上下文评论->生成评论 研…

Java类和对象(七千字详解!!!带你彻底理解类和对象)

目录 一、面向对象的初步认知 1、什么是面向对象 2、面向对象和面向过程 &#xff08;1&#xff09;传统洗衣服的过程 &#xff08;2&#xff09;现代洗衣服过程 ​编辑 二、类的定义和使用 1、类的定义格式 三、类的实例化 1、什么是实例化 2、类和对象说明 四、t…

计算机网络初识

目录 1、计算机网络背景 网络发展 认识 "协议" 2、网络协议初识 OSI七层模型 TCP/IP五层(或四层)模型 3、网络传输基本流程 网络传输流程图 数据包封装和分用 4、网络中的地址管理 认识IP地址 认识MAC地址 1、计算机网络背景 网络发展 在之前呢&…

redis如何保证接口的幂等性

背景 如何防止接口中同样的数据提交&#xff0c;以及如何保证消息不被重复消费&#xff0c;这些都是shigen在学习的过程中遇到的问题。今天&#xff0c;趁着在学习redis的间隙&#xff0c;我写了一篇文章进行简单的实现。 注意&#xff1a;仅使用于单机的场景&#xff0c;对于…

Android Studio实现一笔画完小游戏

文章目录 一、项目概述二、开发环境三、详细设计3.1、数据库设计3.2、普通模式3.3、随机模式3.4、关卡列表 四、运行演示五、项目总结六、源码获取 一、项目概述 Android一笔画完是一种益智游戏&#xff0c;玩家需要从起点开始通过一条连续的线&#xff0c;将图形中所有的方块…

一种基于注意机制的快速、鲁棒的混合气体识别和浓度检测算法,配备了具有双损失函数的递归神经网络

A fast and robust mixture gases identification and concentration detection algorithm based on attention mechanism equipped recurrent neural network with double loss function 摘要 提出一个由注意力机制组成的电子鼻系统。首先采用端到端的编码器译码器&#xff…

树莓派入门

目录 前言系统烧录使用官方烧录工具选择操作系统选择存储卡配置 Win32DiskImager 有屏幕树莓派开机树莓派关机无屏幕树莓派开机获取树莓派IP地址通过路由器获取共享网络方式获取给树莓派配置静态IP地址查找默认网关分盘给树莓派的IP地址修改树莓派DHCP配置文件 ssh登录 让树莓派…

C# PSO 粒子群优化算法 遗传算法 随机算法 求解复杂方程的最大、最小值

复杂方程可以自己定义&#xff0c;以下是看别人的题目&#xff0c;然后自己来做 以下是计算结果 private void GetMinResult(out double resultX1, out double min){double x1, result;Random random1 new Random(DateTime.Now.Millisecond* DateTime.Now.Second);min 99999…

数字城市:科技革命下的未来之城

随着科技的不断进步&#xff0c;数字城市已经成为了未来城市发展的关键趋势。数字城市是指利用先进的信息技术、互联网和大数据等工具&#xff0c;将城市各个方面进行数字化、智能化、互联化的发展模式。它不仅仅是一种技术&#xff0c;更是一种对城市管理、发展和居民生活方式…

局域网ntp服务器设置(windows时间同步服务器NetTime)(ubuntu systemd-timesyncd ntp客户端)123端口、ntp校时

文章目录 背景windows如何配置ntp服务器手动配置配置参数AnnounceFlags和Enabled含义 使用软件配置&#xff08;NetTime&#xff09;实操相关疑问&#xff1a;0.nettime.pool.ntp.org是什么&#xff1f; 注意事项请务必检查windows主机123端口是否已被占用&#xff0c;方法请参…

B站:AB Test 知识全解

AB Test的实质&#xff1a;假设检验&#xff0c;主要有以下几个步骤&#xff1a; 1、在实验开始前&#xff0c;找产品、项目经理等确认&#xff1a;实验需要验证的改动点&#xff08;一次只能看一个&#xff01;&#xff01;&#xff01;&#xff09; 2、数据分析师设计需要去观…

C++ 多态

引例&#xff1a; #include<iostream> using namespace std; class Animal { public:void speak(){cout<<"动物在说话"<<endl;} }; class Cat:public Animal { public:void speak(){cout<<"小猫在说话"<<endl;} }; void Do…

Java事件机制简介 内含面试题

面试题分享 云数据解决事务回滚问题 点我直达 2023最新面试合集链接 2023大厂面试题PDF 面试题PDF版本 java、python面试题 项目实战:AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画、AI讲话、翻译,GPU点亮…