远程调用-Sun RPC

  • 一、概述
    • 二、函数clnt_create
    • 三、RPC例子
      • 四、多线程化
      • 五、服务器捆绑
      • 六、inetd和RPC服务器
      • 七、认证
      • 八、超时和重传
      • 九、TCP连接管理
      • 十、事务ID
      • 十一、服务器重复请求高速缓存
      • 十二、客户或服务器的过早终止
  • 1.服务器的过早终止
    • 2.客户的过早终止
    • 十三、XDR:外部数据表示
    • 十四、例子:链表处理
    • 十五、RPC分组格式

一、概述

构建一个应用程序时,应该在以下两者之间作出选择:

  1. 构建一个庞大的单一程序,完成全部工作。
  2. 把整个应用程序散布到彼此通信的多个进程中。

选择2,需做如下选择:

  1. 假设所有进程运行在同一台主机上。
  2. 假设某些进程会运行在其他主机上。

不同部分之间需要网络通信的应用程序大多数是使用显式网络编程方式编写的,也就是如UNPvl中讲述的那样直接调用套接字API或 XTI API。使用套接字API时,客户调用socket、connect、read和write, 服务器则调用socket、bind、listen、 accept、read和write。熟悉的大多数应用程序(Web浏览器、Web服务器、Telne客户、 Telnet服务器等程序)就是以这种方式编写的。

编写分布式应用程序的另一种方式是使用隐式网络编程远程过程调用(RPC)提供了这样的一个工具。使用早已熟悉的过程调用来编写应用程序,但是调用进程(客户)和含有被调用过程的进程(服务器)可在不同的主机上执行。

客户和服务器运行在不同的主机上而且过程调用中涉及网络I/O, 这样的事实对于程序员基本上是透明的。事实上衡量 一 个RPC 软件包的测度之一就是它能使作为底层支撑的网络I/O对程序员的透明度有多大。

二、函数clnt_create

此函数运行成功返回一个客户句柄。

#include<rpc/rpc.h>
CLIENT *clnt_create(const char *host,unsigned long program,unsigned long versnum,const char *protocol);
//host:主机名、IP地址
//program:程序名
//versnum:版本号
//program和versnum来自square.x文件
//protocol:协议选择,指定为TCP或UDP

三、RPC例子

例:客户以一个长整数调用服务器过程,服务器返回该值的平方。

文件square.x(.x结尾的文件称为RPC说明书文件,定义了服务器过程以及这些过程的参数和结果)内容:

struct square_in {       /* 输入(参数) */long  arg1;
};struct square_out {       /* 输出(结果) */long  res1;
};program SQUARE_PROG {version SQUARE_VERS {square_out  SQUAREPROC(square_in) = 1; /* 程序编号= 1 */} = 1;               /* 版本号 */
} = 0x31230000;            /*程序编号 */

使用随Sun RPC软件包提供的程序来编写这个说明书文件,程序是rpc_gen

#include "square.h"//由rpcgen产生的square.h
int main(int argc, char **argv)
{//声明客户句柄CLIENT     *cl;square_in   in;square_out   *outp;if (argc != 3)err_quit("usage: client <hostname> <integer-value>");//获取客户句柄cl = clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, "tcp");//调用远程过程并输出结果in.arg1 = atol(argv[2]);if ( (outp = squareproc_1(&in, cl)) == NULL)err_quit("%s", clnt_sperror(cl, argv[1]));printf("result: %ld\n", outp->res1);exit(0);
}

square.x说明文件中,称远程为SQUAREPROC,客户程序中称squareproc_1。约定:.x文件中的名字转换成小写字母形式,添上一个底划线后跟版本号。

服务器程序:只编写过程,服务器程序的main函数由rpc_gen程序自动生成。

#include "square.h"
//过程参数
square_out * squareproc_1_svc(square_in *inp, struct svc_req *rqstp)
{static square_out  out;out.res1 = inp->arg1 * inp->arg1;return(&out);
}

此客户-服务器程序没有使用任何显示的网络编程方式。涉及的套接字以及网络I/O的所有细节都由RPC运行时系统来处理。这就是RPC目的:不需要显示的网络编程知识就允许编写分布式应用程序。

构建客户程序可执行文件的步骤:

  1. rpcgen 的-C表示在square.h头文件在生成ANSI C原型。
  2. rpcgen还产生一个称为客户程序存根的源文件square_clnt.c和一个名为square_xdr.c的用来处理XDR数据转换文件。
  3. libunpipc.a是函数库,-lnsl选项指定存放网络支撑函数的系统函数库。

构建服务器程序可执行文件的步骤:


文件square_svc.c中含有服务器程序main函数,构建客户程序生成的含有XDR函数的square_xdr.o文件在服务器程序的构建也需要。

下图显示构建客户-服务器程序所需的文件和步骤。阴影方框必须编写的文件。短划线指出来需要C伪指令#include square.h的那些文件。

下图汇总了一次远程过程调用中通常发生的步骤。

  1. 服务器启动,向所在主机上的端口映射器注册自身。然后客户启动调用clent_create,与服务器主机上的端口映射器联系,找到服务器的临时端口。clent_create函数还建立一个与服务器的TCP连接。

  2. 客户调用一个称为客户程序存根的本地过程(过程名squareproc_1,含有客户程序存根(相要调用真正的服务器过程)的文件由rpcgen产生,名为square_clnt.c)。存根目的在于把有待传递给远程过程的参数打包,转换成某种标准格式,然后构造一个或多个网络消息(集结)。客户程序的各个例程和存根通常调用RPC运行函数库中的函数。

  3. 这些网络消息由客户程序存根发送给远程系统,需要一次陷入本地内核的系统调用(write或sendto)。

  4. 这些网络消息传送到远程系统。运用网络协议TCP或UDP。

  5. 一个服务器程序存根过程一直在远程系统上等待客户的请求。它从这些网络消息中解散出参数。

  6. 服务器程序存根执行一个本地过程调用以激活真正的服务器函数(squareoc_1_svc过程),传递给该函数的参数是它从来自客户的网络消息中解散出来的。

  7. 当服务器过程完成时,它向服务器程序存根返回其返回值。

  8. 服务器程序存根在必要时对返回值进行转换,然后把它们集结到一个或多个网络消息中,以
    便发送回客户。

  9. 这些消息通过网络传送回客户。

  10. 客户程序存根从本地内核中读出这些网络消息(例如read或recvfrom)。

  11. 对返回值进行可能的转换后,客户程序存根最终返回客户函数。这一步看起来像是一个普通的过程返回客户。

四、多线程化

下列程序Sun RPC默认提供一个迭代服务器。

服务器程序:输出所在线程的线程ID,睡眠5秒钟,再输出自己的线程ID然后返回。

#include "square.h"
square_out * squareproc_1_svc(square_in *inp, struct svc_req *rqstp)
{static square_out  out;printf("thread %ld started, arg = %ld\n",pr_thread_id(NULL), inp->arg1);sleep(5);out.res1 = inp->arg1 * inp->arg1;printf("thread %ld done\n", pr_thread_id(NULL));return(&out);
}

客户端程序:

#include "square.h"int main(int argc, char **argv)
{CLIENT     *cl;square_in   in;square_out   *outp;if (argc != 3)err_quit("usage: client <hostname> <integer-value>");cl = clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, "tcp");in.arg1 = atol(argv[2]);if ( (outp = squareproc_1(&in, cl)) == NULL)err_quit("%s", clnt_sperror(cl, argv[1]));printf("result: %ld\n", outp->res1);exit(0);
}

启动服务器,运行客户程序三次:

每个客户输出各自的结果时彼此间有5秒钟的等待发生。服务器输出:

发现各个客户请求是迭代地处理的:处理第一个客户请求后,接着处理第二个客户的请求处理完毕,最后处理第三个客户的请求直到处理完毕。单个线程处理所有的客户请求:默认情况下服务器并不多线程化。

Sun RPC提供了多线程化的服务器的,通过向rpcgen指定一个-M命令行选项启用。这使由rpcgen产生的服务器代码变得线程安全。另一个选项-A是让服务器根据新客户请求的需要自动创建线程。运行rpcgen时,同时使用这两个选项。
例:

square.x文件:版本号从1改为2,服务器过程的参数结构的声明都不变。

struct square_in {long   arg1;
};struct square_out {long   res1;
};program SQUARE_PROG {version SQUARE_VERS {square_out  SQUAREPROC(square_in) = 1;/* 程序编号=1 */} = 2;             /* 版本号=2 */
} = 0x31230000;            /* 程序编号=0x31230000*/

客户端程序:

#include "square.h"int main(int argc, char **argv)
{CLIENT     *cl;square_in   in;//声明存放结果的变量square_out    out;if (argc != 3)err_quit("usage: client <hostname> <integer-value>");cl = clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, "tcp");//过程调用的新参数in.arg1 = atol(argv[2]);if (squareproc_2(&in, &out, cl) != RPC_SUCCESS)err_quit("%s", clnt_sperror(cl, argv[1]));printf("result: %ld\n", out.res1);exit(0);
}

服务器程序:输出自己所在线程的ID,睡眠5秒钟,输出另一个消息,然后返回。

#include "square.h"//新的参数和返回值
bool_t squareproc_2_svc(square_in *inp, square_out *outp, struct svc_req *rqstp)
{printf("thread %ld started, arg = %ld\n",pr_thread_id(NULL), inp->arg1);sleep(5);outp->res1 = inp->arg1 * inp->arg1;printf("thread %ld done\n", pr_thread_id(NULL));return(TRUE);
}
//释放XDR内存空间的新函数
int square_prog_2_freeresult(SVCXPRT *transp, xdrproc_t xdr_result,caddr_t result)
{xdr_free(xdr_result, result);return(1);
}

同时运行客户程序的三个副本:

三个结果是一个紧接一个地输出的,服务器使用三个线程,同时运行的:

五、服务器捆绑

RPC服务器总是先捆绑一个临时端口,再向端口映射器注册自己的临时端口。当一个客户启动时,必须首先跟服务器主机上的端口映射器联系,询问服务器的临时端口号,然后跟这个临时端口上的服务器联系。rpcinfo -p查看端口映射器注册的所有的RPC程序。

六、inetd和RPC服务器

默认情况下,由rpcgen创建的服务器可由inetd超级服务器激活。

七、认证

默认情况下,RPC请求中没有标识客户的信息,服务器回答客户的请求时并不关心客户是谁,这称为空认证或AUTH_NONE。下一个认证级称为Unix认证或AUTH_SYS。客户必须告诉RPC运行时系统随每个请求携带其身份信息。

八、超时和重传

Sun RPC使用了两个超时值:

  1. 总超时: 一个客户等待其服务器的应答的总时间量。TCP和UDP都使用该值。

  2. 重试超时: 只用于UDP,是一个客户在等待其服务器的应答期间每次重传请求的间隔时间。

创建客户句柄后,调用clnt_control查询或设置影响该句柄的选项。

#include<rpc/rpc.h>
bool_t clnt_control(CLIENT *cl,unsigned int request,char *ptr);
//cl:句柄;
//ptr:指向的内容取决于request;
//成功为true;出错false

square.x文件:

struct square_in {long   arg1;
};struct square_out {long   res1;
};program SQUARE_PROG {version SQUARE_VERS {square_out  SQUAREPROC(square_in) = 1;/* procedure number = 1 */} = 1;               /* version number = 1 */
} = 0x31230000;            /* program number = 0x31230000 */

客户端程序:查询并输出两个RPC超时值。

#include "square.h"
int main(int argc, char **argv)
{CLIENT     *cl;square_in   in;square_out   *outp;struct timeval    tv;
//协议是一个命令行选项if (argc != 4)err_quit("usage: client <hostname> <integer-value> <protocol>");cl = clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, argv[3]);
//取得总超时clnt_control(cl, CLGET_TIMEOUT, (char *) &tv);printf("timeout = %ld sec, %ld usec\n", tv.tv_sec, tv.tv_usec);//尝试取得重试超时if (clnt_control(cl, CLGET_RETRY_TIMEOUT, (char *) &tv) == TRUE)printf("retry timeout = %ld sec, %ld usec\n", tv.tv_sec, tv.tv_usec);in.arg1 = atol(argv[2]);if ( (outp = squareproc_1(&in, cl)) == NULL)err_quit("%s", clnt_sperror(cl, argv[1]));printf("result: %ld\n", outp->res1);exit(0);
}

服务器程序:

#include "square.h"square_out * squareproc_1_svc(square_in *inp, struct svc_req *rqstp)
{static square_out  out;printf("thread %ld started, arg = %ld\n",pr_thread_id(NULL), inp->arg1);//保证请求超时sleep(10000);out.res1 = inp->arg1 * inp->arg1;printf("thread %ld done\n", pr_thread_id(NULL));return(&out);
}

九、TCP连接管理

一个客户的TCP连接或者通过调用clnt_destroy显示的终止,或者由客户进程的终止隐式地终止。

#icnlude<rpc/rpc.h>
void clnt_destroy(CLIENT *cl);

十、事务ID

超时和重传策略的另一部分是使用事务ID 即 XID 来标识客户话求和服务器应答的。当一个客户发出一个RPC调用时,RPC运行时系统给这个调用赋 一 个32位整数XID ,该值伴随RPC消息发送。服务器必须伴随其应答返回这个XID。RPC运行时系统重传一 个请求时XID并不改变。使用XID的目的有两个。

  1. 客户验证应答的XlD等于早先随请求发送的XlD, 否则的话客户忽略这个应答。如果使用的是TCP协议,那么客户收到XID不正确的应答的机会非常罕见,然而如果使用的是UDP协议,而且存在重传请求的可能,网络也易于丢失分组,那么接收到XID不正确的应答是绝对可能的。

  2. 服务器允许维护一个存放已发送应答的高速缓存,而用于确定一个请求是否为一个重复请求的条目之一是XID。

十一、服务器重复请求高速缓存

使RPC运行时系统维护一个重复请求高速缓存,服务器必须调用此函数,一旦启用这个高速缓存,没有办法关闭。

#include<rpc/rpc.h>
int svc_dg_enavlecache(SVCXPRT *xprt,unsigned long size);
//xptr:传输句柄,该指针是svc_req结构的一个成员。
//size:需为之分配内存空间的高速缓存项数。
//返回成功为0,错误-1。

启用高速缓存后,服务器便为它所发送的全部应答维护一个FIFO高速缓存。每个应答是由如下信息唯一标识的:

  1. 程序号;
  2. 版本号;
  3. 过程号;
  4. XID;
  5. 客户地址;
    每当服务器中的RPC运行时系统接收到一个客户请求时首先会搜索重复请求高速缓存,看其中是否已有该请求的一个应答。若有,这个高速缓存的应答就返回给客户,而不再调用相应的服务器过程。

重复请求高速缓存的目的:当接收到每个服务器过程的多个重复请求时,避免多次调用该服务器过程,因为该过程也许表示等势的。在网络中接收到重复请求的可能原因是应答丢失或者客户重传请求超前于应答的接收。重复请求只适用于UDP这样的数据报协议,使用TCP协议时应用绝对看不到重复的请求,请求的重复问题由TCP处理的。

十二、客户或服务器的过早终止

1.服务器的过早终止

当服务器线程终止时,与客户的TCP连接并未关闭,仍然在服务器进程中保持打开。因此,服务器主机没有给客户发送FIN,客户仅仅超时。在客户请求已发送给服务器,并且服务器主机的TCP已确认该请求后,若服务器主机崩溃,会出现同样情况。

2.客户的过早终止

当一个RPC客户在其使用TCP的某个RPC过程调用仍在进展期间终止时,客户主机的TCP将向服务器主机的TCP发送一个FIN。服务器的RPC运行时系统能否检测到这个条件,从而可能向服务器过程发出通知。

在客户端发生的情况是我们预期的,但在服务器端没有发生任何特殊之事。服务器过程结束其6秒钟的睡眠后返回。tcpdump查看发生的情况:

  1. 当客户终止时,客户主机的TCP向服务器的TCP发送一个FIN。服务器主机的TCP对它作了确认。TCP称这个过程为半关闭。

  2. 客户和服务器启动约6秒钟时,服务器发送其应答,该应答由服务器主机的TCP发送给看客户。客户主机的TCP响应以一个RST分节,因为客户进程已经终止。服务器下一次在这个连接上读或写时将认识到这个现状。

使用UDP的RPC客户和服务器永远不知道对方是否过早终止。当接收不到响应时,它们可能超时,不过无法分辨错误类型:进程过早终止、对方主机崩溃、网络不可达,等等。

使用TCP的客户和服务器检测出对方所存在问题的机会要大得多,因为对方进程的过早终止自动导致对方主机的TCP关闭其所在端的连接。但是如果对方是一个线程化的服务器,这一点就不起作用,因为对方线程的终止并不会关闭其所在端的连接。另外这一点也无助于检测对方主机的崩溃,因为发生这种情况时,对方主机的TCP并没关闭它的打开着的连接。为处理所有这些情形,超时机制仍然是必需的。

十三、XDR:外部数据表示

Sun RPC使用XDR即外部数据表示标准来描述和编码数据。XDR既是一种描述数据的语言,又是一组用于编码数据的规则。XDR使用隐式类型指定公式,意味着发送者和接收者都得知道数据的类型和字节序:例如32位整数值后跟一个单精度浮点数值,再跟一个字符串。
所有数据类型的XDR表示都需要4的倍数的字节数,这些字节总是以大端字节序传送的。带符号整数值使用二进制补码记法存放,浮点数值则使用 IEEE格 式 存 放 。可变长度字段总是在其末端含有最多 3个字节的填 充 ,这 样下 一 个条目总是落 在 某 个4字 节 的边界。例如 一 个5字 节 的A SC II字 符 串将 作 为 12个字 节 来传送 :

  1. 一个4字节的整数计数,其值为5;
  2. 5字 节 的字 符 串本 身 ;
  3. 3个字节的值为0的填充。

十四、例子:链表处理

使用XDR进行编码和解码含有可变数目元素的链表,下列使用名-值对链表。

opt.x文件:

struct mylist {string    name<>;long       value;mylist    *next;
};struct args {mylist   *list;
};

rpcgen根据opt.x生成的.h文件:

struct mylist {char *name;long value;struct mylist *next;
};
typedef struct mylist mylist;struct args {mylist *list;
};
typedef struct args args;

#include    "opt2.h"int main(int argc, char **argv)
{int        i;XDR       xhandle;long    *lptr;args  out;                /* 填充的结构 */char *buff;              /* XDR编码结果 */mylist nameval[4];         /* 最多4个列表项 */size_t size;out.list = &nameval[2];       /* [2] -> [1] -> [0] */nameval[2].name = "name1";nameval[2].value = 0x1111;nameval[2].next = &nameval[1];nameval[1].name = "namee2";nameval[1].value = 0x2222;nameval[1].next = &nameval[0];nameval[0].name = "nameee3";nameval[0].value = 0x3333;nameval[0].next = NULL;buff = malloc(BUFFSIZE); /* 必须在4字节边界上对齐*/xdrmem_create(&xhandle, buff, BUFFSIZE, XDR_ENCODE);if (xdr_args(&xhandle, &out) != TRUE)err_quit("xdr_args error");size = xdr_getpos(&xhandle);lptr = (long *) buff;for (i = 0; i < size; i += 4)printf("%8lx\n", (long) ntohl(*lptr++));exit(0);
}

十五、RPC分组格式

下图显示了封装在一个TCP分节中的一个RPC请求格式:

  1. 既然TCP是一个字节流,不提供消息边界,因此应用程序必须提供界定各个消息的某种方法。
  2. Sun RPC定义了既可作为请求也可作为应答的记录,每个记录由一个或多个片段构成。
  3. 每个片段以一个4字节值开头:其中最高位是晕终片段的标志,低序31位是计数。如果录终片段标志位为0,那么构成当前记录的还有别的片段。

如果所用的是UDP而不是TCP,那么紧跟在UDP首部之后的第一个字段是XID,如下图所示。

下图展示了RPC应答的各种可能:


下图展示了一个成功的RPC应答格式,不过这次封装在一个UDP数据报中。

远程调用-Sun RPC相关推荐

  1. 远程调用服务(RPC)和消息(Message Queue)对比及其适用/不适用场合

    在阿里的平台技术部参与开发了Dubbo(远程调用服务)和Napoli(消息解决方案),又给网站应用支持这2个产品很长一段时间,了解了这2个产品的实现及应用对这两个产品的用法. 大部分情况下," ...

  2. java socket 远程调用_使用Socket反射Java流操作进行方法的远程调用(模拟RPC远程调用)...

    写在前面 阅读本文首先得具备基本的Socket.反射.Java流操作的基本API使用知识:否则本文你可能看不懂... 服务端的端口监听 进行远程调用,那就必须得有客户端和服务端.服务端负责提供服务,客 ...

  3. springboot整合rpc远程调用_SpringCloud—RPC远程调用

    Eureka介绍 Spring Cloud Eureka 是对Netflix公司的Eureka的二次封装,它实现了服务治理的功能,Spring Cloud Eureka提供服务端与客户端,服务端即是E ...

  4. 戴尔显示rpc服务器,RakNet4远程调用(RPC)--局域网对战基础

    RakNet是一款很优秀的可移植的网络引擎.以高效率著称. 在一般的局域网游戏中(比如CS.魔兽争霸),需要玩家建立一个主机, 然后在局域网内其他玩家发现这个主机,加入主机后玩家会在一个房间里.这时候 ...

  5. 远程调用中间件RPC

    RPC 是指计算机 A 上的进程,调用另外一台计算机 B 上的进程,其中 A 上的调用进程被挂起,而 B 上的被调用进程开始执行,当值返回给 A 时,A 进程继续执行.调用方可以通过使用参数将信息传送 ...

  6. 徒手撸框架--实现 RPC 远程调用

    微服务,已经是每个互联网开发者必须掌握的一项技术.而 RPC 框架,是构成微服务最重要的组成部分之一.趁最近有时间.又看了看 dubbo 的源码.dubbo 为了做到灵活和解耦,使用了大量的设计模式和 ...

  7. 提交响应后无法调用sendredirect_微服务的那些事(三),微服务的远程调用方式。RPC和HTTP...

    2.远程调用方式 无论是微服务还是SOA,都面临着服务间的远程调用.那么服务间的远程调用方式有哪些呢? 常见的远程调用方式有以下几种: RPC:Remote Produce Call远程过程调用,类似 ...

  8. 【Android Binder 系统】一、Binder 系统核心 ( IPC 进程间通信 | RPC 远程调用 )

    文章目录 一.Binder 系统两个核心 二.IPC 进程间通信 三.RPC 远程过程调用 一.Binder 系统两个核心 Binder 系统 最重要的两个核心是 IPC 和 RPC ; IPC ( ...

  9. 整合rpc远程调用_远程过程调用(RPC)

    分布式系统的主要特点是能够将一台机器上的一个任务分解到系统中其他的机器上运行,实现多个CPU的协同工作.远程过程调用RPC就是实现这一特点的有效方法之一 1.什么是RPC RPC的基本思想 (1984 ...

最新文章

  1. 福大计算机国二,福大学子喜获中国大学生计算机设计大赛二三等奖
  2. Google Quest 冠军访谈:3个秘诀,8条建议,还有人在华为做 NLP 研究员
  3. 区块链去中心化的生命之源:“DPOS(委托权益证明)共识机制”
  4. php 微信机器人_微信小程序机器人自动客服功能
  5. 这个24岁北航博士刚毕业就受聘211大学副教授,他大一就保研,学术能力太牛了.........
  6. 陕西师范大学 渝粤教育 《学前儿童数学教育》作业
  7. python的urllib2模块
  8. MATLAB 中RMSE和MAPE的计算方法
  9. Github|基于 Jittor 的 GAN 模型库
  10. HTTP Status 500 - {msg=SolrCore 'collection1' is not available due to init failure: Could not load c
  11. volatile简记
  12. 细数那些不能直视的IE6BUG
  13. LRC软件测试简历,C语言 LRC歌词文件解析
  14. Mercurial使用简单介绍zz
  15. 概率论与数据统计在分类预测中的原理介绍(信息增益、交叉熵等)
  16. 【SVN】SVN版本回退与常用命令总结
  17. Labelme转VOC格式
  18. qq红包代码加群链接
  19. 小程序源码:全新动态视频壁纸-多玩法安装简单
  20. Java清空文件内容

热门文章

  1. Framer X for Mac(交互设计工具)
  2. 如何在C ++中解析文件
  3. 【PAT甲级】1146 Topological Order
  4. cartographer中分支定界法理解——为什么能保证上界
  5. (入门)使用ab进行压力测试
  6. RHCSA——第四天
  7. 卷积神经网络和循环神经网络的思想内核是什么
  8. Java正则表达式校验密码规则
  9. BGP协议基础配置—学习
  10. 【GO】map转json