Android中GRPC的使用-拦截器

news2024/11/24 8:52:05

        在构建GRPC应用程序时,无论是客户端应用程序,还是服务端应用程序,在远程方法执行之前后之后,都可能需要执行一些通用逻辑。在GRPC中可以拦截RPC的执行,来满足特定的需求,如日志、认证、性能度量指标等,这会使用一种名为拦截器的扩展机制。GRPC提供了简单的API,用来在客户端和服务器端的GRPC应用程序中实现拦截器。根据所拦截的RPC调用的类型,GRPC拦截器可风味两类:客户端拦截器和服务端拦截。

       注意:本篇文章的demo是基于Android中GRPC的使用-4种通信模式使用的demo上实现的。

客户端拦截器

        当客户端发起RPC来触发GRPC服务的远程方法时,可以在客户端拦截这些RPC,也可以添加多个拦截器分别对不能的需求进行拦截。拦截器流程如下所示:

源码分析

        以一元RPC为例,首先进入getOrderByUnary方法,在这个方法中,调用了getChannel().newCall,通过源码分析,getChannel的是ManagedChannelImpl的实例,在ManagedChannelImpl中,我们的过newCall可以找到Channel正真的实现类,代码如下:

realChannel = new RealChannel(nameResolver.getServiceAuthority());
    Channel channel = realChannel;
    if (builder.binlog != null) {
      channel = builder.binlog.wrapChannel(channel);
    }
    this.interceptorChannel = ClientInterceptors.intercept(channel, interceptors);

        可以看到首先实例化一个 RealChannel对象,之后再通过ClientInterceptors.intercept方法构建拦截器链,intercept实现如下:

 Preconditions.checkNotNull(channel, "channel");
    for (ClientInterceptor interceptor : interceptors) {
      channel = new InterceptorChannel(channel, interceptor);
    }
    return channel;

            通过 InterceptorChannel将当前拦截器与下一个Channel关联起来,而InterceptorChannel中的newCall方法调用了interceptor中的interceptCall,具体代码如下:

 public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(
        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions) {
      return interceptor.interceptCall(method, callOptions, channel);
    }

        之后通过这种链式调用 ,构建一条拦截的器请求链。我们在getOrderByUnary发现,改方法还调用了asyncUnaryCall,通过asyncUnaryCall方法的调用,最终发现调用了asyncUnaryRequestCall,代码如下:

private static <ReqT, RespT> void asyncUnaryRequestCall(
      ClientCall<ReqT, RespT> call,
      ReqT req,
      StartableListener<RespT> responseListener) {
    startCall(call, responseListener);
    try {
      call.sendMessage(req);
      call.halfClose();
    } catch (RuntimeException e) {
      throw cancelThrow(call, e);
    } catch (Error e) {
      throw cancelThrow(call, e);
    }
  }

        可以发现,首先调用了 startCall,在startCall中最后调用的是ClientCall中的start方法,而在拦截器中,我们调用了下一个拦截器中的ClientCall中的start方法,代码如下:

super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
                        @Override
                        public void onHeaders(Metadata headers) {
                            // 服务端传递回来的header
                            Log.d(TAG," interceptCall : onHeaders = " + headers);
                            super.onHeaders(headers);
                        }

                        @Override
                        public void onClose(Status status, Metadata trailers) {
                            Log.d(TAG," interceptCall : onClose status = " + status ) ;
                            super.onClose(status, trailers);
                        }

                        @Override
                        public void onMessage(RespT message) {
                            Log.d(TAG," interceptCall : onMessage message = " + message);
                            super.onMessage(message);
                        }

                        @Override
                        public void onReady() {
                            Log.d(TAG," interceptCall : onReady");
                            super.onReady();
                        }
                    }, headers);

         最后会调用RealChannel中的ClientCall,该类的ClientCall为ClientCallImpl,至此拦截器的监听链建立完成,具体代码中的类调用关系如下所示:

 

代码实现

        添加两个拦截器的:MyClientInterceptor、MyClient2Interceptor,具体实现如下:

public class MyClientInterceptor implements ClientInterceptor {
    private final String TAG = "MyClientInterceptor";
    private static final Metadata.Key<String> TOKEN = Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER);

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {

        return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {

            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                // 客户端传递链路追中数据,将数据放到headers中
                Log.d(TAG," interceptCall : currentThreadName = "+Thread.currentThread().getName()+"start = " + headers );
                headers.put(TOKEN, "A2D05E5ED2414B1F8C6AEB19F40EF77C");
                try{
                    super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
                        @Override
                        public void onHeaders(Metadata headers) {
                            // 服务端传递回来的header
                            Log.d(TAG," interceptCall : onHeaders = " + headers);
                            super.onHeaders(headers);
                        }

                        @Override
                        public void onClose(Status status, Metadata trailers) {
                            Log.d(TAG," interceptCall : onClose status = " + status);
                            super.onClose(status, trailers);
                        }

                        @Override
                        public void onMessage(RespT message) {
                            Log.d(TAG," interceptCall : onMessage message = " + message);
                            super.onMessage(message);
                        }



                    }, headers);
                }catch (Exception e){
                    Log.d(TAG," interceptCall : err = " + e.getMessage());
                }

            }

            @Override
            public void sendMessage(ReqT message) {
                Log.d(TAG," interceptCall : sendMessage currentThreadName = "+Thread.currentThread().getName()+ "message = " + message );
                super.sendMessage(message);
            }

            @Override
            public void cancel(@Nullable String message, @Nullable Throwable cause) {
                Log.d(TAG," interceptCall : currentThreadName = "+Thread.currentThread().getName()+" cancel  message = " + message + " " + cause.getMessage());
                super.cancel(message, cause);
            }
        };

    }
public class MyClient2Interceptor implements ClientInterceptor {
    private final String TAG = "MyClient2Interceptor";
    private static final Metadata.Key<String> TOKEN = Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER);

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {

        return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {

            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                // 客户端传递链路追中数据,将数据放到headers中
                Log.d(TAG," interceptCall : currentThreadName = "+Thread.currentThread().getName()+"start = " + headers );
                headers.put(TOKEN, "A2D05E5ED2414B1F8C6AEB19F40EF77C");
                try{
                    super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
                        @Override
                        public void onHeaders(Metadata headers) {
                            // 服务端传递回来的header
                            Log.d(TAG," interceptCall : onHeaders = " + headers);
                            super.onHeaders(headers);
                        }

                        @Override
                        public void onClose(Status status, Metadata trailers) {
                            Log.d(TAG," interceptCall : onClose status = " + status);
                            super.onClose(status, trailers);
                        }

                        @Override
                        public void onMessage(RespT message) {
                            Log.d(TAG," interceptCall : onMessage message = " + message);
                            super.onMessage(message);
                        }



                    }, headers);
                }catch (Exception e){
                    Log.d(TAG," interceptCall : err = " + e.getMessage());
                }

            }

            @Override
            public void sendMessage(ReqT message) {
                Log.d(TAG," interceptCall : sendMessage currentThreadName = "+Thread.currentThread().getName()+ "message = " + message );
                super.sendMessage(message);
            }

            @Override
            public void cancel(@Nullable String message, @Nullable Throwable cause) {
                Log.d(TAG," interceptCall : currentThreadName = "+Thread.currentThread().getName()+" cancel  message = " + message + " " + cause.getMessage());
                super.cancel(message, cause);
            }
        };

    }
}

        给ManagedChannelBuilder添加拦截器,代码如下:

.intercept(new MyClientInterceptor(),new MyClient2Interceptor())

运行结果

        以一元RPC为例,运行结果如下:

2022-12-21 11:12:56.728 24644-24930/com.reactnative.grpcserverdemo D/MyClient2Interceptor:  interceptCall : currentThreadName = pool-3-thread-1start = Metadata()
2022-12-21 11:12:56.729 24644-24930/com.reactnative.grpcserverdemo D/MyClientInterceptor:  interceptCall : currentThreadName = pool-3-thread-1start = Metadata(token=A2D05E5ED2414B1F8C6AEB19F40EF77C)
2022-12-21 11:12:56.744 24644-24930/com.reactnative.grpcserverdemo D/MyClient2Interceptor:  interceptCall : sendMessage currentThreadName = pool-3-thread-1message = # com.google.protobuf.StringValue@7c65f
    value: "0"
2022-12-21 11:12:56.746 24644-24930/com.reactnative.grpcserverdemo D/MyClientInterceptor:  interceptCall : sendMessage currentThreadName = pool-3-thread-1message = # com.google.protobuf.StringValue@7c65f
    value: "0"
2022-12-21 11:12:57.251 24644-24935/com.reactnative.grpcserverdemo D/MyClientInterceptor:  interceptCall : onHeaders = Metadata(content-type=application/grpc,grpc-encoding=identity,grpc-accept-encoding=gzip)
2022-12-21 11:12:57.251 24644-24935/com.reactnative.grpcserverdemo D/MyClient2Interceptor:  interceptCall : onHeaders = Metadata(content-type=application/grpc,grpc-encoding=identity,grpc-accept-encoding=gzip)
2022-12-21 11:12:57.291 24644-24935/com.reactnative.grpcserverdemo D/MyClientInterceptor:  interceptCall : onMessage message = # order.OrderManagementOuterClass$Order@2e3553
    id: "0-1"
2022-12-21 11:12:57.293 24644-24935/com.reactnative.grpcserverdemo D/MyClient2Interceptor:  interceptCall : onMessage message = # order.OrderManagementOuterClass$Order@2e3553
    id: "0-1"
2022-12-21 11:12:57.296 24644-24935/com.reactnative.grpcserverdemo D/MyClientInterceptor:  interceptCall : onClose status = Status{code=OK, description=null, cause=null}
2022-12-21 11:12:57.297 24644-24935/com.reactnative.grpcserverdemo D/MyClient2Interceptor:  interceptCall : onClose status = Status{code=OK, description=null, cause=null}

服务端拦截器

        当客户端调用GRPC服务的远程方法时,通过使用服务器端的拦截器,可以在执行远程方法之前,执行一个通用的逻辑。当需要在调用远程方法之前应用认证等特性时,会非常有帮助。也可以添加多个拦截器,分别对不同的功能进行认证。其拦截流程如下:

代码实现

        添加两个拦截器的:MyServerInterceptor、MyServer2Interceptor,具体实现如下:

public class MyServerInterceptor implements ServerInterceptor {
    private final String TAG = "MyServerInterceptor";
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        Log.d(TAG," interceptCall : headers = " + headers);
        ServerCall.Listener<ReqT> delegate = next.startCall(new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
            @Override
            public void sendHeaders(Metadata headers) {
                Log.d(TAG, " interceptCall response : sendHeaders = " + headers);
                super.sendHeaders(headers);
            }

            @Override
            public void sendMessage(RespT message) {
                Log.d(TAG, " interceptCall response : sendMessage = " + headers);
                super.sendMessage(message);
            }

        }, headers);


        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(delegate) {
            @Override
            public void onMessage(ReqT message) {
                super.onMessage(message);
                Log.d(TAG," interceptCall request : onMessage message = " + message);
            }

            @Override
            public void onReady() {
                super.onReady();
                Log.d(TAG," interceptCall request : onReady");
            }

            @Override
            public void onCancel() {
                super.onCancel();
                Log.d(TAG," interceptCall request : onCancel");
            }

            @Override
            public void onComplete() {
                super.onComplete();
                Log.d(TAG," interceptCall request : onComplete");
            }
        };

    }
public class MyServer2Interceptor implements ServerInterceptor {
    private final String TAG = "MyServer2Interceptor";
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        Log.d(TAG," interceptCall : headers = " + headers);

        ServerCall.Listener<ReqT> delegate = next.startCall(new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
            @Override
            public void sendHeaders(Metadata headers) {
                Log.d(TAG, " interceptCall response : sendHeaders = " + headers);
                super.sendHeaders(headers);
            }

            @Override
            public void sendMessage(RespT message) {
                Log.d(TAG, " interceptCall response : sendMessage = " + headers);
                super.sendMessage(message);
            }

        }, headers);


        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(delegate) {
            @Override
            public void onMessage(ReqT message) {
                super.onMessage(message);
                Log.d(TAG," interceptCall request : onMessage message = " + message);
            }

            @Override
            public void onReady() {
                super.onReady();
                Log.d(TAG," interceptCall request : onReady");
            }

            @Override
            public void onCancel() {
                super.onCancel();
                Log.d(TAG," interceptCall request : onCancel");
            }

            @Override
            public void onComplete() {
                super.onComplete();
                Log.d(TAG," interceptCall request : onComplete");
            }
        };
    }
}

运行结果

        以一元RPC为例,运行结果如下:

2022-12-29 15:20:54.257 3345-3804/com.reactnative.grpcserverdemo D/MyServer2Interceptor:  interceptCall : headers = Metadata(user-agent=grpc-java-okhttp/1.42.0,content-type=application/grpc,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,grpc-accept-encoding=gzip)
2022-12-29 15:20:54.258 3345-3804/com.reactnative.grpcserverdemo D/MyServerInterceptor:  interceptCall : headers = Metadata(user-agent=grpc-java-okhttp/1.42.0,content-type=application/grpc,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,grpc-accept-encoding=gzip)
2022-12-29 15:20:54.262 3345-3804/com.reactnative.grpcserverdemo D/MyServerInterceptor:  interceptCall request : onReady
2022-12-29 15:20:54.262 3345-3804/com.reactnative.grpcserverdemo D/MyServer2Interceptor:  interceptCall request : onReady
2022-12-29 15:20:54.277 3345-3804/com.reactnative.grpcserverdemo D/MyServerInterceptor:  interceptCall request : onMessage message = # com.google.protobuf.StringValue@7c65f
    value: "0"
2022-12-29 15:20:54.278 3345-3804/com.reactnative.grpcserverdemo D/MyServer2Interceptor:  interceptCall request : onMessage message = # com.google.protobuf.StringValue@7c65f
    value: "0"
2022-12-29 15:20:54.280 3345-3804/com.reactnative.grpcserverdemo D/MyServerInterceptor:  interceptCall response : sendHeaders = Metadata()
2022-12-29 15:20:54.281 3345-3804/com.reactnative.grpcserverdemo D/MyServer2Interceptor:  interceptCall response : sendHeaders = Metadata()
2022-12-29 15:20:54.303 3345-3804/com.reactnative.grpcserverdemo D/MyServerInterceptor:  interceptCall response : sendMessage = Metadata(user-agent=grpc-java-okhttp/1.42.0,content-type=application/grpc,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,grpc-accept-encoding=gzip)
2022-12-29 15:20:54.304 3345-3804/com.reactnative.grpcserverdemo D/MyServer2Interceptor:  interceptCall response : sendMessage = Metadata(user-agent=grpc-java-okhttp/1.42.0,content-type=application/grpc,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,token=A2D05E5ED2414B1F8C6AEB19F40EF77C,grpc-accept-encoding=gzip)
2022-12-29 15:20:54.332 3345-3804/com.reactnative.grpcserverdemo D/MyServerInterceptor:  interceptCall request : onComplete
2022-12-29 15:20:54.332 3345-3804/com.reactnative.grpcserverdemo D/MyServer2Interceptor:  interceptCall request : onComplete

最后附上demo下载地址

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

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

相关文章

基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离

一、篇头 本文介绍Platform平台驱动框架&#xff0c;使用此框架&#xff0c;将可以用上DTS设备树文件&#xff0c;实现设备的静态枚举&#xff0c;实现设备与驱动的分离。 本文基于Amlogic T972 &#xff0c; Android 9.0&#xff0c; 内核版本 4.9.113 二、系列文章 第1篇&a…

【Docker基础篇】一文完成快速使用

Docker基础篇前言Docker是什么&#xff1f;Docker与传统虚拟机技术区别Docker作用、为什么用docker原理Docker常用命令案例展示redis各种软件安装打包到本地、上传到远程仓库打包到本地推送到远程仓库前言 所有的权威均来自与官方&#xff0c;如有疑问请参考官网文献。人非圣贤…

网络厂商 Aruba 2022 年业绩成倍增长的经验是什么

转眼&#xff0c;2022 年马上就要结束了。 在多重不确定的经济环境之下&#xff0c;网络厂商 Aruba 中国在 2022 年的业绩&#xff0c;与上一财年相比仍然实现了成倍的增长。今年初&#xff0c;CSDN 曾采访过 Aruba 中国区的管理者 Aruba 中国区总经理谢建国与 Aruba 北中国区大…

alpine的介绍与使用

目录 1. alpine 简介 2. 基于alpine构建jdk8镜像 2.1 Dockerfile 2.2 将构建目录上传到linux中 2.3 执行构建 3. 镜像瘦身 4. 上传阿里云镜像仓库 1. alpine 简介 Alpine Linux是一个轻型Linux发行版&#xff0c;它不同于通常的Linux发行版&#xff0c;Alpine采用了musl …

【车间调度】基于卷积神经网络的柔性作业车间调度问题的两阶段算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

CryptoJS加密解密

1.CryptoJS CryptoJS库是前端js写的一个加密解密的工具&#xff0c;使用场景就是对于像账户密码等重要信息要显示在页面上时&#xff0c;需要加密&#xff0c;这样才能保证安全&#xff0c;CryptoJS提供了许多加密的方法&#xff0c; 请参考&#xff1a; cryptojs-JavaScrip…

OSPF的汇总实验

目录 1.拓扑图 2.实验要求 3.实验思路 4.主要配置 5.测试 6.实验总结 1.拓扑图 2.实验要求 R4为ISP&#xff0c;其上只能配置ip&#xff0c;R4与其他所有直连设备间使用公有ip&#xff1b;解决ospf不规则区域&#xff1b;整个ospf环境ip地址为172.16.0.0/16&#xff0c;…

pure pursuit纯跟踪

Pure Pursuit是一种几何追踪方法,速度越小,performance越好; :汽车前轮转角 L:前后轮轴距(车长) R:转弯半径 将车辆模型简化为自行车模型(这里默认左轮和右轮的旋转是一致的)!!! bicycle model: pure pursuit建立于自行车模型和阿克曼小车模型的基础上,goal point为距离后…

2022年用于Web开发的15种最佳编程语言

Web 开发是一个常青的领域&#xff0c;机会总是很多。自 90 年代初出现以来&#xff0c;开发行业在各种方式和领域中发展并蓬勃发展。今天&#xff0c;在 21 世纪&#xff0c;Web 开发仍然是最受欢迎的技能之一。要在这个领域开始职业生涯&#xff0c;掌握Web 开发语言是必不可…

UDS-10.4 SecurityAccess (27) service

10.4 安全访问(27)服务 来自&#xff1a;ISO 14229-1-2020.pdf 10.4.1 服务描述 本服务的目的是提供一种访问数据和/或诊断服务的方法&#xff0c;这些数据和/或诊断服务由于安全、排放或安全原因而被限制访问。用于将例程或数据下载/上传到服务器以及从服务器读取特定内存位置…

spring 基础知识-- IOC 和 DI 的配置和使用。

目录 一、基本概念 二、Spring 核心概念 1、问题分析 2、IOC、IOC容器、Bean、DI 3、IOC 入门案例 4、DI 入门案例 三、IOC 详解 1、bean 基础配置 2、bean 实例化 3、bean 生命周期 四、DI 详解 1、setter 注入 2、构造器注入 3、自动配置 4、集合注入 一、基…

go调度和性能分析利器之trace

trace的使用示例 import ("fmt""log""os""runtime/trace""sync" )func main() {//runtime.GOMAXPROCS(1)// 1. 创建trace持久化的文件句柄f, err : os.Create("trace.out")if err ! nil {log.Fatalf("failed…

再次学习make

目录 1.Makefile的重要性 2.MakeFile的概念 3.Makefile的优点 4.Makefile的基本语法 5.变量 5.1 自定义变量 5.2 变量的赋值 5.3自动变量 5.4 Makefile的隐含变量 6.Makefile的函数 6.1意义&#xff1a; 6.2 基本语法&#xff1a; 6.3 部分常用函数 6.4 自定义函…

ECS-弹性容器服务 - Part 1

67-ECS-弹性容器服务 - Part 1 Hello大家好&#xff0c;我们今天的课时内容是ECS-AWS的弹性容器服务。 ECS-AWS的弹性容器服务 ECS是高度可扩展的、快速的容器管理和编排服务。 使用ECS&#xff0c;能够将您的Docker容器运行在AWS EC2或者 Fargate管理的无服务器架构上。 将容…

磨金石教育摄影技能干货分享|什么是序列摄影?它让摄影更加深刻

著名摄影师肖尔曾这样表达过自己的摄影理念。 他说&#xff1a;“吸引我的总是平淡无奇的瞬间”。 他与著名的纪实摄影师布列松不同&#xff0c;他不喜欢去游荡在生活之外&#xff0c;去抓拍他人的精彩瞬间。 他也不喜欢报道式的摄影&#xff0c;不去过多关注社会话题。 而是将…

第二证券|首批浮动费率基金三年成绩出炉 规模虽小收益可观

2019年12月18日至12月26日&#xff0c;6家基金公司试点建立了职业第一批逐笔比例提取成绩酬劳的起浮办理费率基金&#xff08;下称“起浮费率基金”&#xff09;。如今&#xff0c;这6只起浮费率基金运作均已满三年&#xff0c;到2022年12月26日&#xff0c;6只产品建立以来均匀…

Selenium Webdriver 实现原理详解

目录 1. Selenium 概述 2. 术语解释&#xff1a; 3. Selenium WebDriver 实现原理 4. 安装selenium 客户端&#xff0c;浏览器&#xff0c;驱动 4.1 安装selenium client lib 4.2 安装浏览器和浏览器驱动 4.3 例子代码 4.4 省略浏览器驱动的方法 4.5 测试代码与Webdr…

MAC控制器驱动

嵌入式Linux开发模式下&#xff0c;以太网硬件架构一般都是 MAC与PHY是独立的。所以以太网模块的硬件相关的驱动代码主要包括 GMAC 和 PHY&#xff0c;其中MAC控制器驱动由SoC厂商开发&#xff0c;PHY芯片驱动由PHY厂商开发&#xff0c;PHY 驱动一般使用通用 PHY 驱动&#xff…

无线烧录器(2)

传送门&#xff1a;连接 继上次完成了ESP32无线烧录器。那么有一个问题就是&#xff0c;一堆人都在一起想用这个来无线烧录呢&#xff0c;那么固件中定死的热点名称岂不是会互相的扰乱。所以需要自己编译属于自己的热点名称。 首先需要准备以下几样东西。 Ubuntu &#xff08;需…

excel数据透视表:善用这些功能,提高工作效率!下篇

在上篇文章中&#xff0c;我们为大家分享了透视表的前5条妙用&#xff0c;分别是合并同类项、按条件汇总数据、统计非重复数据、排名、批量创建表格&#xff0c;不知道大家都还记得吗&#xff1f;那么今天我们书接上回&#xff0c;继续为大家分享关于透视表的后5条妙用。 六、…