百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

RPC框架原理简述:从实现一个简易RPCFramework说起

ccwgpt 2024-09-17 12:49 38 浏览 0 评论

摘要:

本文阐述了RPC框架与远程调用的产生背景,介绍了RPC的基本概念和使用背景,之后手动实现了简易的RPC框架并佐以实例进行演示,以便让各位看官对RPC有一个感性、清晰和完整的认识,最后讨论了RPC框架几个较为重要问题。总之,RPC框架的精髓在于动态代理和反射,通过它们使得远程调用“本地化”,对用户透明且友好。

一. 引子

上学时我们写得应用大都比较简单,基本上都属于单体应用,服务调用也局限于本地,如下所示:

// 服务接口
public interface HelloService {
 String hello(String name);
 String hi(String msg);
}// 服务本地实现
public class HelloServiceImpl implements HelloService{
 @Override
 public String hello(String name) { return "Hello " + name;
 } @Override
 public String hi(String msg) { return "Hi, " + msg;
 }
}// 服务本地调用
public class Main {
 public static void main(String[] args) {
 HelloService helloService = new HelloServiceImpl();
 helloServiceProxy.hello("Panda");
 helloServiceProxy.hi("Panda");
 }/** Output
 hello : Hello rico
 hi : Hi, panda
 **/ }

我们写这样的单体应用来学习、做实验正常且合理,但是在生产环境中,单体应用在各方面的性能上和可维护性方面就远远不能满足需求了。应用内各项业务互相纠缠、耦合性太大,不利于后期的维护和升级,主要表现在以下两点上:

  • 可用性低。所有鸡蛋都放在同一个篮子里,一旦有问题导致单体应用挂掉,所有业务都不能访问,稳定性要求难以满足;
  • 不利于各业务团队进行合作,开发效率低。单体应用各业务耦合度太高,不同业务团队开发进度和实现细节不尽相同,难以高效协作。

将不同的业务拆分到多个应用中,让不同的应用分别承担不同的功能是解决这些问题的必杀技。将不同业务分拆到不同的应用后,不但可以大幅度提升系统的稳定性还有助于丰富技术选型,进一步保证系统的性能。总的来说,从单体应用到分布式多体应用是系统升级必经之路。

当一个单体应用演化成多体应用后,远程调用就粉墨登场了。在一个应用时,相互通信直接通过本地调用就可完成,而变为多体应用时,相互通信就得依赖远程调用了,这时一个高效稳定的RPC框架就显得非常必要了。可能有的同学会觉得,没必要非得用RPC框架啊,简单的HTTP调用不是也可以实现远程通信吗?确实,简单的HTTP调用确实也可以实现远程通信,但是它不是那么的合适,原因有二:

  • RPC远程调用像本地调用一样干净简洁,但其他方式对代码的侵入性就比较强;
  • 一般使用RPC框架实现远程通信效率比其他方式效率要高一些。

当我们踏入公司尤其是大型互联网公司就会发现,公司的系统都由成千上万大大小小的服务组成,各服务部署在不同的机器上,由不同的团队负责。这时就会有两个很关键的问题:

  • 要搭建一个新服务,免不了需要依赖已有的服务,而现在已有的服务都在远端,怎么调用?
  • 其它团队想使用我们的新服务,我们的服务该怎么发布以便他人调用?

下文将对RPC框架的基本原理进行介绍,并对这两个问题展开探讨,同时参考前辈的博文《RPC框架几行代码就够了》手写一个简易RPC框架以加深对PRC原理的理解。

二. RPC 框架介绍

对于多体应用,由于各服务部署在不同机器,服务间的调用免不了网络通信过程,服务消费方每调用一个服务都要写一坨网络通信相关的代码,不仅复杂而且极易出错。如果有一种方式能让我们像调用本地服务一样调用远程服务,而让调用者对网络通信这些细节透明,那么将大大解放程序员的双手,大幅度提高生产力。比如,服务消费方在执行helloService.hi(“Panda”)时,实质上调用的是远端的服务。这种方式其实就是RPC(Remote Procedure Call Protocol),在各大互联网公司中被广泛使用,如阿里巴巴的HSF、Dubbo(开源)、Facebook的Thrift(开源)、Google GRPC(开源)、Twitter的Finagle(开源)等。

RPC的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。要让网络通信细节对使用者透明,我们需要对通信细节进行封装,下面是一个RPC的经典调用的流程,并且反映了所涉及到的一些通信细节:

(1). 服务消费方(client)以本地调用方式调用服务;

(2). client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;

(3). client stub找到服务地址,并将消息发送到服务端;

(4). server stub收到消息后进行解码;

(5). server stub根据解码结果 反射调用 本地的服务;

(6). 本地服务执行并将结果返回给server stub;

(7). server stub将返回结果打包成消息并发送至消费方;

(8). client stub接收到消息,并进行解码;

(9). 服务消费方得到最终结果。

RPC框架就是要将2~8这些步骤封装起来,让用户对这些细节透明,使得远程方法调用看起来像调用本地方法一样。

三. RPC框架简易实现及其实例分析

(1).服务端

服务端提供客户端所期待的服务,一般包括三个部分:服务接口,服务实现以及服务的注册暴露三部分,如下:

  • 服务接口
public interface HelloService {
 String hello(String name);
 String hi(String msg);
}

  • 服务实现
public class HelloServiceImpl implements HelloService{
 @Override
 public String hello(String name) { return "Hello " + name;
 } @Override
 public String hi(String msg) { return "Hi, " + msg;
 }
}

  • 服务暴露:只有把服务暴露出来,才能让客户端进行调用,这是RPC框架功能之一。
public class RpcProvider {
 public static void main(String[] args) throws Exception {
 HelloService service = new HelloServiceImpl(); // RPC框架将服务暴露出来,供客户端消费
 RpcFramework.export(service, 1234);
 }
}

(2).客户端

客户端消费服务端所提供的服务,一般包括两个部分:服务接口和服务引用两个部分,如下:

  • 服务接口:与服务端共享同一个服务接口
public interface HelloService {
 String hello(String name);
 String hi(String msg);
}

  • 服务引用:消费端通过RPC框架进行远程调用,这也是RPC框架功能之一
public class RpcConsumer {
 public static void main(String[] args) throws Exception { // 由RpcFramework生成的HelloService的代理
 HelloService service = RpcFramework.refer(HelloService.class, "127.0.0.1", 1234);
 String hello = service.hello("World");
 System.out.println("客户端收到远程调用的结果 : " + hello);
 }
}

(3).RPC框架原型实现

RPC框架主要包括两大功能:一个用于服务端暴露服务,一个用于客户端引用服务。

  • 服务端暴露服务
 /**
 * 暴露服务
 *
 * @param service 服务实现
 * @param port 服务端口
 * @throws Exception
 */
 public static void export(final Object service, int port) throws Exception { 
 if (service == null) { 
 throw new IllegalArgumentException("service instance == null");
 } 
 if (port <= 0 || port > 65535) { 
 throw new IllegalArgumentException("Invalid port " + port);
 }
 System.out.println("Export service " + service.getClass().getName() + " on port " + port); // 建立Socket服务端
 ServerSocket server = new ServerSocket(port); 
for (; ; ) { 
try { // 监听Socket请求
 final Socket socket = server.accept(); 
new Thread(new Runnable() { 
@Override
public void run() { 
try { 
 try { /* 获取请求流,Server解析并获取请求*/
 // 构建对象输入流,从源中读取对象到程序中
 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); 
try {
 System.out.println("\nServer解析请求 : ");
 String methodName = input.readUTF();
 System.out.println("methodName : " + methodName); // 泛型与数组是不兼容的,除了通配符作泛型参数以外
 Class<?>[] parameterTypes = (Class<?>[])input.readObject();
 System.out.println( "parameterTypes : " + Arrays.toString(parameterTypes));
 Object[] arguments = (Object[])input.readObject();
 System.out.println("arguments : " + Arrays.toString(arguments)); /* Server 处理请求,进行响应*/
 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); 
try { // service类型为Object的(可以发布任何服务),故只能通过反射调用处理请求
 // 反射调用,处理请求
 Method method = service.getClass().getMethod(methodName,
 parameterTypes);
 Object result = method.invoke(service, arguments);
 System.out.println("\nServer 处理并生成响应 :");
 System.out.println("result : " + result);
 output.writeObject(result);
 } catch (Throwable t) {
 output.writeObject(t);
 } finally {
 output.close();
 }
 } finally {
 input.close();
 }
 } finally {
 socket.close();
 }
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }).start();
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }

从该RPC框架的简易实现来看,RPC服务端逻辑是:首先创建ServerSocket负责监听特定端口并接收客户连接请求,然后使用Java原生的序列化/反序列化机制来解析得到请求,包括所调用方法的名称、参数列表和实参,最后反射调用服务端对服务接口的具体实现并将得到的结果回传至客户端。至此,一次简单PRC调用的服务端流程执行完毕。


  • 客户端引用服务
 /**
 * 引用服务
 *
 * @param <T> 接口泛型
 * @param interfaceClass 接口类型
 * @param host 服务器主机名
 * @param port 服务器端口
 * @return 远程服务,返回代理对象
 * @throws Exception
 */
 @SuppressWarnings("unchecked") 
public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception { 
if (interfaceClass == null) { 
throw new IllegalArgumentException("Interface class == null");
 } 
// JDK 动态代理的约束,只能实现对接口的代理
 if (!interfaceClass.isInterface()) { 
throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
 } 
if (host == null || host.length() == 0) { 
throw new IllegalArgumentException("Host == null!");
 } 
if (port <= 0 || port > 65535) { 
throw new IllegalArgumentException("Invalid port " + port);
 }
 System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port); 
// JDK 动态代理
 T proxy = (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() { 
// invoke方法本意是对目标方法的增强,在这里用于发送RPC请求和接收响应
 @Override
 public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable { 
// 创建Socket客户端,并与服务端建立链接
 Socket socket = new Socket(host, port); 
try { /* 客户端像服务端进行请求,并将请求参数写入流中*/
 // 将对象写入到对象输出流,并将其发送到Socket流中去
 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); 
try { // 发送请求
 System.out.println("\nClient发送请求 : ");
 output.writeUTF(method.getName());
 System.out.println("methodName : " + method.getName());
 output.writeObject(method.getParameterTypes());
 System.out.println("parameterTypes : " + Arrays.toString(method
 .getParameterTypes()));
 output.writeObject(arguments);
 System.out.println("arguments : " + Arrays.toString(arguments)); /* 客户端读取并返回服务端的响应*/
 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); 
try {
 Object result = input.readObject(); 
if (result instanceof Throwable) { 
throw (Throwable)result;
 }
 System.out.println("\nClient收到响应 : ");
 System.out.println("result : " + result); 
return result;
 } finally {
 input.close();
 }
 } finally {
 output.close();
 }
 } finally {
 socket.close();
 }
 }
 }); return proxy;
 }

从该RPC框架的简易实现来看,RPC客户端逻辑是:首先创建Socket客户端并与服务端建立链接,然后使用Java原生的序列化/反序列化机制将调用请求发送给客户端,包括所调用方法的名称、参数列表将服务端的响应返回给用户即可。至此,一次简单PRC调用的客户端流程执行完毕。特别地,从代码实现来看,实现透明的PRC调用的关键就是 动态代理,这是RPC框架实现的灵魂所在。


  • RPC原型实现
public class RpcFramework {
 /**
 * 暴露服务
 *
 * @param service 服务实现
 * @param port 服务端口
 * @throws Exception
 */
 public static void export(final Object service, int port) throws Exception { if (service == null) { throw new IllegalArgumentException("service instance == null");
 } if (port <= 0 || port > 65535) { throw new IllegalArgumentException("Invalid port " + port);
 }
 System.out.println("Export service " + service.getClass().getName() + " on port " + port); // 建立Socket服务端
 ServerSocket server = new ServerSocket(port); for (; ; ) { try { // 监听Socket请求
 final Socket socket = server.accept(); new Thread(new Runnable() { @Override
 public void run() { try { try { /* 获取请求流,Server解析并获取请求*/
 // 构建对象输入流,从源中读取对象到程序中
 ObjectInputStream input = new ObjectInputStream(
 socket.getInputStream()); try {
 System.out.println("\nServer解析请求 : ");
 String methodName = input.readUTF();
 System.out.println("methodName : " + methodName); // 泛型与数组是不兼容的,除了通配符作泛型参数以外
 Class<?>[] parameterTypes = (Class<?>[])input.readObject();
 System.out.println( "parameterTypes : " + Arrays.toString(parameterTypes));
 Object[] arguments = (Object[])input.readObject();
 System.out.println("arguments : " + Arrays.toString(arguments)); /* Server 处理请求,进行响应*/
 ObjectOutputStream output = new ObjectOutputStream(
 socket.getOutputStream()); try { // service类型为Object的(可以发布任何服务),故只能通过反射调用处理请求
 // 反射调用,处理请求
 Method method = service.getClass().getMethod(methodName,
 parameterTypes);
 Object result = method.invoke(service, arguments);
 System.out.println("\nServer 处理并生成响应 :");
 System.out.println("result : " + result);
 output.writeObject(result);
 } catch (Throwable t) {
 output.writeObject(t);
 } finally {
 output.close();
 }
 } finally {
 input.close();
 }
 } finally {
 socket.close();
 }
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }).start();
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 } /**
 * 引用服务
 *
 * @param <T> 接口泛型
 * @param interfaceClass 接口类型
 * @param host 服务器主机名
 * @param port 服务器端口
 * @return 远程服务,返回代理对象
 * @throws Exception
 */
 @SuppressWarnings("unchecked") public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception { if (interfaceClass == null) { throw new IllegalArgumentException("Interface class == null");
 } // JDK 动态代理的约束,只能实现对接口的代理
 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( "The " + interfaceClass.getName() + " must be interface class!");
 } if (host == null || host.length() == 0) { throw new IllegalArgumentException("Host == null!");
 } if (port <= 0 || port > 65535) { throw new IllegalArgumentException("Invalid port " + port);
 }
 System.out.println( "Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port); // JDK 动态代理
 T proxy = (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() { // invoke方法本意是对目标方法的增强,在这里用于发送RPC请求和接收响应
 @Override
 public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable { // 创建Socket客户端,并与服务端建立链接
 Socket socket = new Socket(host, port); try { /* 客户端像服务端进行请求,并将请求参数写入流中*/
 // 将对象写入到对象输出流,并将其发送到Socket流中去
 ObjectOutputStream output = new ObjectOutputStream(
 socket.getOutputStream()); try { // 发送请求
 System.out.println("\nClient发送请求 : ");
 output.writeUTF(method.getName());
 System.out.println("methodName : " + method.getName());
 output.writeObject(method.getParameterTypes());
 System.out.println("parameterTypes : " + Arrays.toString(method
 .getParameterTypes()));
 output.writeObject(arguments);
 System.out.println("arguments : " + Arrays.toString(arguments)); /* 客户端读取并返回服务端的响应*/
 ObjectInputStream input = new ObjectInputStream(
 socket.getInputStream()); try {
 Object result = input.readObject(); if (result instanceof Throwable) { throw (Throwable)result;
 }
 System.out.println("\nClient收到响应 : ");
 System.out.println("result : " + result); return result;
 } finally {
 input.close();
 }
 } finally {
 output.close();
 }
 } finally {
 socket.close();
 }
 }
 }); return proxy;
 }
}

以上是简易RPC框架实现的简易完整代码。

四. 关于RPC框架的若干问题说明

(1).RPC框架如何做到透明化远程服务调用?

如何封装通信细节才能让用户像以本地调用方式调用远程服务呢?就Java而言,动态代理恰是解决之道。Java动态代理有JDK动态代理和CGLIB动态代理两种方式。尽管字节码生成方式实现的代理更为强大和高效,但代码维护不易,因此RPC框架的大部分实现还是选择JDK动态代理的方式。在上面的例子中,RPCFramework实现中的invoke方法封装了与远端服务通信的细节,消费方首先从RPCFramework获得服务提供方的接口,当执行helloService.hi(“Panda”)方法时就会调用invoke方法。


(2).如何发布自己的服务?

如何让别人使用我们的服务呢?难道就像我们上面的代码一样直接写死服务的IP以及端口就可以了吗?事实上,在实际生产实现中,使用人肉告知的方式是不现实的,因为实际生产中服务机器上/下线太频繁了。如果你发现一台机器提供服务不够,要再添加一台,这个时候就要告诉调用者我现在有两个IP了,你们要轮询调用来实现负载均衡;调用者咬咬牙改了,结果某天一台机器挂了,调用者发现服务有一半不可用,他又只能手动修改代码来删除挂掉那台机器的ip。这必然是相当痛苦的!

有没有一种方法能实现自动告知,即机器的上线/下线对调用方透明,调用者不再需要写死服务提供方地址?当然可以,生产中的RPC框架都采用的是自动告知的方式,比如,阿里内部使用的RPC框架HSF是通过ConfigServer来完成这项任务的。此外,Zookeeper也被广泛用于实现服务自动注册与发现功能。不管具体采用何种技术,他们大都采用的都是 发布/订阅模式。


(3).序列化与反序列化

我们知道,Java对象是无法直接在网络中进行传输的。那么,我们的RPC请求如何发给服务端,客户端又如何接收来自服务端的响应呢?答案是,在传输Java对象时,首先对其进行序列化,然后在相应的终端进行反序列化还原对象以便进行处理。事实上,序列化/反序列化技术也有很多种,比如Java的原生序列化方式、JSON、阿里的Hessian和ProtoBuff序列化等,它们在效率上存在差异,但又有各自的特点。


除上面提到的三个问题外,生产中使用的RPC框架要考虑的东西还有很多,在此就不作探讨了。本文的目的就是为了让各位看官对RPC框架有一个感性的、较为深入的了解,如果达到了这一目的,笔者的目的基本就算达到了。

五. 总结

本文阐述了远程调用的产生背景,然后介绍了RPC的基本概念和要解决的问题,之后手动实现了简易得RPC框架并佐以实例进行演示,使看官们对RPC有一个感性完整的认识,最后讨论了RPC框架的几个重要问题。总之,RPC框架的精髓在于动态代理和反射,通过它们使得远程调用“本地化”,对用户透明且友好。

引用

RPC原理及RPC实例分析

http://www.importnew.com/22003.html

RPC框架几行代码就够了

http://javatar.iteye.com/blog/1123915

相关推荐

2025南通中考作文解读之四:结构框架

文题《继续走,迈向远方》结构框架:清晰叙事,层层递进示例结构:1.开头(点题):用环境描写或比喻引出“走”与“远方”,如“人生如一条长路,每一次驻足后,都需要继续走,才能看见更美的风景”。2.中间...

高中数学的知识框架(高中数学知识框架图第三章)

高中数学的知识框架可以划分为多个核心板块,每个板块包含具体的知识点与内容,以下为详细的知识框架结构:基础知识1.集合与逻辑用语:涵盖集合的概念、表示方式、性质、运算,以及命题、四种命题关系、充分条件...

决定人生的六大框架(决定人生的要素)

45岁的自己混到今天,其实是失败的,要是早点意识到影响人生的六大框架,也不至于今天的模样啊!排第一的是环境,不是有句话叫人是环境的产物,身边的环境包括身边的人和事,这些都会对一个人产生深远的影响。其次...

2023年想考过一级造价师土建计量,看这30个知识点(三)

第二章工程构造考点一:工业建筑分类[考频分析]★★★1.按厂房层数分:(1)单层厂房;(2)多层厂房;(3)混合层数厂房。2.按工业建筑用途分:(1)生产厂房;(2)生产辅助厂房;(3)动力用厂房;(...

一级建造师习题集-建筑工程实务(第一章-第二节-2)

建筑工程管理与实务题库(章节练习)第一章建筑工程技术第二节结构设计与构造二、结构设计1.常见建筑结构体系中,适用建筑高度最小的是()。A.框架结构体系B.剪力墙结构体系C.框架-剪力墙结构体系D...

冷眼读书丨多塔斜拉桥,这么美又这么牛

”重大交通基础设施的建设是国民经济和社会发展的先导,是交通运输行业新技术集中应用与创新的综合体现。多塔斜拉桥因跨越能力强、地形适应性强、造型优美等特点,备受桥梁设计者的青睐,在未来跨越海峡工程中将得...

2021一级造价师土建计量知识点:民用建筑分类

2021造价考试备考开始了,学霸君为大家整理了一级造价师备考所用的知识点,希望对大家的备考道路上有所帮助。  民用建筑分类  一、按层数和高度分  1.住宅建筑按层数分类:1~3层为低层住宅,4~6层...

6个建筑结构常见类型,你都知道吗?

建筑结构是建筑物中支承荷载(作用)起骨架作用的体系。结构是由构件组成的。构件有拉(压)杆、梁、板、柱、拱、壳、薄膜、索、基础等。常见的建筑结构类型有6种:砖混结构、砖木结构、框架结构、钢筋混凝土结构、...

框架结构设计经验总结(框架结构设计应注意哪些问题)

1.结构设计说明主要是设计依据,抗震等级,人防等级,地基情况及承载力,防潮抗渗做法,活荷载值,材料等级,施工中的注意事项,选用详图,通用详图或节点,以及在施工图中未画出而通过说明来表达的信息。2.各...

浅谈混凝土框架结构设计(混凝土框架结构设计主要内容)

浅谈混凝土框架结构设计 摘要:结构设计是个系统的全面的工作,需要扎实的理论知识功底,灵活创新的思维和严肃认真负责的工作态度。钢筋混凝土框架结构虽然相对简单,但设计中仍有很多需要注意的问题。本文针...

2022一级建造师《建筑实务》1A412020 结构设计 精细考点整理

历年真题分布统计1A412021常用建筑结构体系和应用一、混合结构体系【2012-3】指楼盖和屋盖采用钢筋混凝土或钢木结构,而墙和柱采用砌体结构建造的房屋,大多用在住宅、办公楼、教学楼建筑中。优点:...

破土动工!这个故宫“分院”科技含量有点儿高

故宫“分院”设计图。受访者供图近日,位于北京海淀区西北旺镇的故宫北院区项目已开始破土动工,该项目也被称作故宫“分院”,筹备近十年之久。据悉,故宫本院每年展览文物的数量不到1万件,但是“分院”建成后,预...

装配式结构体系介绍(上)(装配式结构如何设计)

PC构件深化、构件之间连接节点做法等与相应装配式结构体系密切相关。本节列举目前常见的几种装配式结构体系:装配整体式混凝土剪力墙结构体系、装配整体式混凝土框架结构体系、装配整体式混凝土空腔结构体系(S...

这些不是双向抗侧结构体系(这些不是双向抗侧结构体系的特点)

双向抗侧土木吧规范对双向抗恻力结构有何规定?为何不应采用单向有墙的结构?双向抗侧土木吧1.规范对双向抗侧力结构体系的要求抗侧力体系是指抵抗水平地震作用及风荷载的结构体系。对于结构体系的布置,规范针对...

2022一级建造师《建筑实务》1A412020 结构设计 精细化考点整理

1A412021常用建筑结构体系和应用一、混合结构体系【2012-3】指楼盖和屋盖采用钢筋混凝土或钢木结构,而墙和柱采用砌体结构建造的房屋,大多用在住宅、办公楼、教学楼建筑中。优点:抗压强度高,造价...

取消回复欢迎 发表评论: