尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
gRPC Go 入门实战:用 RouteGuide 示例掌握四种 RPC 编程模型
gRPC Go 入门实战用 RouteGuide 示例掌握四种 RPC 编程模型【免费下载链接】grpc-goThe Go language implementation of gRPC. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-go本教程面向 Go 开发者以 grpc-go 仓库中的 RouteGuide 路径导航示例为主线系统讲解 gRPC 的核心开发流程在.proto文件中定义服务、使用协议缓冲区编译器生成客户端与服务端代码、以及借助 Go gRPC API 编写一个覆盖全部四种 RPC 模式简单 RPC、服务端流式 RPC、客户端流式 RPC、双向流式 RPC的完整客户端与服务端。读完本文你将能够独立使用 gRPC-Go 完成从服务定义、代码生成到服务实现与调用的全链路开发并理解流式接口的底层语义。为什么使用 gRPC一次定义多语言复用本教程的示例是一个简单的路径导航RouteGuide应用客户端可以向服务器查询沿途的地理特征Feature、汇总整条路线的行程信息并与其他客户端及服务器实时交换路线上的备注信息RouteNote。使用 gRPC 的核心价值在于服务只需在.proto文件中定义一次即可用 gRPC 支持的任何语言Go、Java、C、Python 等实现客户端和服务端且这些实现可以运行在各种环境——从 Google 内部的服务器到个人的平板设备。不同语言、不同环境之间的通信复杂性序列化、传输、连接管理等全部由 gRPC 框架屏蔽。同时使用 protocol buffers 还能获得高效序列化、简洁的接口描述语言IDL以及易于演进升级的接口定义等全部优势。示例代码与环境准备本教程使用的完整示例代码位于仓库的 examples/route_guide 目录目录结构如下examples/route_guide/ ├── client/ │ └── client.go # gRPC 客户端实现 ├── routeguide/ │ ├── route_guide.proto # 服务与消息定义教程核心 │ ├── route_guide.pb.go # 由 protoc 生成消息编解码代码 │ └── route_guide_grpc.pb.go # 由 protoc 生成gRPC 客户端/服务端接口 ├── server/ │ └── server.go # gRPC 服务端实现 ├── testdata/ │ └── route_guide_db.json # 特征数据库含真实地名坐标 └── README.md # 示例运行说明获取代码有两种方式通过go get拉取依赖并进入示例目录$ go get google.golang.org/grpc $ cd $GOPATH/src/google.golang.org/grpc/examples/route_guide直接克隆仓库后进入示例目录克隆地址为https://gitcode.com/GitHub_Trending/gr/grpc-go.git。示例模块的依赖版本以 examples/go.mod 为准Go 版本要求1.25.0gRPC 库版本为google.golang.org/grpc v1.83.0protocol buffers 运行时为google.golang.org/protobuf v1.36.12。注意该模块通过replace google.golang.org/grpc ../将 gRPC 指向仓库根目录因此在仓库内可直接以本地源码运行。若要自行从.proto重新生成代码需要安装protoc编译器以及两个 Go 插件protoc-gen-go消息代码与protoc-gen-go-grpcgRPC 代码。仓库内的 cmd/protoc-gen-go-grpc 目录保存了官方 gRPC Go 插件的源码与使用说明scripts/install-protoc.sh 则提供了protoc的安装脚本。使用 .proto 文件定义服务开发的第一步与 gRPC 快速入门一致是使用 protocol buffers 定义 gRPC服务以及各方法的请求/响应消息类型。本教程使用 proto3 语法仓库中的 route_guide.proto 采用了较新的edition 2023声明语义等同于 proto3。声明服务与四种 RPC 方法在.proto文件中通过命名service声明一个服务service RouteGuide { ... }在服务体内定义rpc方法并指明各自的请求与响应类型。gRPC 支持四种服务方法RouteGuide 服务恰好全部覆盖仓库中的完整定义如下1. 简单 RPCunary——客户端通过 stub 发送一个请求像普通函数调用一样等待响应返回// A simple RPC. // // Obtains the feature at a given position. // // A feature with an empty name is returned if theres no feature at the given // position. rpc GetFeature(Point) returns (Feature) {}2. 服务端流式 RPCserver-side streaming——客户端发送一个请求得到一个可连续读取消息的流直到流结束。只需在响应类型前加stream关键字// A server-to-client streaming RPC. // // Obtains the Features available within the given Rectangle. Results are // streamed rather than returned at once (e.g. in a response message with a // repeated field), as the rectangle may cover a large area and contain a // huge number of features. rpc ListFeatures(Rectangle) returns (stream Feature) {}3. 客户端流式 RPCclient-side streaming——客户端通过流连续写入一系列消息写完后等待服务器读取完毕并返回单个响应。只需在请求类型前加stream关键字// A client-to-server streaming RPC. // // Accepts a stream of Points on a route being traversed, returning a // RouteSummary when traversal is completed. rpc RecordRoute(stream Point) returns (RouteSummary) {}4. 双向流式 RPCbidirectional streaming——双方各持一个可读可写流发送消息序列。两个流彼此独立客户端与服务器可以按任意顺序读写服务器可以等收完客户端全部消息再回复也可以读一条、写一条交替进行或采用其他任意组合每个流内部的消息顺序得到保证。只需在请求与响应类型前都加stream关键字// A Bidirectional streaming RPC. // // Accepts a stream of RouteNotes sent while a route is being traversed, // while receiving other RouteNotes (e.g. from other users). rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}定义消息类型.proto文件还包含服务方法用到的全部请求/响应消息类型定义。例如Point消息使用 E7 表示法纬度/经度乘以 10 的 7 次方后取整纬度范围为 ±90 度、经度范围为 ±180 度含端点// Points are represented as latitude-longitude pairs in the E7 representation // (degrees multiplied by 10**7 and rounded to the nearest integer). // Latitudes should be in the range /- 90 degrees and longitude should be in // the range /- 180 degrees (inclusive). message Point { int32 latitude 1; int32 longitude 2; }其余消息类型同样定义在该文件中Rectangle由lo/hi两个对角Point组成Feature描述某点的特征name为空表示未命名特征RouteNote是途径某点时的备注消息位置 文本RouteSummary汇总行程统计信息点数、经过的特征数、以米为单位的距离、耗时秒数。完整的service RouteGuide定义可查看 examples/route_guide/routeguide/route_guide.proto。使用 protoc 生成客户端与服务端代码定义好服务后使用带 gRPC Go 插件的protoc编译器生成接口代码。在routeguide目录下执行请先确认已安装protoc及两个 Go 插件protoc --go_out. --go_optpathssource_relative --go-grpc_out. --go-grpc_optpathssource_relative route_guide.proto--go_out/--go_optpathssource_relative生成消息代码protocol buffer 的填充、序列化与读取逻辑--go-grpc_out/--go-grpc_optpathssource_relative生成 gRPC 服务代码。在当前目录会生成两个文件原教程只提及route_guide.pb.go实际上自 protoc-gen-go-grpc 独立为插件后gRPC 代码单独存放在第二个文件中route_guide.pb.go——全部消息类型Point、Rectangle、Feature、RouteNote、RouteSummary的填充、序列化、读取代码route_guide_grpc.pb.go——gRPC 相关代码包含供客户端调用的接口类型stubRouteGuideClient供服务端实现的接口类型RouteGuideServer各方法的路由注册、ServiceDesc描述符与 RPC handler。仓库中已生成的代码可以直接查看 examples/route_guide/routeguide/route_guide_grpc.pb.go。从生成代码可以看到现代 gRPC-Go 的两个重要实现细节泛型流接口ListFeatures返回grpc.ServerStreamingClient[Feature]RecordRoute返回grpc.ClientStreamingClient[Point, RouteSummary]RouteChat返回grpc.BidiStreamingClient[RouteNote, RouteNote]同时保留了RouteGuide_ListFeaturesClient等类型别名以保证向后兼容见 route_guide_grpc.pb.go。服务端接口必须内嵌UnimplementedRouteGuideServer该空结构体为所有方法提供了返回codes.Unimplemented的默认实现见 route_guide_grpc.pb.go这样即使.proto后续新增了方法未实现新方法的服务端也不会因接口不匹配而编译失败。创建服务端完整的示例服务端位于 examples/route_guide/server/server.go。让RouteGuide服务跑起来需要两部分工作实现从服务定义生成的服务接口——完成服务的实际业务逻辑运行一个 gRPC 服务器——监听客户端请求并分发给对应的服务实现。实现 RouteGuide 接口服务端定义一个routeGuideServer结构体实现生成的RouteGuideServer接口type routeGuideServer struct { pb.UnimplementedRouteGuideServer savedFeatures []*pb.Feature // read-only after initialized mu sync.Mutex // protects routeNotes routeNotes map[string][]*pb.RouteNote }结构体内嵌了pb.UnimplementedRouteGuideServer以保证向前兼容savedFeatures保存从 JSON 加载的特征数据库routeNotes与互斥锁mu用于在并发场景下保护双向流中积累的备注数据。简单 RPCGetFeatureGetFeature接收客户端的Point从数据库返回对应位置的Featurefunc (s *routeGuideServer) GetFeature(_ context.Context, point *pb.Point) (*pb.Feature, error) { for _, feature : range s.savedFeatures { if proto.Equal(feature.Location, point) { return feature, nil } } // No feature was found, return an unnamed feature return pb.Feature{Location: point}, nil }方法接收一个 RPC 上下文对象和客户端的Point请求返回一个携带响应信息的Feature与一个error。返回nilerror 即告知 gRPC 本 RPC 处理完毕、可将Feature回传给客户端未找到特征时返回一个无名特征name为空这与.proto中的注释语义一致。服务端流式 RPCListFeaturesListFeatures需要向客户端发送多个Feature因此方法参数不再是请求 响应对象而是请求对象Rectangle与一个用于写响应的RouteGuide_ListFeaturesServer流对象func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { for _, feature : range s.savedFeatures { if inRange(feature.Location, rect) { if err : stream.Send(feature); err ! nil { return err } } } return nil }方法内部用stream.Send()逐个写出符合矩形范围的Feature最后返回nilerror 表示响应写毕若中途出错则返回非nilerrorgRPC 层会将其转换为合适的 RPC status 在线上传输。inRange辅助函数在 server.go 中通过经纬度边界比较实现范围判定。客户端流式 RPCRecordRouteRecordRoute从客户端接收一串Point最终返回单个RouteSummary。与之前不同方法没有任何请求参数只拿到一个既可读又可写的RouteGuide_RecordRouteServer流——用Recv()接收客户端消息用SendAndClose()返回唯一的响应func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { var pointCount, featureCount, distance int32 var lastPoint *pb.Point startTime : time.Now() for { point, err : stream.Recv() if err io.EOF { endTime : time.Now() return stream.SendAndClose(pb.RouteSummary{ PointCount: pointCount, FeatureCount: featureCount, Distance: distance, ElapsedTime: int32(endTime.Sub(startTime).Seconds()), }) } if err ! nil { return err } pointCount for _, feature : range s.savedFeatures { if proto.Equal(feature.Location, point) { featureCount } } if lastPoint ! nil { distance calcDistance(lastPoint, point) } lastPoint point } }循环中使用stream.Recv()反复读取客户端请求每次调用后都必须检查返回的 error为nil说明流仍然有效可继续读取为io.EOF说明消息流已结束此时通过SendAndClose()返回行程汇总为其他值则原样返回 error由 gRPC 层转换为 RPC status。距离计算使用 haversine 公式地球半径取 6371000 米实现在 server.go。双向流式 RPCRouteChatRouteChat拿到一个可读可写的RouteGuide_RouteChatServer流。与客户端流式不同的是它会在客户端仍在写消息时就通过流返回响应func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { for { in, err : stream.Recv() if err io.EOF { return nil } if err ! nil { return err } key : serialize(in.Location) s.mu.Lock() s.routeNotes[key] append(s.routeNotes[key], in) // Note: this copy prevents blocking other clients while serving this one. // We dont need to do a deep copy, because elements in the slice are // insert-only and never modified. rn : make([]*pb.RouteNote, len(s.routeNotes[key])) copy(rn, s.routeNotes[key]) s.mu.Unlock() for _, note : range rn { if err : stream.Send(note); err ! nil { return err } } } }读写语法与客户端流式方法类似区别在于这里用Send()而不是SendAndClose()——因为要写多条响应。实现中先用互斥锁保护routeNotes的追加操作再拷贝出当前切片后在锁外发送避免在服务当前客户端时阻塞其他客户端。虽然每侧总是按写入顺序收到对方的消息但双方可以任意顺序读写——两个流完全独立。启动服务器实现完所有方法后需要启动 gRPC 服务器供客户端调用flag.Parse() lis, err : net.Listen(tcp, fmt.Sprintf(localhost:%d, *port)) if err ! nil { log.Fatalf(failed to listen: %v, err) } var opts []grpc.ServerOption if *tls { if *certFile { *certFile data.Path(x509/server_cert.pem) } if *keyFile { *keyFile data.Path(x509/server_key.pem) } creds, err : credentials.NewServerTLSFromFile(*certFile, *keyFile) if err ! nil { log.Fatalf(Failed to generate credentials: %v, err) } opts []grpc.ServerOption{grpc.Creds(creds)} } grpcServer : grpc.NewServer(opts...) pb.RegisterRouteGuideServer(grpcServer, newServer()) grpcServer.Serve(lis)启动流程分为四步指定监听端口lis, err : net.Listen(tcp, fmt.Sprintf(localhost:%d, *port))端口由-port标志控制默认50051见 server.go 的 flag 定义创建 gRPC 服务器实例grpc.NewServer()可传入grpc.ServerOption如 TLS 凭据注册服务实现pb.RegisterRouteGuideServer(grpcServer, newServer())其中newServer()会通过loadFeatures从 testdata/route_guide_db.json 或内嵌的exampleData加载特征数据阻塞服务grpcServer.Serve(lis)阻塞等待直到进程被终止或调用Stop()。默认以明文 TCP 运行使用-tlstrue可启用 TLS此时服务器通过credentials.NewServerTLSFromFile加载证书证书与密钥默认取自examples/data/x509/下的server_cert.pem与server_key.pem通过 examples/data/data.go 中的data.Path辅助函数解析绝对路径。创建客户端完整的示例客户端位于 examples/route_guide/client/client.go。创建 channel 与 stub要调用服务方法首先需要创建与服务器通信的 gRPCchannel。将服务器地址与端口传给grpc.NewClientconn, err : grpc.NewClient(*serverAddr, opts...) if err ! nil { log.Fatalf(fail to dial: %v, err) } defer conn.Close()如果目标服务要求认证可以在grpc.NewClient中通过DialOptions配置认证凭据如 TLS、GCE 凭据、JWT 凭据RouteGuide 服务默认不需要。本示例默认使用insecure.NewCredentials()建立明文连接启用-tlstrue时改用credentials.NewClientTLSFromFile加载 CA 证书并校验服务器主机名默认覆盖主机名x.test.example.com。channel 建立后用.proto生成的pb包提供的构造函数创建客户端 stubclient : pb.NewRouteGuideClient(conn)调用四种服务方法需要说明的是gRPC-Go 的 RPC 采用阻塞/同步模式RPC 调用会等待服务器响应最终返回响应或错误。简单 RPCGetFeature调用简单 RPC 几乎与调用本地方法一样直接feature, err : client.GetFeature(ctx, pb.Point{Latitude: 409146138, Longitude: -746188906}) if err ! nil { ... } log.Println(feature)在方法参数中创建并填充请求协议缓冲区对象这里是Point同时传入context.Context——借助它可以在需要时改变 RPC 行为例如对进行中的 RPC 设置超时或取消。示例中每个调用都通过context.WithTimeout(context.Background(), 10*time.Second)设置了 10 秒超时。调用无错误时可从第一个返回值读取服务器的响应信息。服务端流式 RPCListFeatures调用服务端流式方法ListFeatures会返回一个地理Feature流。与服务端实现类似流式 RPC 在两端实现方式对称rect : pb.Rectangle{ ... } // initialize a pb.Rectangle stream, err : client.ListFeatures(ctx, rect) if err ! nil { ... } for { feature, err : stream.Recv() if err io.EOF { break } if err ! nil { log.Fatalf(%v.ListFeatures(_) _, %v, client, err) } log.Println(feature) }与方法一样传入 context 和请求但返回的不是响应对象而是RouteGuide_ListFeaturesClient流实例用它读取服务器响应。循环中用Recv()反复读取响应消息并检查 errornil表示流有效继续读取io.EOF表示消息流结束其他值则是 RPC 错误通过err透传。客户端流式 RPCRecordRoute客户端流式方法RecordRoute只需传 context返回一个可读可写的RouteGuide_RecordRouteClient流// Create a random number of random points pointCount : int(rand.Int32N(100)) 2 // Traverse at least two points var points []*pb.Point for i : 0; i pointCount; i { points append(points, randomPoint()) } log.Printf(Traversing %d points., len(points)) stream, err : client.RecordRoute(ctx) if err ! nil { log.Fatalf(%v.RecordRoute(_) _, %v, client, err) } for _, point : range points { if err : stream.Send(point); err ! nil { log.Fatalf(%v.Send(%v) %v, stream, point, err) } } reply, err : stream.CloseAndRecv() if err ! nil { log.Fatalf(%v.CloseAndRecv() got error %v, want %v, stream, err, nil) } log.Printf(Route summary: %v, reply)用流的Send()方法向服务器发送请求写完所有请求后调用CloseAndRecv()告知 gRPC 写方已结束并等待接收响应。RPC 状态从CloseAndRecv()返回的err获得若为nil其第一个返回值即为有效的服务器响应。示例中的randomPoint()在经纬度合法范围内随机生成点纬度 ±90 度、经度 ±180 度乘以 1e7。双向流式 RPCRouteChat双向流式RouteChat同样只传 context返回可读可写的流与RecordRoute不同的是可以在服务器仍在写消息时就通过自己的流发送数据。由于读写两个方向并行客户端用一个 goroutine 独立消费接收流stream, err : client.RouteChat(ctx) waitc : make(chan struct{}) go func() { for { in, err : stream.Recv() if err io.EOF { // read done. close(waitc) return } if err ! nil { log.Fatalf(Failed to receive a note : %v, err) } log.Printf(Got message %s at point(%d, %d), in.Message, in.Location.Latitude, in.Location.Longitude) } }() for _, note : range notes { if err : stream.Send(note); err ! nil { log.Fatalf(Failed to send a note: %v, err) } } stream.CloseSend() -waitc读写语法与客户端流式方法几乎一致区别在于发送完毕后调用流的CloseSend()结束本侧写操作。尽管每侧都按写入顺序收到对方消息双方仍可任意顺序读写——两个流完全独立。示例中向 3 个不同位置各发送 2 条备注服务器会回传此前积累在对应位置上的所有备注含刚写入的客户端在 goroutine 中持续打印收到的消息直到收到io.EOF关闭waitc通道后主流程结束。运行示例在examples/route_guide目录下分别启动服务端与客户端见 README.md$ go run server/server.go服务端启动后另开终端运行客户端$ go run client/client.go客户端会依次演示四种 RPC查询(409146138, -746188906)处的已知特征、查询(0, 0)处的缺失特征返回无名特征、在(40,-75)到(42,-73)矩形范围内列出全部特征、随机遍历若干点获取行程汇总、以及多轮备注的双向流聊天。服务端与客户端均支持可选命令行标志例如默认明文运行启用 TLS 的方式如下$ go run server/server.go -tlstrue $ go run client/client.go -tlstrue完整标志列表服务端有-tls、-cert_file、-key_file、-json_db_file、-port客户端有-tls、-ca_file、-addr、-server_host_override具体定义见 server.go 与 client.go。从生成代码看调用链若想进一步理解 gRPC-Go 的底层机制可以研读生成的 route_guide_grpc.pb.go简单 RPC 的客户端方法通过c.cc.Invoke(ctx, RouteGuide_GetFeature_FullMethodName, in, out, cOpts...)完成一次完整调用并自动追加grpc.StaticMethod()选项三种流式方法则通过c.cc.NewStream(ctx, RouteGuide_ServiceDesc.Streams[n], ...)创建底层流再包装为泛型的grpc.GenericClientStream[...]。服务端侧RegisterRouteGuideServer将服务实现与RouteGuide_ServiceDesc一起注册进grpc.Server每种方法都有对应的 handler 负责解码请求、执行实现方法必要时串联拦截器并编码响应。这套定义.proto→ 生成代码 → 实现接口 → 注册并服务的开发范式是使用 gRPC-Go 构建一切服务的通用起点。在此基础上读者可以进一步阅读仓库中的 Documentation 目录了解认证、压缩、keepalive、负载均衡等进阶能力。【免费下载链接】grpc-goThe Go language implementation of gRPC. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-go创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

Lean 期货保证金数据更新指南:基于 CME Performance Bond-Margins 通知维护初始与维持保证金

Lean 期货保证金数据更新指南:基于 CME Performance Bond-Margins 通知维护初始与维持保证金

Lean 期货保证金数据更新指南:基于 CME Performance Bond-Margins 通知维护初始与维持保证金 【免费下载链接】Lean Lean Algorithmic Trading Engine by QuantConnect (Python, C#) 项目地址: https://gitcode.com/GitHub_Trending/le/Lean 导读 本指南面向…

📅 2026/9/13 18:35:04
AG2 记忆接入指南:用 Hindsight 的 retain / recall / reflect 工具为 AG2 智能体构建跨会话长期记忆

AG2 记忆接入指南:用 Hindsight 的 retain / recall / reflect 工具为 AG2 智能体构建跨会话长期记忆

AG2 记忆接入指南&#xff1a;用 Hindsight 的 retain / recall / reflect 工具为 AG2 智能体构建跨会话长期记忆 【免费下载链接】hindsight Hindsight: Agent Memory That Learns 项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight <output_art…

📅 2026/9/13 18:35:04
Telegraf 输出缓冲策略全解:从内存环形队列到 WAL 磁盘持久化(TSD-005 规范与实现)

Telegraf 输出缓冲策略全解:从内存环形队列到 WAL 磁盘持久化(TSD-005 规范与实现)

Telegraf 输出缓冲策略全解&#xff1a;从内存环形队列到 WAL 磁盘持久化&#xff08;TSD-005 规范与实现&#xff09; 【免费下载链接】telegraf Agent for collecting, processing, aggregating, and writing metrics, logs, and other arbitrary data. 项目地址: https://…

📅 2026/9/13 18:35:04
MORE NEWS

更多资讯

📰

基于51单片机的8键电子琴设计与仿真

简介&#xff1a;这是一份基于51单片机实现8键电子琴的完整毕业设计资料&#xff0c;适合电子信息、嵌入式相关专业学生用于课程设计、毕业设计或单片机入门实践。资源围绕电子琴的硬件搭建与软件编程展开&#xff0c;覆盖按键扫描、音符识别、频率生成及时序控制等核心模块&am…

📰

Linux驱动开发完整路径:内核模块、设备树与I2C/CAN实战解析

做Linux驱动开发这些年&#xff0c;我见到最多的问题不是代码有多难写&#xff0c;而是很多人把驱动开发理解成了“写一个C文件”。尤其当你刚接触内核模块、设备树、I2C、CAN这些名词时&#xff0c;每一个单独拿出去都能搜到教程&#xff0c;但真到了板子上&#xff0c;把它们…

📰

DESIGN.md 颜色令牌怎么定 primary、secondary、tertiary、neutral?palette 命名约定与 missing-primary 警告解析

DESIGN.md 颜色令牌怎么定 primary、secondary、tertiary、neutral&#xff1f;palette 命名约定与 missing-primary 警告解析 【免费下载链接】design.md A format specification for describing a visual identity to coding agents. DESIGN.md gives agents a persistent, s…

📰

FOC电流采样全解析:单/双/三电阻拓扑与中心对齐PWM的ADC触发要点

做FOC调试这些年&#xff0c;我最深的感受是&#xff1a;炸机不可怕&#xff0c;可怕的是不知道为什么炸。而十次炸机&#xff0c;有七八次都跟电流采样脱不了干系。你可能已经把SVPWM扇区推导背得滚瓜烂熟&#xff0c;PI参数也算得头头是道&#xff0c;但只要电流采样在这个链…

📰

MaaAssistantArknights 连接 Android 实体设备全指南:ADB 调试、分辨率适配与无线连接

MaaAssistantArknights 连接 Android 实体设备全指南&#xff1a;ADB 调试、分辨率适配与无线连接 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手&#xff0c;全日常一键长草&#xff01;| A one-click tool for the daily tasks of Arknights, supporting all cl…

📰

MaaAssistantArknights 基建换班深度解析:常规模式调度算法、跨设施组合与自定义排班协议

MaaAssistantArknights 基建换班深度解析&#xff1a;常规模式调度算法、跨设施组合与自定义排班协议 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手&#xff0c;全日常一键长草&#xff01;| A one-click tool for the daily tasks of Arknights, supporting all…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬