rpc到自己java实现rpc调用再到rpc框架设计

news2025/2/24 6:17:28

目录

  • rpc(Remote Procedure Call)
    • rpc一般架构
    • 为什么要引入rpc
    • 自己实现rpc调用
      • 1. 新建一个maven项目,加入hessian依赖
      • 2. 服务端
      • 3. Stub代理
      • 4. 客户端测试输出
      • 5. rpc程序分析
        • 附 请求参数和序列化程序
      • 6. 总结
    • 回顾RPC
      • RPC 序列化协议
      • RPC 网络协议
      • 注册中心的引入
      • dubbo框架看一个rpc框架的实现架构

rpc(Remote Procedure Call)

Remote Procedure Call (RPC) is a powerful technique for constructing distributed, client-server based applications. It is based on extending the conventional local procedure calling so that the called procedure need not exist in the same address space as the calling procedure. The two processes may be on the same system, or they may be on different systems with a network connecting them.

rpc一般架构

在这里插入图片描述

  • 客户端(Client):服务调用方
  • 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息(序列化),再通过网络传输发送给服务端
  • Network Service:底层传输,可以是 TCP 或 HTTP,或其它网络协议
  • 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包(反序列化),然后再调用本地服务进行处理
  • 服务端(Server):服务的真正提供者

为什么要引入rpc

两台不同的主机进程要进行通信?

最易想到的最原始做法:tcp/ip通信,二进制数据传输

蛮烦点在于:要写网络相关处理;对方服务进行动态扩展后,客户端又得重新对接处理。最好能能像本地调用localService.doSth()一样,能调用B机器的bService.doSth(), C机器的cService.doSth()

要解决这个问题,提升开发效率,由此开始了rpc的引入,下面通过java编程来完成这一基本目标

自己实现rpc调用

基础知识点如下,其实很基础,就是大一学生学完Java就基本能操作

  • JAVA socket编程基础
  • JAVA反射
  • 代理模式/动态代理
  • 序列化

1. 新建一个maven项目,加入hessian依赖

项目结构如下:
在这里插入图片描述

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.rpc</groupId>
    <artifactId>rpctest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.38</version>
        </dependency>
    </dependencies>

</project>

2. 服务端

package com;

import com.entity.RpcRequest;
import com.service.impl.ProServiceImpl;
import com.service.impl.UserServiceImpl;
import com.util.HessianSerializerUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Author mubi
 * @Date 2020/5/18 23:18
 */
public class Server {
    public static void main(String[] args) throws Exception {
        // 监听指定的端口
        final int port = 55533;
        ServerSocket server = new ServerSocket(port);

        // server将一直等待连接的到来
        System.out.println("server将一直等待连接的到来");
        while (true) {
            Socket client = server.accept();
            System.out.println("accept client:" + client.getPort());
            new Thread(() -> {
                try {
                    // 建立好连接后,从socket中获取客户端传递过来的对象
                    InputStream in = client.getInputStream();
                    RpcRequest rpcRequest = HessianSerializerUtil.deserialize(readInputStream(in));
                    System.out.println("rpcRequest:" + rpcRequest);

                    // 执行方法,
                    // 需要从服务注册中找到具体的类,这里模拟判断
                    Class clazz = null;
                    if (rpcRequest.getClassName().equals("com.service.IUserService")) {
                        clazz = UserServiceImpl.class;
                    }
                    if (rpcRequest.getClassName().equals("com.service.IProService")) {
                        clazz = ProServiceImpl.class;
                    }
                    Method method = clazz.getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
                    Object o = method.invoke(clazz.newInstance(), rpcRequest.getArgs());

                    // 返回对象 二进制形式发送给客户端
                    OutputStream out = client.getOutputStream();
                    out.write(HessianSerializerUtil.serialize(o));
                    out.flush();

                    client.close();
                } catch (Exception e) {

                }
            }).start();
        }
//        server.close();
    }

    public static byte[] readInputStream(InputStream inputStream) throws IOException {
        byte[] buffer = new byte[2048];
        int len;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while((len = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        return bos.toByteArray();
    }

}

3. Stub代理

package com;

import com.entity.RpcRequest;
import com.util.HessianSerializerUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;

/**
 * @Author mubi
 * @Date 2020/5/18 23:18
 */
public class Stub {

    public static Object getStub(Class clazz){
        // 调用方法处理器
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 要连接的服务端IP地址和端口
                final String host = "127.0.0.1";
                final int port = 55533;
                // 与服务端建立连接
                Socket socket = new Socket(host, port);

                // 构造请求服务器的对象
                RpcRequest rpcRequest = new RpcRequest(clazz.getName(), method.getName(),
                        method.getParameterTypes(), args);
                // 传递二进制给服务端
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write(HessianSerializerUtil.serialize(rpcRequest));
                socket.shutdownOutput();

                // 直接读取服务端返回的二进制, 反序列化为对象返回
                InputStream inputStream = socket.getInputStream();
                byte[] bytes = readInputStream(inputStream);
                Object o = HessianSerializerUtil.deserialize(bytes);

                inputStream.close();
                outputStream.close();
                socket.close();

                return o;
            }
        };

        Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, h);
        System.out.println(o.getClass().getInterfaces()[0]);
        return o;
    }

    public static byte[] readInputStream(InputStream inputStream) throws IOException {
        byte[] buffer = new byte[2048];
        int len;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while((len = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        return bos.toByteArray();
    }

}

4. 客户端测试输出

package com;

import com.service.IProService;
import com.service.IUserService;

/**
 * @Author mubi
 * @Date 2020/5/18 23:18
 */
public class Client {

    public static void main(String[] args) {
        IUserService iUserService = (IUserService) Stub.getStub(IUserService.class);
        System.out.println(iUserService.getUserById(12));

        IProService iProService = (IProService) Stub.getStub(IProService.class);
        System.out.println(iProService.getProById(12));
    }

}

输出如下:
在这里插入图片描述

可以看到客户端使用远程服务像本地服务一样的调用了

5. rpc程序分析

 public static Object getStub(Class clazz){
        // 调用方法处理器
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 要连接的服务端IP地址和端口
                final String host = "127.0.0.1";
                final int port = 55533;
                // 与服务端建立连接
                Socket socket = new Socket(host, port);

                // 构造请求服务器的对象
                RpcRequest rpcRequest = new RpcRequest(clazz.getName(), method.getName(),
                        method.getParameterTypes(), args);
                // 传递二进制给服务端
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write(HessianSerializerUtil.serialize(rpcRequest));
                socket.shutdownOutput();

                // 直接读取服务端返回的二进制, 反序列化为对象返回
                InputStream inputStream = socket.getInputStream();
                byte[] bytes = readInputStream(inputStream);
                Object o = HessianSerializerUtil.deserialize(bytes);

                inputStream.close();
                outputStream.close();
                socket.close();

                return o;
            }
        };

        Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, h);
        System.out.println(o.getClass().getInterfaces()[0]);
        return o;
    }

可以看到使用了java动态代理,客户端拿到的ServiceImpl实际上是个代理对象,

调用行为进行了代理,如下几个步骤

  1. 网络请求socket连接服务端
  2. 使用hessian将参数 序列化,转化为字节流,进行socket通信
  3. 服务端socket通信,hessian 反序列化客户端传入的参数,然后反射完成服务的调用,然后 序列化 返回结果
  4. 客户端收到服务端响应,反序列化结果,输出
附 请求参数和序列化程序
  • RpcRequest
package com.entity;

import java.io.Serializable;
import java.util.Arrays;

/**
 * rpc请求通用参数结构
 * @Author mubi
 * @Date 2020/5/18 23:10
 */
public class RpcRequest implements Serializable {
    String className;
    String methodName;
    Class[] paramTypes;
    Object[] args;

    public RpcRequest(String className, String methodName, Class[] paramTypes, Object[] args) {
        this.className = className;
        this.methodName = methodName;
        this.paramTypes = paramTypes;
        this.args = args;
    }

    public String getClassName() {
        return className;
    }

    public String getMethodName() {
        return methodName;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public Object[] getArgs() {
        return args;
    }

    @Override
    public String toString() {
        return "RpcRequest{" +
                "className='" + className + '\'' +
                ", methodName='" + methodName + '\'' +
                ", paramTypes=" + Arrays.toString(paramTypes) +
                ", args=" + Arrays.toString(args) +
                '}';
    }
}

  • HessianSerializerUtil
package com.util;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.entity.User;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;


public class HessianSerializerUtil {

    public static <T> byte[] serialize(T obj) {
        byte[] bytes = null;
        // 1、创建字节输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        // 2、对字节数组流进行再次封装

        // step 1. 定义外部序列化工厂
        //ExtSerializerFactory extSerializerFactory = new ExtSerializerFactory();
        //extSerializerFactory.addSerializer(java.time.OffsetDateTime.class, new OffsetDateTimeRedisSerializer());
        //extSerializerFactory.addDeserializer(java.time.OffsetDateTime.class, new OffsetDateTimeRedisDeserializer());
        // step 2. 序列化工厂
        //SerializerFactory serializerFactory = new SerializerFactory();
        //serializerFactory.addFactory(extSerializerFactory);

        HessianOutput hessianOutput = new HessianOutput(bos);
        //hessianOutput.setSerializerFactory(serializerFactory);

        try {
            // 注意,obj 必须实现Serializable接口
            hessianOutput.writeObject(obj);
            bytes = bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return bytes;
    }

    public static <T> T deserialize(byte[] data) {
        if (data == null) {
            return null;
        }
        // 1、将字节数组转换成字节输入流
        ByteArrayInputStream bis = new ByteArrayInputStream(data);

        // step 1. 定义外部序列化工厂
        //ExtSerializerFactory extSerializerFactory = new ExtSerializerFactory();
        //extSerializerFactory.addSerializer(java.time.OffsetDateTime.class, new OffsetDateTimeRedisSerializer());
        //extSerializerFactory.addDeserializer(java.time.OffsetDateTime.class, new OffsetDateTimeRedisDeserializer());
        // step 2. 序列化工厂
        //SerializerFactory serializerFactory = new SerializerFactory();
        //serializerFactory.addFactory(extSerializerFactory);
        HessianInput hessianInput = new HessianInput(bis);
        //hessianInput.setSerializerFactory(serializerFactory);
        Object object = null;

        try {
            object = hessianInput.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return (T) object;
    }

    static void test() throws Exception{
        User user = new User(1, "wang");
        byte[] bytes = HessianSerializerUtil.serialize(user);
        System.out.println(user);
        User user1 = HessianSerializerUtil.deserialize(bytes);
        System.out.println(user1);
    }

    public static void main(String[] args) throws Exception {
        test();
    }
}

6. 总结

程序其实完成了如下图:
在这里插入图片描述

通过最基础的java反射、态代理、socket编程等就实现了简单的类似rpc调用

接下来不管多少种服务;只要双方约定好,客户端直接调用即可,基本不需要修改Stub相关代码; client 调用远程方法,就像调用本地方法一样(客户端同学都不需要懂网络底层,直接服务端有什么,就能用什么)

回顾RPC

在这里插入图片描述

RPC 序列化协议

RPC(Remote Procedure Call,远程过程调用)序列化协议是用于在网络上传输数据的一种机制,特别是在客户端和服务器之间传输函数调用请求和响应时。序列化是将数据结构或对象状态转换为可以存储或传输的格式的过程。在RPC通信中,序列化是关键步骤,因为它使得数据能够在网络上安全传输并被另一端正确解析。

常见的序列化协议包括json、xml、hession、protobuf、thrift、text、bytes等;

  • JSON是一种轻量级的数据交换格式,易于阅读和编写,支持跨语言使用。然而,JSON的序列化和反序列化速度较慢,且序列化后的数据体积较大,不适合对性能要求较高的场景‌

  • Protobuf‌:由谷歌开发,支持多语言平台,序列化后的数据体积小,序列化速度快。它需要预编译IDL文件,适用于需要高效数据传输和存储的场景‌

  • Thrift‌:由Facebook开发,支持跨语言服务开发,序列化速度快,数据体积小。Thrift既是传输协议也是序列化协议,适用于需要高效数据处理的分布式系统‌

  • Hessian‌:主要用于Web服务的序列化和反序列化,支持跨语言调用,但相对于其他协议,Hessian的性能略逊一筹‌

RPC 网络协议

如下通信协议

  • TCP/UDP
  • Web Service
  • Restful(http + json)
  • RMI(Remote Method Invocation)
  • JMS(Java Message Service)
  • RPC(Remote Procedure Call)

不过本质还是掌握Socket编程,了解网络IO相关知识

注册中心的引入

随着服务数量的增多,各个服务之间的调用变得错综复杂,一个服务可能依赖外部多个服务,当一个服务的域名或IP地址改变了之后如何通知依赖方,或者依赖方如何快速的发现服务提供方的地址变化。

两种方案:

  1. 客户端与服务端自己维护:有多少个服务,客户端就要维护多少个(服务增减,负载均衡,心跳)
  2. 找个代理,客户端有需求找代理,代理维持这些服务,也能给客户通知;(可以看成代理模式

在这里插入图片描述

显然是注册中心的方式更加合理和方便。

服务中心可以进行服务注册,类似维护一个登记簿,它管理系统内所有的服务地址。当新的服务启动后,它会向登记簿交待自己的地址信息。服务的依赖方直接向登记簿要Service Provider地址就行了,或者基于某种约定(负载均衡)拿到服务的一个具体实例进行通信就好了

回顾springboot+dubbo+zookeeper的注册服务和调用实践:https://blog.csdn.net/qq_26437925/article/details/145790590

dubbo框架看一个rpc框架的实现架构

在这里插入图片描述

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

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

相关文章

Milvus向量数据库可视化客户端Attu

概述 关于Milvus的介绍&#xff0c;可搜索网络资料。Milvus的使用还在摸索中&#xff1b;打算写一篇&#xff0c;时间待定。 关于Attu的资料&#xff1a; 官网GitHub文档 对于Milvus的数据可视化&#xff0c;有如下两个备选项&#xff1a; Milvus_cli&#xff1a;命令行工…

【落羽的落羽 数据结构篇】顺序结构的二叉树——堆

文章目录 一、堆1. 概念与分类2. 结构与性质3. 入堆4. 出堆 二、堆排序三、堆排序的应用——TOP-K问题 一、堆 1. 概念与分类 上一期我们提到&#xff0c;二叉树的实现既可以用顺序结构&#xff0c;也可以用链式结构。本篇我们来学习顺序结构的二叉树&#xff0c;起个新名字—…

基于STM32的智能农业大棚环境控制系统

1. 引言 传统农业大棚环境调控依赖人工经验&#xff0c;存在控制精度低、能耗高等问题。本文设计了一款基于STM32的智能农业大棚环境控制系统&#xff0c;通过多参数环境监测、作物生长模型与精准执行控制&#xff0c;实现大棚环境的智能优化&#xff0c;提高作物产量与品质。…

Git常见命令--助力开发

git常见命令&#xff1a; 创建初始化仓库&#xff1a; git 将文件提交到暂存区 git add 文件名 将文件提交到工作区 git commit -m "注释&#xff08;例如这是发行的版本1&#xff09;" 文件名 查看状态 如果暂存区没有文件被提交显示&#xff1a; $ git status On…

一:将windows上的Python项目部署到Linux上,并使用公网IP访问

windows中python的版本&#xff1a;python3.13.1&#xff0c;项目使用的是虚拟环境解释器 linux系统&#xff1a;仅有python3.6.7 服务器&#xff1a;阿里云服务器有公网IP&#xff0c;访问端口XXXX 在linux上安装python3.13.1 linux中如果是超级管理员root&#xff0c;执行所…

【数据标准】数据标准化是数据治理的基础

导读&#xff1a;数据标准化是数据治理的基石&#xff0c;它通过统一数据格式、编码、命名与语义等&#xff0c;全方位提升数据质量&#xff0c;确保准确性、完整性与一致性&#xff0c;从源头上杜绝错误与冲突。这不仅打破部门及系统间的数据壁垒&#xff0c;极大促进数据共享…

计算机视觉:经典数据格式(VOC、YOLO、COCO)解析与转换(附代码)

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络(附代码) 第五章&#xff1…

七星棋牌顶级运营产品全开源修复版源码教程:6端支持,200+子游戏玩法,完整搭建指南(含代码解析)

棋牌游戏一直是移动端游戏市场中极具竞争力和受欢迎的品类&#xff0c;而七星棋牌源码修复版无疑是当前行业内不可多得的高质量棋牌项目之一。该项目支持 6大省区版本&#xff08;湖南、湖北、山西、江苏、贵州&#xff09;&#xff0c;拥有 200多种子游戏玩法&#xff0c;同时…

编程考古-忘掉它,Delphi 8 for the Microsoft .NET Framework

忘掉它吧&#xff0c;作一篇记录&#xff01; 【圣何塞&#xff0c;加利福尼亚 – 2003年11月3日】在今日的Borland开发者大会上&#xff0c;Borland正式推出了Delphi 8 for Microsoft .NET Framework。这款新版本旨在为Delphi开发者提供一个无缝迁移路径&#xff0c;将现有的…

[通俗易懂C++]:指针和const

之前的文章有说过,使用指针我们可以改变指针指向的内容(通过给指针赋一个新的地址)或者改变被保存地址的值(通过给解引用指针赋一个新值): int main() {int x { 5 }; // 创建一个整数变量 x&#xff0c;初始值为 5int* ptr { &x }; // 创建一个指针 ptr&#xff0c;指向 …

大一高数(上)速成:导数和微分

目录 1.分段函数的可导性&#xff1a; 2.隐函数求导: 3.参数方程求导: 4.对数求导法: 5.函数的微分: 1.分段函数的可导性&#xff1a; 2.隐函数求导: 3.参数方程求导: 4.对数求导法: 5.函数的微分:

京东cfe滑块 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 headers {"accept&qu…

react 踩坑记 too many re-renders.

报错信息&#xff1a; too many re-renders. React limits the number of randers to prevent an infinite loop. 需求 tabs只有特定标签页才展示某些按钮 button要用 传递函数引用方式 ()>{} *还有要注意子组件内loading触发 导致的重复渲染

BGP分解实验·19——BGP选路原则之起源

当用不同的方式为BGP注入路由时&#xff0c;起源代码将标识路由的来源。 &#xff08;在BGP表中&#xff0c;Network为“i”&#xff0c;重分布是“&#xff1f;”&#xff09; 实验拓扑如下&#xff1a; R2上将来自IGP的路由10.3.3.3/32用network指令注入BGP;在R4上将来自I…

单机上使用docker搭建minio集群

单机上使用docker搭建minio集群 1.集群安装1.1前提条件1.2步骤指南1.2.1安装 Docker 和 Docker Compose&#xff08;如果尚未安装&#xff09;1.2.2编写docker-compose文件1.2.3启动1.2.4访问 2.使用2.1 mc客户端安装2.2创建一个连接2.3简单使用下 这里在ubuntu上单机安装一个m…

家用路由器的WAN口和LAN口有什么区别

今时今日&#xff0c;移动终端盛行的时代&#xff0c;WIFI可以说是家家户户都有使用到的网络接入方式。那么路由器当然也就是家家户户都不可或缺的设备了。而路由器上的两个实现网络连接的基础接口 ——WAN 口和 LAN 口&#xff0c;到底有什么区别&#xff1f;它们的功能和作用…

实操解决Navicat连接postgresql时出现‘datlastsysoid does not exist‘报错的问题

1 column “datlastsysoid“ does not exist2 Line1:SELECT DISTINCT datalastsysoid FROM pg_database问题分析 Postgres 15 从pg_database表中删除了 datlastsysoid 字段引发此错误。 决绝方案 解决方法1&#xff1a;升级navicat 解决方法2&#xff1a;降级pgsql 解决方…

3分钟idea接入deepseek

DeepSeek简介 DeepSeek 是杭州深度求索人工智能基础技术研究有限公司开发的一系列大语言模型&#xff0c;背后是知名量化资管巨头幻方量化3。它专注于开发先进的大语言模型和相关技术&#xff0c;拥有多个版本的模型&#xff0c;如 DeepSeek-LLM、DeepSeek-V2、DeepSeek-V3 等&…

树莓派理想二极管电路分析

如果 Vin Vout&#xff0c;比如说 5.0V&#xff0c;PNP 晶体管以当前的镜像配置偏置。晶体管 U14 的 Vb 将为 5-0.6 4.4V&#xff0c;镜像配置意味着 Vg 也将为 4.4V. Vgs 为4.4-5.0 -0.6V。mosfet 将处于关闭状态&#xff08;几乎打开&#xff09;。如果 Vout 略低于 Vin&a…

Unity贴图与模型相关知识

一、贴图 1.贴图的类型与形状 贴图类型 贴图形状 2.在Unity中可使用一张普通贴图来生成对应的法线贴图&#xff08;但并不规范&#xff09; 复制一张该贴图将复制后的贴图类型改为Normal Map 3.贴图的sRGB与Alpha sRGB&#xff1a;勾选此选项代表此贴图存储于Gamma空间中…