开启 Kerberos 安全认证的大数据环境中如何正确指定 HS2 的 jdbc url 地址?

news2024/10/7 16:18:33

开启 Kerberos 安全认证的大数据环境中如何正确指定 HS2 的 jdbc url 地址?

1 Kerberos 环境中 HS2 的认证方式概述

大家知道,HIVE 的认证方式可以通过参数 hive.server2.authentication 在服务端进行统一配置,而在开启了 Kerberos 安全认证的大数据环境中:

  • 我们可以配置 hive.server2.authentication=kerberos,代表配置 HS2 使用 Kerberos安全认证;
  • 我们可以配置 hive.server2.authentication=ldap,代表配置 HS2 使用 Kerberos和LDAP的双重认证。

更多详细信息,可以查看前期博文《大数据生态安全框架的实现原理与最佳实践》。

2 Kerberos 认证模式下HS2 的 jdbc url 地址格式

在 HS2 的 Kerberos 认证模式下(hive.server2.authentication=kerberos),HS2 的 jdbc url 地址格式为 jdbc:hive2://:/;principal=<Server_Principal_of_HiveServer2>,其中 hs2地址部分的host可以指定为具体域名或Ip,principal部分的 Server_Principal_of_HiveServer2可以指定为具体域名,具体IP或特殊字符“_HOST”,所以所有排列组合下的可能值如下:

  • beeline -u “jdbc:hive2://uf30-1:10000/default;principal=hive/uf30-1@xxx.com”;
  • beeline -u jdbc:hive2://100.116.3.228:10005/default;principal=hive/192.168.71.70@xxx.com";
  • beeline -u “jdbc:hive2://uf30-1:10000/default;principal=hive/_HOST@xxx.com”;
  • beeline -u “jdbc:hive2://192.168.71.70:10000/default;principal=hive/uf30-1@xxx.com”;
  • beeline -u “jdbc:hive2://100.116.3.228:10005/default;principal=hive/192.168.71.70@xxx.com”;
  • beeline -u “jdbc:hive2://100.116.3.228:10005/default;principal=hive/_HOST@xxx.com”;

3 Kerberos 认证模式下因 HS2 jdbc url 地址格式使用错误带来的常见问题

在 Kerberos 认证模式下,HS2 jdbc url 地址格式中 hs2地址部分的host可以指定为具体域名或具体IP,principal部分的 Server_Principal_of_HiveServer2可以指定为具体域名,具体IP或特殊字符“_HOST”,所以所有排列组合下的可能值共有6种;在具体使用过程中,因为各个环境中 kerberos kdc 中配置的差异,很容易出现连接失败问题,某次连接失败时详细的报错日志如下:

Connecting to jdbc:hive2://192.168.71.70:10000/default;principal=hive/_HOST@CDH.COM
23/05/04 15:06:01 [main]: ERROR transport.TSaslTransport: SASL negotiation failure
javax.security.sasl.SaslException: GSS initiate failed
        at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:211) ~[?:1.8.0_181]
        at org.apache.thrift.transport.TSaslClientTransport.handleSaslStartMessage(TSaslClientTransport.java:94) ~[hive-exec-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.thrift.transport.TSaslTransport.open(TSaslTransport.java:271) [hive-exec-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.thrift.transport.TSaslClientTransport.open(TSaslClientTransport.java:37) [hive-exec-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hadoop.hive.thrift.client.TUGIAssumingTransport$1.run(TUGIAssumingTransport.java:52) [hive-exec-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hadoop.hive.thrift.client.TUGIAssumingTransport$1.run(TUGIAssumingTransport.java:49) [hive-exec-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_181]
        at javax.security.auth.Subject.doAs(Subject.java:422) [?:1.8.0_181]
        at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1875) [hadoop-common-3.0.0-cdh6.3.2.jar:?]
        at org.apache.hadoop.hive.thrift.client.TUGIAssumingTransport.open(TUGIAssumingTransport.java:49) [hive-exec-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.jdbc.HiveConnection.openTransport(HiveConnection.java:229) [hive-jdbc-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.jdbc.HiveConnection.<init>(HiveConnection.java:184) [hive-jdbc-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.jdbc.HiveDriver.connect(HiveDriver.java:107) [hive-jdbc-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at java.sql.DriverManager.getConnection(DriverManager.java:664) [?:1.8.0_181]
        at java.sql.DriverManager.getConnection(DriverManager.java:208) [?:1.8.0_181]
        at org.apache.hive.beeline.DatabaseConnection.connect(DatabaseConnection.java:145) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.DatabaseConnection.getConnection(DatabaseConnection.java:209) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.Commands.connect(Commands.java:1617) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.Commands.connect(Commands.java:1512) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_181]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_181]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_181]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_181]
        at org.apache.hive.beeline.ReflectiveCommandHandler.execute(ReflectiveCommandHandler.java:56) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.BeeLine.execCommandWithPrefix(BeeLine.java:1290) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.BeeLine.dispatch(BeeLine.java:1329) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.BeeLine.connectUsingArgs(BeeLine.java:864) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.BeeLine.initArgs(BeeLine.java:768) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.BeeLine.begin(BeeLine.java:1004) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.BeeLine.mainWithInputRedirection(BeeLine.java:526) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at org.apache.hive.beeline.BeeLine.main(BeeLine.java:508) [hive-beeline-2.1.1-cdh6.3.2.jar:2.1.1-cdh6.3.2]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_181]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_181]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_181]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_181]
        at org.apache.hadoop.util.RunJar.run(RunJar.java:313) [hadoop-common-3.0.0-cdh6.3.2.jar:?]
        at org.apache.hadoop.util.RunJar.main(RunJar.java:227) [hadoop-common-3.0.0-cdh6.3.2.jar:?]
Caused by: org.ietf.jgss.GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7) - LOOKING_UP_SERVER)
        at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:770) ~[?:1.8.0_181]
        at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248) ~[?:1.8.0_181]
        at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179) ~[?:1.8.0_181]
        at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:192) ~[?:1.8.0_181]
        ... 36 more
Caused by: sun.security.krb5.KrbException: Server not found in Kerberos database (7) - LOOKING_UP_SERVER
        at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:73) ~[?:1.8.0_181]
        at sun.security.krb5.KrbTgsReq.getReply(KrbTgsReq.java:251) ~[?:1.8.0_181]
        at sun.security.krb5.KrbTgsReq.sendAndGetCreds(KrbTgsReq.java:262) ~[?:1.8.0_181]
        at sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:308) ~[?:1.8.0_181]
        at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:126) ~[?:1.8.0_181]
        at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:458) ~[?:1.8.0_181]
        at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:693) ~[?:1.8.0_181]
        at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248) ~[?:1.8.0_181]
        at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179) ~[?:1.8.0_181]
        at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:192) ~[?:1.8.0_181]
        ... 36 more
Caused by: sun.security.krb5.Asn1Exception: Identifier doesn't match expected value (906)
        at sun.security.krb5.internal.KDCRep.init(KDCRep.java:140) ~[?:1.8.0_181]
        at sun.security.krb5.internal.TGSRep.init(TGSRep.java:65) ~[?:1.8.0_181]
        at sun.security.krb5.internal.TGSRep.<init>(TGSRep.java:60) ~[?:1.8.0_181]
        at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:55) ~[?:1.8.0_181]
        at sun.security.krb5.KrbTgsReq.getReply(KrbTgsReq.java:251) ~[?:1.8.0_181]
        at sun.security.krb5.KrbTgsReq.sendAndGetCreds(KrbTgsReq.java:262) ~[?:1.8.0_181]
        at sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:308) ~[?:1.8.0_181]
        at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:126) ~[?:1.8.0_181]
        at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:458) ~[?:1.8.0_181]
        at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:693) ~[?:1.8.0_181]
        at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248) ~[?:1.8.0_181]
        at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179) ~[?:1.8.0_181]
        at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:192) ~[?:1.8.0_181]
        ... 36 more
23/05/04 15:06:01 [main]: WARN jdbc.HiveConnection: Failed to connect to 192.168.71.70:10000
Unknown HS2 problem when communicating with Thrift server.
Error: Could not open client transport with JDBC Uri: jdbc:hive2://192.168.71.70:10000/default;principal=hive/_HOST@CDH.COM: GSS initiate failed (state=08S01,code=0)

  • 查看kdc 服务端日志可见”TGS_REQ (1 etypes {17}) 192.168.71.70: LOOKING_UP_SERVER: authtime 0, dap@CDH.COM for hive/192.168.71.70@CDH.COM, Server not found in Kerberos database“:

  • 上述hs2客户端与kdc服务端日志的核心报错信息是 “Server not found in kerberos database (7) - LOOKING_UP_SERVER",其含义是 kdc 数据库中没有指定的Server_Principal_of_HiveServer2(hive/192.168.71.70@CDH.COM)对应的server;

  • 通过命令 kadmin.local -q “listprincs” |egrep -i ‘hive|dap’ 查看 kdc数据库,可见确实如此(KDC数据库中指定的都是具体的域名而不是IP):

4 技术背景与问题解决

  • KDC包括 Authentication Server(AS),也包括 Ticket Granting Server(TGS);KDC数据库中包括了 user/group/service/computer相关信息;KDC 可以响应客户端的AS_REQ请求(once per user login session),也可以响应客户端的TGS_REQ(once per type of service);
  • kerberos认证模式下,客户端创建到hs2的JDBC连接时,KDC首先会通过 AS_REQ和 AS_REP校验用户身份(用户需要通过密码或keytab文件的方式提供身份凭证,如kinit user_name -kt xx.keytab);在用户身份校验通过后,KDC 会通过 TGS_REQ 和 TGS_REP 申请 service ticket (用户需要在JDBC URL 中指定 Server_Principal_of_HiveServer2);
  • 如果用户在 hs2 JDBC URL 中指定 Server_Principal_of_HiveServer2 使用域名或IP,则 HS2 会以用户提供的信息直接发起 TGS_REQ 请求;
  • 如果用户在 hs2 JDBC URL 中指定 Server_Principal_of_HiveServer2 为特殊字符”_HOST“,则 HS2 首先会将特殊字符”_HOST“ 替换为 hs2 JDBC URL 地址中 hs2 host的域名或IP地址,然后再向KDC发起 TGS_REQ 请求;
  • 如果 KDC 数据库中没有对应域名或IP的 principal,就会报错:Server not found in Kerberos database (7) - LOOKING_UP_SERVER;
  • The special string _HOST in the principal will be replaced automatically with the correct host name by hs2 before issue TGS_REQ;
  • Kerberos principals are traditionally defined with hostnames of the form hbase@worker3/EXAMPLE.COM, not hbase/10.10.15.1/EXAMPLE.COM,The issue of whether Hadoop should support IP addresses has been raised HADOOP-9019 & HADOOP-7510 Current consensus is no: you need DNS set up, or at least a consistent and valid /etc/hosts file on every node in the cluster.

知道了相关技术背景后,上述Server not found in Kerberos database (7) - LOOKING_UP_SERVER问题的解决方法就很清晰了:

  • 不使用特殊字符_HOST:此时在hs2 jdbc url中指定 Server_Principal_of_HiveServer2 使用hs2节点的域名而不是IP(此时hs2 host可以使用具体域名或IP);
  • 使用特殊字符_HOST:此时在hs2 jdbc url中指定 Server_Principal_of_HiveServer2 使用特殊字符 _HOST, 并指定hs2 host节点使用具体域名而不是IP;

5 相关源码:

- org.apache.hive.service.auth.KerberosSaslHelper#createSubjectAssumedTransport
- org.apache.hive.service.auth.KerberosSaslHelper#getKerberosTransport
- org.apache.hive.jdbc.HiveConnection#createBinaryTransport
- org.apache.hive.jdbc.HiveConnection#HiveConnection(java.lang.String, java.util.Properties, org.apache.hive.jdbc.saml.IJdbcBrowserClientFactory) 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Metasploit Framework

简介 目前最流行、最强大、最具扩展性的渗透测试平台软件基于Metasploit进行渗透测试和漏洞分析的流程和方法 2003年由HDMore发布第一版&#xff0c;2007年用 ruby 语言重写 框架集成了渗透测试标准(PETS)思想一定程度上统一了渗透测试和漏洞研究的工作环境新的攻击代码可以比较…

若依切换数据源失败

1.遇到的问题 我们使用若依框架&#xff0c;首先这个框架完全开源&#xff0c;真的是很牛也很友好的框架。不像芋道各种关注各种收费&#xff0c;看个文档还要加入199的知识星球&#xff0c;真是恶心&#xff0c;个人觉得完全没必要&#xff0c;若依提供的扩展项目中都做了集成…

电商后台架构演变

单机架构 在网站最初时&#xff0c;应用数量与用户数都较少&#xff0c;可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时&#xff0c;首先经过DNS服务器&#xff08;域名系统&#xff09;把域名转换为实际IP地址10.102.4.1&#xff0c;浏览器转而…

原生微信小程序开发-获取时区名称

参考以下文章&#xff1a;jstz - npmTimezone detection for JavaScript. Latest version: 2.1.1, last published: 5 years ago. Start using jstz in your project by running npm i jstz. There are 55 other projects in the npm registry using jstz.https://www.npmjs.co…

MOTOROLA MVME5500 数字量控制模块

Motorola MVME5500 是一款嵌入式计算平台&#xff0c;不是数字量控制模块&#xff0c;而是用于嵌入式计算和控制的计算机硬件。通常情况下&#xff0c;它被用于工业自动化、控制系统、数据采集以及嵌入式应用中。以下是 Motorola MVME5500 的一般功能和特点&#xff1a; 计算性…

2023年下半年软考高级系统架构设计师论文指南(收藏)

由于今年下半年软考改为了机考&#xff0c;所以今年是看大家码字的速度了&#xff0c;但是好处还是有的&#xff0c;错了还能删除&#xff0c;之前纸质的 还有点不方便。 1、选择题目 &#xff08;1&#xff09;控制选题的时间。不要浪费太多时间在纠结选题上面。 &#xff…

9.为算法中的特定行添加注释

在 algorithm2e 宏包中&#xff0c;您可以使用 \tcp{} 命令来为算法中的特定行添加注释。这个命令会在算法伪代码中的某一行的末尾添加注释文本。 以下是一个\tcp 示例&#xff0c;演示如何在算法中添加注释&#xff1a; \documentclass{article} \usepackage[linesnumbered,…

MySQL-树型结构数据查询

表结构 进行树形结构查询&#xff0c;利用特殊语法进行操作 with recursive t as(select parent_id , business_namefrom business_line where id 21union allselect a.parent_id, a.business_namefrom business_line a join t on a.id t.parent_id) select business_name f…

四川天蝶电子商务有限公司可靠吗?真实吗?

​近年来&#xff0c;抖音带货已成为一种日益流行的新零售趋势。而四川天蝶电子商务有限公司作为抖音带货领域的先行者之一&#xff0c;备受消费者关注。但是&#xff0c;我们不能仅凭其名声来评判其可靠性。在对该公司的抖音带货服务进行评估之前&#xff0c;有必要了解和了解…

代码随想录 Day5 哈希表1 T242 相同字母的异序词 T349两个数组的交集 T202 快乐数 T1 两数之和

本文详细解答和思路来自于: 代码随想录 (programmercarl.com) 前导知识 - 哈希表 定义:哈希表是根据关键码的值而直接进行访问的数据结构.所谓的关键码就是下标实际上数组就是一张哈希表. 那么哈希表有什么作用呢?哈希表可以以O(1)的性能迅速定位你要找的元素,相较于依次遍…

代码随想录第34天 | 343. 整数拆分 96.不同的二叉搜索树

343. 整数拆分 /*** param {number} n* return {number}*/ var integerBreak function(n) {let dpnew Array(n1)dp.fill(1)for(let i3;i<n;i)for(let j1;j<i/2;j){dp[i] max(dp[i],max(j*(i-j),dp[i-j]*j))} function max(a,b){return a>b?a:b }return dp[n] }; 想…

四、初识FreeRTOS之FreeRTOS移植

一、获取FreeRTOS&#xff08;熟悉&#xff09; 1.1 源码获取&#xff1a; FreeRTOS官网&#xff1a;https://www.freertos.org/,本人所使用的例程为FreeRTOS的V10.4.6版本。 进入后点击下载FreeRTOS&#xff0c;选择“FreeRTOS 202112.00”文件&#xff0c;下载完成后解压到…

企业资源计划即 ERP (Enterprise Resource Planning)

ERP是一种主要面向制造行业进行物质资源、资金资源和信息资源集成一体化管理的企业信息管理系统。ERP是一个以管理会计为核心可以提供跨地区、跨部门、甚至跨公司整合实时信息的企业管理软件。针对物资资源管理&#xff08;物流&#xff09;、人力资源管理&#xff08;人流&…

(09_22)【有奖体验】轻点鼠标,让古籍数字化“重生_

卷帙浩繁的古籍是古典文化的载体&#xff0c;珍贵的古籍往往很难轻易示人&#xff0c;数字化是解决古籍‘藏’与‘用’之间矛盾的最好方式&#xff0c;函数计算联合开发者宋杰开发“古籍识别“应用&#xff0c;希望更多开发者行动起来&#xff0c;用Serverless AI 让古籍“活”…

upload-labs靶场未知后缀名解析漏洞

upload-labs靶场未知后缀名解析漏洞 版本影响&#xff1a; phpstudy 版本&#xff1a;5.2.17 ​ 1 环境搭建 1.1 在线靶场下载&#xff0c;解压到phpstudy的www目录下&#xff0c;即可使用 https://github.com/c0ny1/upload-labs1.2 已启动&#xff1a;访问端口9000&…

阿里云Stable Diffusion操作教程

大家好,我是雄雄,欢迎关注微信公众号:雄雄的小课堂 前言 Stable Diffusion是⼀种深度学习模型,主要⽤于将⽂本描述转化为详细的图像,也可以应⽤于其他图像处理任务 。 这个模型由创业公司Stability AI 与学术研究者合作开发,使⽤了⼀种称为潜在扩散模型(LDM)的扩散模型…

测试15k薪资第1步 —— 自动化测试理论基础

目录 1、自动化测试定义 2、自动化测试分类&工具 3、未来发展趋势 1.1、什么是自动化测试 自动化测试指的是利用软件工具或脚本来执行测试任务&#xff0c;以替代手动测试过程的一种测试方法。它的主要目的是通过自动化执行、验证和评估软件应用的功能、稳定性、性能等方面…

SpringBoot 学习(九)Redis

11. 集成 Redis 11.1 说明 SpringBoot 操作数据&#xff1a;sping-data、jpa、jdbc、mongodb、redis SpringBoot 2. 后&#xff0c;jedis 被替换为 lettuce jedis&#xff1a;采用直连&#xff0c;多线程操作不安全&#xff0c;增强安全性需使用 jedis pool 连接池&#xff0…

hanoi塔问题

汉诺塔 5层攻略31步_哔哩哔哩_bilibili 问题描述&#xff1a; n阶Hanoi塔问题&#xff0c;假设有3个分别命名为A、B、C塔座&#xff0c;在塔座A上插有n个直径大小各不相同、依小到大的圆盘。现要求将A轴上的n个圆盘移动至塔座C上并按同样顺序叠排&#xff0c;圆盘移动时必须遵…

《计算机网络》——应用层

2.1 应用层协议原理&#xff08;P54&#xff09; 研发网络应用的核心是写出能够运行在不同端系统和通过网络彼此交流的程序。 2.1.1 网络应用程序体系结构 两种主流的应用体系结构&#xff1a;客户-服务器体系结构、对等体系结构。 客户-服务器体系&#xff1a;服务器是一个…