对称和反向gRPC连接【译】

原文链接为 https://tilde.town/~hut8/post/grpc-connections/ 使用kimi进行翻译 背景 gRPC是一项出色的现代技术,用于远程过程调用。它允许你在客户端创建一个“存根”对象,该对象的目的是调用服务器上的方法。它是许多情况下REST或GraphQL的绝佳替代品,通常值得学习这项技术。更多信息可以在官方文档中找到。 网络 概念上,我们对客户端和服务器有两种不同的想法。 TCP服务器和客户端 - gRPC在HTTP/2之上运行,HTTP/2在TCP之上运行,所以我将讨论TCP中“服务器”和“客户端”的含义。在TCP连接中,客户端是连接的发起者,服务器是连接的接收者。然而,一旦建立了连接,连接就是对称的;客户端和服务器都可以发送和接收消息,直到一方通过shutdown(2)关闭,或通过close(2)关闭连接。 gRPC服务器和客户端 - gRPC客户端通过存根调用在服务器上运行的方法。这不是对称的。服务器不能在客户端上调用方法。 服务器/客户端类型耦合问题 如果你有一个gRPC客户端,它也是TCP客户端(它调用connect(2))。如果你有一个gRPC服务器,它也是TCP服务器(它调用listen(2)和accept(2))。 因此,如果你想让两台机器可以相互调用gRPC,那么每台机器都是客户端和服务器(在TCP和gRPC两种意义上),现在你有了两个与彼此无关的TCP连接。在有两朵云实例的场景中,这种架构通常并不复杂。但TCP通常很混乱。防火墙、NAT和动态分配的IP地址可能会使客户端到服务器的单向连接变得复杂。 这里有一个这样的情况的例子:假设你的“服务器”是一个在家庭路由器后面的笔记本电脑上运行的程序,它必须接收命令(例如,远程控制)来自“客户端”(例如,云计算实例)。根据gRPC,客户端必须是云计算实例(这很容易被称为“服务器”),因为那是创建远程过程调用的一方。服务器是笔记本电脑,因为那是实际发生过程调用的地方。 当你在客户端(即云计算实例)上创建一个“存根”时,你必须创建一个TCP连接并连接到服务器(即笔记本电脑)。现在你遇到了几个可能的问题: 你不知道笔记本电脑的IP地址,所以你现在需要一个反向服务,让笔记本电脑告诉云它的IP地址。 笔记本电脑可能在NAT后面,所以一旦你找到了IP地址,笔记本电脑将不得不配置端口转发。这在企业环境中很可能是不可能的。 笔记本电脑可能在防火墙后面,这将禁止传入连接。这可能是本地机器上的,也可能是在网关路由器上的。 这些问题中的一些可能是无法解决的,所以我们需要另一种方法。 gRPC独有的解决方案 服务器不能在客户端gRPC上调用方法。也许你可以让客户端在服务器上调用一个方法,其唯一目的是接收描述服务器希望客户端运行的方法的消息。这可以通过流式响应来完成。但这很复杂,需要大量的代码来绕过gRPC的设计。我在其他地方看到过这个建议,但我认为这是一个丑陋的临时解决方案,它制造的问题比它解决的还要多。考虑一下,你将如何在静态类型的方式中实际调用这些“客户端方法”。 基于隧道的解决方案 将TCP客户端/服务器从gRPC客户端/服务器中解耦的一个好方法是实现某种隧道。在上面的例子中,笔记本电脑可以向云计算实例发起一个TCP连接,然后通过实现特定于语言的接口,“拨号”操作让云计算实例(gRPC客户端)连接到笔记本电脑(gRPC服务器)可以简单地使用现有的TCP连接。 SSH是一个不可思议的协议,它的用途比大多数用户知道的还要多。在我们的情况下,它是将我们的TCP连接从gRPC连接中解耦的完美方式。它还有其他好处:尽管gRPC提供认证和加密,但如果更方便,你可以使用SSH提供的。 这些例子是Go语言特有的,但你可以在任何语言中做类似的事情。gRPC服务器不需要监听端口;你可以传入任何实现了Go的net.Listener的类型。所以我们可以做一个net.Listener,它将接受SSH连接,任何时候请求我们的自定义类型的新SSH通道,我们将接受它并返回一个新的net.Conn,这是我们将实现的另一个类型,它只是通过我们的隧道传输数据。 让我们从SSHDataTunnel开始,它是我们的net.Conn。 import ( "net" "time" "golang.org/x/crypto/ssh" ) // SSHDataTunnel实现了net.Conn type SSHDataTunnel struct { Chan ssh.Channel Conn net.Conn } func NewSSHDataTunnel(sshChan ssh.Channel, carrier net.Conn) *SSHDataTunnel { return &SSHDataTunnel{ Chan: sshChan, Conn: carrier, } } func (c *SSHDataTunnel) Read(b []byte) (n int, err error) { return c....

在Go语言中使用Arrow、Flight和Duckdb

从duckdb开始 DuckDB 是一个嵌入式分析型数据库,专为 OLAP(在线分析处理)工作负载设计。本文将基于 go-duckdb 项目的示例,详细介绍 DuckDB 在 Go 语言中的各种使用场景。 基础使用 简单查询 package main import ( "database/sql" _ "github.com/marcboeker/go-duckdb" ) func main() { db, err := sql.Open("duckdb", ":memory:") if err != nil { panic(err) } defer db.Close() // 执行查询 rows, err := db.Query("SELECT 42") if err != nil { panic(err) } defer rows.Close() } 高级特性 Copy COPY 函数可以用于导入导出数据: package main import ( "database/sql" "fmt" _ "github.com/marcboeker/go-duckdb" ) func main() { db, err := sql....

在Go语言中处理带BOM的json数据

缘起 今天开发的时候遇到一个奇怪的问题,一个JSON文件,使用文本编辑器打开复制,并使用strings.NewReader来decode,是正常的,但是通过文件打开同样调用的方法来decode,却是失败的。后面通过打开IDE,发现文件前面有一些空白的内容。是一些bom信息。 关于BOM BOM (Byte Order Mark) 的历史原因和用途主要与字符编码和跨平台兼容性有关: 历史原因 Unicode 出现前: ASCII 只用 1 字节,没有字节序问题 各国有自己的编码标准(GB2312、Shift-JIS等) Unicode 引入后: UTF-16 使用 2 字节表示字符 不同CPU架构的字节序不同: Big Endian (大端序): 高位字节在前 Little Endian (小端序): 低位字节在前 跨平台问题: Intel x86 使用小端序 Motorola 68k 使用大端序 同一文件在不同平台解析可能出错 BOM 的作用 UTF-16 的字节序标记: FE FF: Big Endian FF FE: Little Endian UTF-8 的编码标识: EF BB BF: 表明这是 UTF-8 编码 UTF-8 实际不需要 BOM(字节序无关) Windows 添加 BOM 主要为了兼容性 实际例子 // 字符 "中" 在不同编码下的表示 text := "中" // UTF-8: E4 B8 AD // UTF-16BE: 4E 2D // UTF-16LE: 2D 4E // 示例代码 func showEncoding() { text := "中" utf8Bytes := []byte(text) // UTF-8 utf16beBytes := utf16....

Rust正则表达式实践

正则表达式是一种强大的文本处理工具。在 Rust 中,我们主要通过 regex crate 来使用正则表达式。让我们通过实际案例来学习。 1. 基础匹配 use regex::Regex; fn main() { // 创建正则表达式对象 let re = Regex::new(r"\d+").unwrap(); // 测试匹配 let text = "The year is 2024"; if re.is_match(text) { println!("Found a number!"); } // 提取匹配内容 if let Some(matched) = re.find(text) { println!("Found number: {}", matched.as_str()); } } 2. 处理重复单词 根据文章中提到的一个常见问题 - 查找重复单词: use regex::Regex; fn remove_duplicate_words(text: &str) -> String { // (\w+) 捕获一个单词,\s+匹配空白字符,\1 回引用前面捕获的单词 let re = Regex::new(r"(\w+)\s+\1").unwrap(); re....

一些任天堂switch资源

最近整了个硬破的Switch,整理了下相关资源方便查找。 固件和系统 软破 新版本都是硬破了 TegraRCM: https://github.com/eliboa/TegraRcmGUI/releases 大气层 设备需要硬破 大气层系统 https://github.com/Atmosphere-NX/Atmosphere ak大气层整合包 https://github.com/AK478BB/AK-Atmosphere/releases 大气层集成sys-patch插件破解包 https://github.com/laila509/Atmosphere-syspatch Hekate引导: https://github.com/CTCaer/hekate/releases Switch固件 模拟器固件和key https://prodkeys.net/ Ryujinx 模拟器 Retroarch Switch官方固件分享下载 https://github.com/THZoria/NX_Firmware/ 其他 Check If Your Switch Is Patched: https://ismyswitchpatched.com/ Sig Patches: https://sigmapatches.su https://rentry.org/EristaEmuNAND Sig Patches: https://gbatemp.net/threads/sigpatches-for-atmosphere-hekate-fss0-fusee-package3.571543/ EaseUs Partition: https://www.easeus.com/partition-manager/epm-free.html switch主题 https://themezer.net 游戏下载 网盘资源 switch游戏下载 https://www.123pan.com/s/sl82jv-6JVV3.html 网站或论坛 悠游任天堂 2cyshare Switch520 rutracker的switch板块 上游世界 nsw2u Game中文网 switch主题 相关文章及博客 Switch 软破、硬破总结 字节智造 其他 BYRUTOR steam游戏破解下载 skidrowreloaded steamunlocked Online Fix koyso

Connect Rpc快速上手

入门 Connect RPC 是一个轻量级的 HTTP API 构建库,支持浏览器和 gRPC 兼容的 API。它通过 Protocol Buffer 定义服务,并生成类型安全的服务器和客户端代码。 压缩和序列化 Connect 支持多种压缩和序列化选项,默认情况下,Connect 处理程序支持在默认压缩级别使用标准库的 compress/gzip 进行 gzip 压缩。Connect 客户端默认发送未压缩的请求并请求 gzip 压缩的响应。如果您知道服务器支持 gzip,则还可以在客户端构建期间使用 WithSendGzip 选项来压缩请求。 // 服务端不需要进行什么选项设置 参考https://github.com/connectrpc/connect-go/issues/773 handler := greetv1connect.NewGreetServiceHandler( &GreetServer{}, ) // 客户端配置压缩,默认 client := greetv1connect.NewGreetServiceClient( http.DefaultClient, "http://localhost:8080", connect.WithSendGzip(), ) 自定义压缩实现: type CustomCompressor struct{} func (c *CustomCompressor) Name() string { return "custom" } func (c *CustomCompressor) Compress(w io.Writer) (io.WriteCloser, error) func (c *CustomCompressor) Decompress(r io.Reader) (io.Reader, error) Get 请求 Connect 支持通过 HTTP GET 进行无副作用的请求,这使得可以在浏览器、CDN 或代理中缓存某些类型的请求。...

Go Ble开发实战

⚠️代码仅最后部分进行了测试,前面的暂未测试 1. 简介 BLE (Bluetooth Low Energy) 是一种低功耗蓝牙技术。Go-BLE 是 Go 语言的 BLE 库,提供了简单易用的 API 来开发 BLE 应用。本文将通过一个完整的示例来展示如何使用 Go-BLE 创建一个蓝牙服务器。 Go-BLE 主要支持 Linux 和 macOS 平台(1),但需要注意 macOS 部分目前并未被积极维护。 2. 环境准备 2.1 安装依赖 # 安装 go-ble go get -u github.com/go-ble/ble # 安装设备支持库 go get -u github.com/go-ble/ble/examples/lib/dev # Linux系统需要设置权限 sudo setcap 'cap_net_raw,cap_net_admin+eip' ./your_program 3. BLE基础概念 3.1 核心概念 Peripheral (外围设备):提供服务的设备 Central (中心设备):连接外围设备的设备(如手机) Service (服务):功能的集合 Characteristic (特征):具体的数据点 Descriptor (描述符):特征的元数据 3.2 常见的 UUID 标准服务 UUID: 电池服务:0x180F 设备信息服务:0x180A 心率务:0x180D 标准特征 UUID: 电池电量:0x2A19 设备名称:0x2A00 制造商名称:0x2A29 型号名称:0x2A24 序列号:0x2A25 固件版本:0x2A26 信号强度:0x2A1C 自定义 UUID 生成: // 使用在线工具生成 UUID v4 svcUUID := ble....

使用 Suture在Go中实现可靠的监督树

简介 在构建复杂的分布式系统时,我们常常需要面对各种意外情况,如服务崩溃、网络中断等。为了提高系统的可靠性和容错能力,监督树模式应运而生。Suture 是一个受 Erlang OTP 框架启发的 Go 语言监督树库,它为 Go 开发者提供了一种优雅的方式来管理和监控长时间运行的服务。 监督树的核心思想是将系统组织成一个树状结构,其中父节点(监督者)负责监控和管理子节点(工作者)。当子节点发生故障时,父节点可以根据预定策略进行重启或其他恢复操作,从而提高系统的整体稳定性。 Suture 的核心概念 Suture 的设计围绕以下几个核心概念: Service 接口: 定义了可被监督的服务应该实现的方法。 Supervisor 结构体: 代表一个监督者,负责管理一组服务。 重启策略: 定义了当服务失败时,监督者应该如何响应。 安装和基本使用 首先,通过以下命令安装 Suture: go get github.com/thejerf/suture/v4 然后,在你的 Go 代码中导入 Suture: import "github.com/thejerf/suture/v4" 创建一个简单的 Service: type MyService struct{} var _ suture.Service = (*MyService)(nil) func (s *MyService) Serve(ctx context.Context) error { for { select { case <-ctx.Done(): return nil default: // 执行服务逻辑 time.Sleep(time.Second) fmt.Println("Service is running") } } } 设置 Supervisor 并添加 Service:...

rust中map_error的用法

Rust中的map_err方法是Result类型的一个方法,用于转换错误类型。它允许你在保持Ok值不变的情况下,修改Err值。这在错误处理和类型转换中非常有用。 以下是map_err的详细用法和一些例子: 基本用法 fn main() -> Result<(), String> { let result: Result<i32, &str> = Err("原始错误"); let mapped = result.map_err(|e| e.to_string()); mapped } 在这个例子中,我们将&str类型的错误转换为String类型。 链式调用 use std::num::ParseIntError; fn parse_and_multiply(s: &str) -> Result<i32, String> { s.parse::<i32>() .map_err(|e: ParseIntError| e.to_string()) .map(|n| n * 2) } fn main() { match parse_and_multiply("10") { Ok(n) => println!("结果: {}", n), Err(e) => println!("错误: {}", e), } match parse_and_multiply("abc") { Ok(n) => println!("结果: {}", n), Err(e) => println!("错误: {}", e), } } 这个例子展示了如何在解析字符串并进行计算的过程中使用map_err转换错误类型。...

Rust derive_builder快速使用参考

derive_builder是 简化Rust结构体构建的强大工具,本文使用claude 3.5 生成 1. 引言 在Rust编程中,创建复杂的结构体实例常常需要大量的样板代码。derive_builder 是一个强大的过程宏库,它可以自动为结构体生成 builder 模式的实现,大大简化了结构体的创建过程。本文将深入介绍 derive_builder 的使用方法、特性和最佳实践。 2. 基本用法 2.1 安装 首先,在你的 Cargo.toml 文件中添加 derive_builder 依赖: [dependencies] derive_builder = "0.20.0" 2.2 简单示例 让我们从一个简单的例子开始: use derive_builder::Builder; #[derive(Builder, Debug)] struct Server { host: String, port: u16, } fn main() { let server = ServerBuilder::default() .host("localhost".to_string()) .port(8080) .build() .unwrap(); println!("{:?}", server); } 在这个例子中,我们为 Server 结构体派生了 Builder 特征。这会自动生成一个 ServerBuilder 结构体,它提供了流畅的 API 来构建 Server 实例。 3. 字段属性 derive_builder 提供了多种属性来自定义字段的行为。...