万物互联之~RPC专栏

3.RPC引入

上篇回顾:万物互联之~深入篇

Code:https://github.com/lotapp/BaseCode/tree/master/python/6.net/6.rpc/

其他专栏最新篇:协程加强之~兼容答疑篇 | 聊聊数据库~SQL环境篇

3.1.概念

RPC(Remote Procedure Call):分布式系统常见的一种通信方法(远程过程调用),通俗讲:可以一台计算机的程序调用另一台计算机的子程序(可以把它看成之前我们说的进程间通信,只不过这一次的进程不在同一台PC上了)

PS:RPC的设计思想是力图使远程调用中的通讯细节对于使用者透明,调用双方无需关心网络通讯的具体实现

引用一张网上的图:
1.rpc.png

HTTP有点相似,你可以这样理解:

  1. 老版本的HTTP/1.0是短链接,而RPC是长连接进行通信
    • HTTP协议(header、body),RPC可以采取HTTP协议,也可以自定义二进制格式
  2. 后来HTTP/1.1支持了长连接(Connection:keep-alive),基本上和RPC差不多了
    • keep-alive一般都限制有最长时间,或者最多处理的请求数,而RPC是基于长连接的,基本上没有这个限制
  3. 后来谷歌直接基于HTTP/2.0建立了gRPC,它们之间的基本上也就差不多了
    • 如果硬是要区分就是:HTTP-普通话RPC-方言的区别了
    • RPC高效而小众,HTTP效率没RPC高,但更通用
  4. PS:RPCHTTP调用不用经过中间件,而是端到端的直接数据交互
    • 网络交互可以理解为基于Socket实现的(RPCHTTP都是Socket的读写操作)

简单概括一下RPC的优缺点就是:

  1. 优点:
    1. 效率更高(可以自定义二进制格式)
    2. 发起RPC调用的一方,在编写代码时可忽略RPC的具体实现(跟编写本地函数调用一般
  2. 缺点:
    • 通用性不如HTTP(方言普及程度肯定不如普通话),如果传输协议不是HTTP协议格式,调用双方就需要专门实现通信库

PS:HTTP更多是ClientServer的通讯;RPC更多是内部服务器间的通讯

3.2.引入

上面说这么多,可能还没有来个案例实在,我们看个案例:

本地调用sum()

def sum(a, b):"""return a+b"""return a + bdef main():result = sum(1, 2)print(f"1+2={result}")if __name__ == "__main__":main()

输出:(这个大家都知道)

1+2=3

1.xmlrpc案例

官方文档:

https://docs.python.org/3/library/xmlrpc.client.html
https://docs.python.org/3/library/xmlrpc.server.html

都说RPC用起来就像本地调用一样,那么用起来啥样呢?看个案例:

服务端:(CentOS7:192.168.36.123:50051)

from xmlrpc.server import SimpleXMLRPCServerdef sum(a, b):"""return a+b"""return a + b# PS:50051是gRPC默认端口
server = SimpleXMLRPCServer(('', 50051))
# 把函数注册到RPC服务器中
server.register_function(sum)
print("Server启动ing,Port:50051")
server.serve_forever()

客户端:(Win10:192.168.36.144

from xmlrpc.client import ServerProxystub = ServerProxy("http://192.168.36.123:50051")
result = stub.sum(1, 2)
print(f"1+2={result}")

输出:(Client用起来是不是和本地差不多?就是通过代理访问了下RPCServer而已)

1+2=3

2.server.png

PS:CentOS服务器不是你绑定个端口就一定能访问的,如果不能记让防火墙开放对应的端口

这个之前在说MariaDB环境的时候有详细说:https://www.cnblogs.com/dotnetcrazy/p/9887708.html#_map4

# 添加 --permanent永久生效(没有此参数重启后失效)
firewall-cmd --zone=public --add-port=80/tcp --permanent

2.ZeroRPC案例:

zeroRPC用起来和这个差不多,也简单举个例子吧:

把服务的某个方法注册到RPCServer中,供外部服务调用

import zerorpcclass Test(object):def say_hi(self, name):return f"Hi,My Name is{name}"# 注册一个Test的实例
server = zerorpc.Server(Test())
server.bind("tcp://0.0.0.0:50051")
server.run()

调用服务端代码

import zerorpcclient = zerorpc.Client("tcp://192.168.36.123:50051")
result = client.say_hi("RPC")
print(result)

3.3.简单版自定义RPC

看了上面的引入案例,是不是感觉RPC不过如此?NoNoNo,要是真这么简单也就谈不上RPC架构了,上面两个是最简单的RPC服务了,可以这么说:生产环境基本上用不到,只能当案例练习罢了,对Python来说,最常用的RPC就两个gRPC and Thrift

PS:国产最出名的是Dubbo and Tars,Net最常用的是gRPCThriftSurging

1.RPC服务的流程

要自己实现一个RPC Server那么就得了解整个流程了:

  1. Client(调用者)以本地调用的方式发起调用
  2. 通过RPC服务进行远程过程调用(RPC的目标就是要把这些步骤都封装起来,让使用者感觉不到这个过程)
    1. 客户端的RPC Proxy组件收到调用后,负责将被调用的方法名、参数等打包编码成自定义的协议
    2. 客户端的RPC Proxy组件在打包完成后通过网络把数据包发送给RPC Server
    3. 服务端的RPC Proxy组件把通过网络接收到的数据包按照相应格式进行拆包解码,获取方法名和参数
    4. 服务端的RPC Proxy组件根据方法名和参数进行本地调用
    5. RPC Server(被调用者)本地执行后将结果返回给服务端的RPC Proxy
    6. 服务端的RPC Proxy组件将返回值打包编码成自定义的协议数据包,并通过网络发送给客户端的RPC Proxy组件
    7. 客户端的RPC Proxy组件收到数据包后,进行拆包解码,把数据返回给Client
  3. Client(调用者)得到本次RPC调用的返回结果

用一张时序图来描述下整个过程:
4.时序图.png

PS:RPC Proxy有时候也叫Stub(存根):(Client Stub,Server Stub)

为屏蔽客户调用远程主机上的对象,必须提供某种方式来模拟本地对象,这种本地对象称为存根(stub),存根负责接收本地方法调用,并将它们委派给各自的具体实现对象

PRC服务实现的过程中其实就两核心点:

  1. 消息协议:客户端调用的参数和服务端的返回值这些在网络上传输的数据以何种方式打包编码和拆包解码
    • 经典代表:Protocol Buffers
  2. 传输控制:在网络中数据的收发传输控制具体如何实现(TCP/UDP/HTTP

2.手写RPC

下面我们就根据上面的流程来手写一个简单的RPC:

1.Client调用:

# client.py
from client_stub import ClientStubdef main():stub = ClientStub(("192.168.36.144", 50051))result = stub.get("sum", (1, 2))print(f"1+2={result}")result = stub.get("sum", (1.1, 2))print(f"1.1+2={result}")time_str = stub.get("get_time")print(time_str)if __name__ == "__main__":main()

输出:

1+2=3
1.1+2=3.1
Wed Jan 16 22

2.Client Stub,客户端存根:(主要有打包解包、和RPC服务器通信的方法)

# client_stub.py
import socketclass ClientStub(object):def __init__(self, address):"""address ==> (ip,port)"""self.socket = socket.socket()self.socket.connect(address)def convert(self, obj):"""根据类型转换成对应的类型编号"""if isinstance(obj, int):return 1if isinstance(obj, float):return 2if isinstance(obj, str):return 3def pack(self, func, args):"""打包:把方法和参数拼接成自定义的协议格式:func:函数名@params:类型-参数,类型2-参数2..."""result = f"func:{func}"if args:params = ""# params:类型-参数,类型2-参数2...for item in args:params += f"{self.convert(item)}-{item},"# 去除最后一个,result += f"@params:{params[:-1]}"# print(result)  # log 输出return result.encode("utf-8")def unpack(self, data):"""解包:获取返回结果"""msg = data.decode("utf-8")# 格式应该是"data:xxxx"params = msg.split(":")if len(params) > 1:return params[1]return Nonedef get(self, func, args=None):"""1.客户端的RPC Proxy组件收到调用后,负责将被调用的方法名、参数等打包编码成自定义的协议"""data = self.pack(func, args)# 2.客户端的RPC Proxy组件在打包完成后通过网络把数据包发送给RPC Serverself.socket.send(data)# 等待服务端返回结果data = self.socket.recv(2048)if data:return self.unpack(data)return None

简要说明下:(我根据流程在Code里面标注了,看起来应该很轻松)

之前有说到核心其实就是消息协议and传输控制,我客户端存根的消息协议是自定义的格式(后面会说简化方案):func:函数名@params:类型-参数,类型2-参数2...,传输我是基于TCP进行了简单的封装


3.Server端:(实现很简单)

# server.py
import socket
from server_stub import ServerStubclass RPCServer(object):def __init__(self, address, mycode):self.mycode = mycode# 服务端存根(RPC Proxy)self.server_stub = ServerStub(mycode)# TCP Socketself.socket = socket.socket()# 端口复用self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 绑定端口self.socket.bind(address)def run(self):self.socket.listen()while True:# 等待客户端连接client_socket, client_addr = self.socket.accept()print(f"来自{client_addr}的请求:\n")# 交给服务端存根(Server Proxy)处理self.server_stub.handle(client_socket, client_addr)if __name__ == "__main__":from server_code import MyCodeserver = RPCServer(('', 50051), MyCode())print("Server启动ing,Port:50051")server.run()

为了简洁,服务端代码我单独放在了server_code.py中:

# 5.RPC Server(被调用者)本地执行后将结果返回给服务端的RPC Proxy
class MyCode(object):def sum(self, a, b):return a + bdef get_time(self):import timereturn time.ctime()

4.然后再看看重头戏Server Stub:

# server_stub.py
import socketclass ServerStub(object):def __init__(self, mycode):self.mycode = mycodedef convert(self, num, obj):"""根据类型编号转换类型"""if num == "1":obj = int(obj)if num == "2":obj = float(obj)if num == "3":obj = str(obj)return objdef unpack(self, data):"""3.服务端的RPC Proxy组件把通过网络接收到的数据包按照相应格式进行拆包解码,获取方法名和参数"""msg = data.decode("utf-8")# 格式应该是"格式:func:函数名@params:类型编号-参数,类型编号2-参数2..."array = msg.split("@")func = array[0].split(":")[1]if len(array) > 1:args = list()for item in array[1].split(":")[1].split(","):temps = item.split("-")# 类型转换args.append(self.convert(temps[0], temps[1]))return (func, tuple(args))  # (func,args)return (func, )def pack(self, result):"""打包:把方法和参数拼接成自定义的协议"""# 格式:"data:返回值"return f"data:{result}".encode("utf-8")def exec(self, func, args=None):"""4.服务端的RPC Proxy组件根据方法名和参数进行本地调用"""# 如果没有这个方法则返回Nonefunc = getattr(self.mycode, func, None)if args:return func(*args)  # 解包else:return func()  # 无参函数def handle(self, client_socket, client_addr):while True:# 获取客户端发送的数据包data = client_socket.recv(2048)if data:try:data = self.unpack(data)  # 解包if len(data) == 1:data = self.exec(data[0])  # 执行无参函数elif len(data) > 1:data = self.exec(data[0], data[1])  # 执行带参函数else:data = "RPC Server Error Code:500"except Exception as ex:data = "RPC Server Function Error"print(ex)# 6.服务端的RPC Proxy组件将返回值打包编码成自定义的协议数据包,并通过网络发送给客户端的RPC Proxy组件data = self.pack(data)  # 把函数执行结果按指定协议打包# 把处理过的数据发送给客户端client_socket.send(data)else:print(f"客户端:{client_addr}已断开\n")break

再简要说明一下:里面方法其实主要就是解包执行函数返回值打包

输出图示:
3.div.png

再贴一下上面的时序图:
4.时序图.png

课外拓展:

HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
https://www.cnblogs.com/heluan/p/8620312.html简述分布式RPC框架
https://blog.csdn.net/jamebing/article/details/79610994分布式基础—RPC
http://www.dataguru.cn/article-14244-1.html

4.RPC简化与提炼

上篇回顾:万物互联之~RPC专栏 https://www.cnblogs.com/dunitian/p/10279946.html

上节课解答

之前有网友问,很多开源的RPC中都是使用路由表,这个怎么实现?

其实路由表实现起来也简单,代码基本上不变化,就修改一下server_stub.py__init__exe两个方法就可以了:

class ServerStub(object):def __init__(self, mycode):self.func_dict = dict()# 初始化一个方法名和方法的字典({func_name:func})for item in mycode.__dir__():if not item.startswith("_"):self.func_dict[item] = getattr(mycode, item)def exec(self, func, args=None):"""4.服务端的RPC Proxy组件根据方法名和参数进行本地调用"""# 如果没有这个方法则返回None# func = getattr(self.mycode, func, None)func = self.func_dict[func]if args:return func(*args)  # 解包else:return func()  # 无参函数

4.1.Json序列化

Python比较6的同志对上节课的Code肯定嗤之以鼻,上次自定义协议是同的通用方法,这节课我们先来简化下代码:

再贴一下上节课的时序图:
4.时序图.png

1.Json知识点

官方文档:https://docs.python.org/3/library/json.html

# 把字典对象转换为Json字符串
json_str = json.dumps({"func": func, "args": args})# 把Json字符串重新变成字典对象
data = json.loads(data)
func, args = data["func"], data["args"]

需要注意的就是类型转换了(eg:python tuple ==> json array

PythonJSON
dictobject
list, tuplearray
strstring
int, floatnumber
Truetrue
Falsefalse
Nonenull

PS:序列化:json.dumps(obj),反序列化:json.loads(json_str)

2.消息协议采用Json格式

在原有基础上只需要修改下Stubpackunpack方法即可

Client_Stub(类型转换都省掉了)

import json
import socketclass ClientStub(object):def pack(self, func, args):"""打包:把方法和参数拼接成自定义的协议格式:{"func": "sum", "args": [1, 2]}"""json_str = json.dumps({"func": func, "args": args})# print(json_str)  # log 输出return json_str.encode("utf-8")def unpack(self, data):"""解包:获取返回结果"""data = data.decode("utf-8")# 格式应该是"{data:xxxx}"data = json.loads(data)# 获取不到就返回Nonereturn data.get("data", None)# 其他Code我没有改变

Server Stub()

import json
import socketclass ServerStub(object):def unpack(self, data):"""3.服务端的RPC Proxy组件把通过网络接收到的数据包按照相应格式进行拆包解码,获取方法名和参数"""data = data.decode("utf-8")# 格式应该是"格式:{"func": "sum", "args": [1, 2]}"data = json.loads(data)func, args = data["func"], data["args"]if args:return (func, tuple(args))  # (func,args)return (func, )def pack(self, result):"""打包:把方法和参数拼接成自定义的协议"""# 格式:"data:返回值"json_str = json.dumps({"data": result})return json_str.encode("utf-8")# 其他Code我没有改变

输出图示:
3.div.png

4.2.Buffer序列化

RPC其实更多的是二进制的序列化方式,这边简单介绍下

1.pickle知识点

官方文档:https://docs.python.org/3/library/pickle.html

用法和Json类似,PS:序列化:pickle.dumps(obj),反序列化:pickle.loads(buffer)

2.简单案例

和Json案例类似,也只是改了packunpack,我这边就贴一下完整代码(防止被吐槽)

1.Client

# 和上一节一样
from client_stub import ClientStubdef main():stub = ClientStub(("192.168.36.144", 50051))result = stub.get("sum", (1, 2))print(f"1+2={result}")result = stub.get("sum", (1.1, 2))print(f"1.1+2={result}")time_str = stub.get("get_time")print(time_str)if __name__ == "__main__":main()

2.ClientStub

import socket
import pickleclass ClientStub(object):def __init__(self, address):"""address ==> (ip,port)"""self.socket = socket.socket()self.socket.connect(address)def pack(self, func, args):"""打包:把方法和参数拼接成自定义的协议"""return pickle.dumps((func, args))def unpack(self, data):"""解包:获取返回结果"""return pickle.loads(data)def get(self, func, args=None):"""1.客户端的RPC Proxy组件收到调用后,负责将被调用的方法名、参数等打包编码成自定义的协议"""data = self.pack(func, args)# 2.客户端的RPC Proxy组件在打包完成后通过网络把数据包发送给RPC Serverself.socket.send(data)# 等待服务端返回结果data = self.socket.recv(2048)if data:return self.unpack(data)return None

3.Server

# 和上一节一样
import socket
from server_stub import ServerStubclass RPCServer(object):def __init__(self, address, mycode):self.mycode = mycode# 服务端存根(RPC Proxy)self.server_stub = ServerStub(mycode)# TCP Socketself.socket = socket.socket()# 端口复用self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 绑定端口self.socket.bind(address)def run(self):self.socket.listen()while True:# 等待客户端连接client_socket, client_addr = self.socket.accept()print(f"来自{client_addr}的请求:\n")try:# 交给服务端存根(Server Proxy)处理self.server_stub.handle(client_socket, client_addr)except Exception as ex:print(ex)if __name__ == "__main__":from server_code import MyCodeserver = RPCServer(('', 50051), MyCode())print("Server启动ing,Port:50051")server.run()

4.ServerCode

# 和上一节一样
# 5.RPC Server(被调用者)本地执行后将结果返回给服务端的RPC Proxy
class MyCode(object):def sum(self, a, b):return a + bdef get_time(self):import timereturn time.ctime()

5.ServerStub

import socket
import pickleclass ServerStub(object):def __init__(self, mycode):self.mycode = mycodedef unpack(self, data):"""3.服务端的RPC Proxy组件把通过网络接收到的数据包按照相应格式进行拆包解码,获取方法名和参数"""func, args = pickle.loads(data)if args:return (func, args)  # (func,args)return (func, )def pack(self, result):"""打包:把方法和参数拼接成自定义的协议"""return pickle.dumps(result)def exec(self, func, args=None):"""4.服务端的RPC Proxy组件根据方法名和参数进行本地调用"""# 如果没有这个方法则返回Nonefunc = getattr(self.mycode, func)if args:return func(*args)  # 解包else:return func()  # 无参函数def handle(self, client_socket, client_addr):while True:# 获取客户端发送的数据包data = client_socket.recv(2048)if data:try:data = self.unpack(data)  # 解包if len(data) == 1:data = self.exec(data[0])  # 执行无参函数elif len(data) > 1:data = self.exec(data[0], data[1])  # 执行带参函数else:data = "RPC Server Error Code:500"except Exception as ex:data = "RPC Server Function Error"print(ex)# 6.服务端的RPC Proxy组件将返回值打包编码成自定义的协议数据包,并通过网络发送给客户端的RPC Proxy组件data = self.pack(data)  # 把函数执行结果按指定协议打包# 把处理过的数据发送给客户端client_socket.send(data)else:print(f"客户端:{client_addr}已断开\n")break

输出图示:
3.div.png

然后关于RPC高级的内容(会涉及到注册中心),咱们后面说架构的时候继续,网络这边就说到这

转载于:https://www.cnblogs.com/dunitian/p/10279946.html

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

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

相关文章

前端进阶必备Node.js,你得了解一下

作为前端开发,工作中肯定离不开 JavaScript ,而 Node.js 是基于 JavaScript 语言和 V8 引擎的 Web 服务器项目,让你可以直接使用 JavaScript 来搭架服务器。而且在 Node 环境下,通过模块化的 JavaScript 代码,加上函数…

Web Components 上手指南

现在的前端开发基本离不开 React、Vue 这两个框架的支撑,而这两个框架下面又衍生出了许多的自定义组件库:Element(Vue)Ant Design(React)这些组件库的出现,让我们可以直接使用已经封装好的组件&…

如何远程连接Windows和linux服务器

linux的方法在下面 Windows服务器远程连接 登录控制台查看服务器系统是什么系统例如阿里云的ECS服务器 Windows系统可以使用微软自带的远程工具进行连接,可以连接的系统有Windows server 和Windows 7-10 等等系列;Windows系统,例如Windows10系…

手把手教你接入前端热门抓包神器 - whistle

大家好,我是若川,今天推荐腾讯前端团队的这篇好文。whistle 是一个基于 Node.js 的跨平台网络调试工具。最近随着 Nohost 的开源,有不少同学问了 whistle 相关的问题,本篇文章将结合几个常见的业务场景介绍如何在本地前端项目开发…

React 与 Vue 框架的设计思路大 PK

大家好,我是若川。今天分享一篇框架设计思路的好文。关于我 大家好我是花果山的大圣,今天很荣幸,有机会跟大家分享一下很多年轻人感兴趣的话题《 Vue 和 React 设计思想 PK》,个人水平有限,如果有理解不到位的请倾盆,大…

php foreach id是否存在数组_请纠正这 5 个 PHP 编码小陋习

在做过大量的代码审查后,我经常看到一些重复的错误,以下是纠正这些错误的方法。在循环之前测试数组是否为空$items [];// ...if (count($items) > 0) {foreach ($items as $item) {// process on $item ...}}foreach以及数组函数 (array_*) 可以处理…

1161转进制(C语言)

一:题目 二:思路分析 1.首先该题目让我们使用递归求十进制转其他进制 2.其次,我们要知道十进制转换为其他进制怎么转换,以例题所给的数据为例 由此图可以看出,十进制转换为其他进制,是辗转相除法&#xf…

应对无协议脱欧 葡萄牙机场将为英籍旅客设快速通道

中新网1月18日电 据台湾《联合报》援引英媒报道,英国首相特蕾莎•梅的脱欧协议遭下院否决后,英国无协议脱欧的可能性变大。葡萄牙总理科斯塔17日表示,里斯本当局正对机场开设特殊通道进行规划,使英国旅客无论英国最后如何脱欧&…

6轮字节前端校招面试经验分享

大家好,我是若川。最近金三银四,今天分享一篇字节前端校招面试经验的轻松好文,相信看完会有所收获。也欢迎点击下方卡片关注或者星标我的公众号若川视野因为我错过了2020年的秋招(ps: 那时候连数据结构与算法都还没学完&#xff0…

斥资近1亿港元,小米二次回购

1月21日消息,小米集团发布公告称,公司于1月18日回购了984.96万股B类普通股股票,占已发行股份0.041%,平均价为每股B类股10.1527港元,总计斥资近1亿港元。 这也是继1月17日首次回购后,小米集团连续两日出手进…

ios macos_设计师可以从iOS 14和macOS Big Sur中学到什么?

ios macos重点 (Top highlight)With the introduction of iOS 14 and macOS Big Sur, we are the witness of the next big thing in UI Design. Changes are not so revolutionary like in iOS 7 years before, but they undoubtedly present the trend UI Designers will fol…

网页设计简约_简约网页设计的主要功能

网页设计简约重点 (Top highlight)Minimalism is synonymous with simplicity. Not quite. As the name suggests, minimalism is definitely not about opulent design. But the assumption that minimalism is design-less and plain is also wrong. Minimalism is simple ye…

Expo 2010 Japan Pavilion

^_^转载于:https://www.cnblogs.com/mmmhhhlll/archive/2010/04/16/1713680.html

深度对比学习Vue和React两大框架

作为国内应用最广的两个框架,Vue 和 React 是前端必须掌握的内容,也是面试的重点。但大多数读者都只擅长其中一个框架,当面试涉及到另一个框架的内容时,就答不好了。比如虚拟dom,两个框架中都有应用,面试官…

java rwd_面向任务的设计-不仅限于Mobile First和RWD

java rwdWe already know that majority of solutions should start with a design for smartphones, we know that all websites should be responsive. Now, it’s time to think about holistic solutions with specific tasks adapted to all kind of devices.我们已经知道…

HOJ 1015 Nearly prime numbers

代码 //Nearly prime number is an integer positive number for which it is possible //to find such primes P1 and P2 that given number is equal to P1*P2.#include <stdio.h>#include <stdlib.h>#include <math.h>//decide n whither is a nearly pri…

「前端工程化」该怎么理解?

大家好&#xff0c;我是若川。今天分享一篇「前端工程化」的好文。非广告&#xff0c;请放心阅读。可点击下方卡片关注我&#xff0c;或者查看系列文章。今天发文比较晚&#xff0c;以往都是定时早上7:30发文&#xff0c;也不知道是不是有点早。一.什么是前端工程&#xff1f;一…

figma下载_Figma和ProtoPie中的原型制作,比较

figma下载第1部分 (Part 1) Prototyping has never had such a high profile with a whole host of tools that now give you varying ability to realize your designs beyond their static UI and into a working usable thing. It’s fair to say that prototyping within t…

「前端组件化」该怎么理解?

大家好&#xff0c;我是若川。今天分享一篇关于「前端组件化」的好文。欢迎点击下方卡片关注我。以下是正文~这里我们一起来学习前端组件化的知识&#xff0c;而组件化在前端架构里面是最重要的一个部分。讲到前端架构&#xff0c;其实前端架构中最热门的就有两个话题&#xff…

大屏设计的视觉统一_视觉设计中的统一

大屏设计的视觉统一视觉设计的统一性是什么&#xff1f; (What is unity in visual design?) The concept of unity in visual design means a group of elements working together to create a greater whole. It means, as the clich goes: A whole that is greater than th…