GOLANG笔记第四周

devtools/2024/11/20 5:42:31/

什么是RPC,RPC的挑战是什么?

在这里插入图片描述

而对于远程过程面临的一些问题:

  1. 原本的本地函数放到另一个服务器上运行,但是引入了很多新问题
  2. Call 的id映射
  3. 序列化和反序列化
  4. 网络传输

Call 的id映射

当客户端发起一个远程调用时,它会为每个调用分配一个唯一的 Call ID。这个 Call ID 用于在服务器端准确地识别客户端请求的是哪个具体的函数调用,并且在服务器返回结果时,客户端也可以通过这个 Call ID 来确定返回的结果对应的是哪一个调用请求。

序列化和反序列化

序列化是将数据结构或对象转换为可以在网络上传输或者存储的格式(通常是字节流)的过程

与序列化相反,反序列化是将接收到的字节流重新转换为原来的数据结构或对象的过程。

package mainimport "fmt"func Add(a, b int) int {total := a + breturn total
}type Company struct {Name    stringAddress string
}type Employee struct {Name    stringcompany Company
}type PrintResult struct {Info stringErr  error
}func RpcPrintln(employee Employee) PrintResult {/*客户端1.建立连接 tcp/http2.将employee对象序列化成json字符串 - 序列化3.发送json字符串 -- 调用成功后接受到的是二进制数据4.等待服务器发送结果5.将服务器返回的数据解析成PrintResult对象 -- 反序列化服务端1.监听网络端口 802.读取数据 -- 二进制的json数据3.对数据反序列化成Employee4.开始处理业务逻辑5.将处理的结果PrintResult序列化成json二进制数据6.将数据返回序列化和反序列化是可以选择的,不一定是json而在rpc中,*/}func main() {//现在我们想把Add变成一个远程的函数调用,也就意味着需要把Add函数放在远程服务器上运行//一定会牵扯到网络,做成一个web服务(gin,beego,net/httpserver)/*1.这个函数的调用参数如何传递-json(json是一种数据格式的协议)  xml/protobuf/msgpack现在网络调用有两个端 - 客户端,应该将数据传输到gingin -- 服务端,服务端负责解析数据*/fmt.Println(Add(1, 2))//将这个打印的工作放在另一台服务器上,我需要将本地的内存对象 struct,这样不行//可行的方式就是将struct序列化成json对象//远程的服务器需要将二进制对象反解成struct对象fmt.Println(Employee{Name: "tc",company: Company{Name:    "tc",Address: "深圳",},})}

那么,直接全部使用json去格式化不行吗?
这种做法在浏览器和gin服务之间是可行的,但如果你是一个大型的分布式系统,那么会很难维护,基本是不行的

在 RPC 中,数据需要在不同的进程或者机器之间传输。由于网络只能传输字节流,所以必须将对象或数据结构进行序列化才能进行传输。比如,一个客户端在调用远程服务器上的一个函数时,需要把函数的参数进行序列化后发送给服务器,这样服务器才能接收到这些参数。

网络传输

http1.x 和 http2.0协议

http协议底层使用的也是tcp,http现在主流的是1.x 这种协议有性能问题(一次性)
一旦结果返回,连接就断开
1.直接自己基于tcp/udp协议 myhttp,但是没用通用性 – http2.0既有http特性,也有长连接特性

demo:

服务端
package mainimport ("encoding/json""fmt""net/http""strconv"
)func main() {// http//127.0.0.1:8000/add?a=1&b=2//Callid 问题:r.URL.Path//数据的传输协议 URL的协议//网络传输协议 httphttp.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {_ = r.ParseForm() //解析参数fmt.Println("path: ", r.URL.Path)a, _ := strconv.Atoi(r.Form["a"][0])b, _ := strconv.Atoi(r.Form["b"][0])//返回的格式化,json{"data":3}w.Header().Set("Content-Type", "application/json")jData, _ := json.Marshal(map[string]int{"data": a + b,})_, _ = w.Write(jData)})_ = http.ListenAndServe(":8000", nil)}
客户端
package mainimport ("encoding/json""fmt""github.com/kirinlabs/HttpRequest"
)
type ResponseData struct {Data string `json:"data"`
}func Add(a,b int) int{req := HttpRequest.NewRequest()res, _ := req.Get(fmt.Sprintf("http://127.0.0.1:8000/%s?a=%d&b=%d","add",a,b))body, _ := res.Body()//fmt.Println(string(body))rspData := ResponseData{}_ = json.Unmarshal(body, &rspData)return rspData.Data
}func main() {fmt.Println(Add(2, 2))}

rpc开发四大要素

客户端,客户端存根,服务端,服务端存根

在这里插入图片描述

快速体验rpc开发

rpc三步走
1.实例化一个server
2.注册处理逻辑handler
3.启动服务

对应实例代码:

服务端代码

package mainimport ("net""net/rpc"
)type HelloService struct{}func (s *HelloService) Hello(request string, reply *string) error {*reply = "Hello " + requestreturn nil
}func main() {//三步走//1.实例化一个server//监听端口listener, _ := net.Listen("tcp", ":1234")//2.注册处理逻辑handler_ = rpc.RegisterName("HelloService", &HelloService{})//3.启动服务conn, _ := listener.Accept()rpc.ServeConn(conn)
}

服务端代码运行无误后
运行客户端代码

package mainimport ("fmt""net/rpc"
)func main() {//1、建立连接client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {panic("连接失败")}var reply *stringerr = client.Call("HelloService.Hello", "bobby", reply)if err != nil {panic("调用失败")}fmt.Println(reply)//2、}

打印“调用失败”

debug
在这里插入图片描述

错误是,我们初始化了一个指针变量 var reply *string 初始化的时候的nil的,然后我们client.Call("HelloService.Hello", "bobby", reply)把nil指针放到了服务器上去。
而服务器对没用指向任何地方的指针nil加值,当然是不可以的。

我们可以使用new来初始化, var reply *string = new(string)

在这里插入图片描述
成功

当然也可以直接使用string类型初始化reply,记得加&符

	var reply stringerr = client.Call("HelloService.Hello", "tc", &reply)

在这里插入图片描述
成功

问题:
在server中,我们监听用到是net.Listen("tcp", ":1234")
接受用的是listener.Accept()
是不是没有rpc也可以呢?

不是的
在rpc中有几个问题需要解决
1.call id
2.序列化和反序列化
这两个工作,其实net是没有完成的,都是我们的rpc完成的

问题又来了,我们使用远程服务器的函数hello时
使用的是client.Call("HelloService.Hello", "tc", &reply)

但是我们实际上想要的是和本地开发一样
直接 client.Hello("tc", &reply)
可以吗?
可以,我们需要自己封装

替换rpc的序列化协议为json

go 语言的rpc的序列化和反序列化协议是什么(Gob)

能否替换成常见的序列化?可以

把之前server中的

	rpc.ServeConn(conn)

替换成

	rpc.ServeCodec(jsonrpc.NewServerCodec(conn))

server端代码改成

package mainimport ("net""net/rpc""net/rpc/jsonrpc"
)type HelloService struct{}func (s *HelloService) Hello(request string, reply *string) error {*reply = "Hello " + requestreturn nil
}func main() {//三步走//1.实例化一个server//监听端口listener, _ := net.Listen("tcp", ":1234")//2.注册处理逻辑handler_ = rpc.RegisterName("HelloService", &HelloService{})//3.启动服务for {conn, _ := listener.Accept()rpc.ServeCodec(jsonrpc.NewServerCodec(conn))}
}

直接使用net监听

protobuf和json直观对比

package mainimport ("9_22_helloworld/helloworld/proto""encoding/json""fmt""github.com/golang/protobuf/proto"
)type Hello struct {Name string `json:"name"`
}func main() {req := helloworld.HelloRequest{Name: "tc",}jsonStruct := Hello{Name: "tc"}jsonRsp, _ := json.Marshal(jsonStruct)fmt.Println(string(jsonRsp))rsp, _ := proto.Marshal(&req)fmt.Println(string(rsp))}

在这里插入图片描述
长度比是16:7

使用更大的数据来测试比较
在这里插入图片描述

比较len()
在这里插入图片描述
可以看出差异仍然很大
protobuf的效率比json高出好多

检查完了传输效率,检查是否能正常解析unmarshal()

	rsp, _ := proto.Marshal(&req)newReq := &helloworld.HelloRequest{}_ = proto.Unmarshal(rsp, newReq)fmt.Println(newReq.Name, newReq.Age, newReq.Courses)

在这里插入图片描述
没问题

解析
protoc -I . helloworld.proto --go_out=. --go-grpc_out=.

-I 表示(INPUT)
"."表示当前目录之下
helloworld.proto 就是要解析的文件名
“- -go_out” 表示生成go的文件

rpc的四种模式

简单模式(simple rpc)

客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的RPC没什么太大的区别

服务端数据流模式(server-side streaming rpc)

客户端发起一次请求,服务端返回一段连续的数据流。典型的是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。

订阅模式

客户端数据流模式(client-side streaming rpc)

与服务端相反,客户端源源不断的向服务端发送数据流,在发送结束后,服务端返回一个响应,典型的例子就是,物联网终端向服务器报送数据。

双向数据流模式(bidirectional streaming rpc)

客户端和服务端都可以向对方发送数据,这个时候双方的数据可以同时互相发送,实时交互。典型的例子就是聊天机器人。

使用proto创建项目

syntax = "proto3";option go_package=".;proto";service Greeter{rpc GetStream(StreamReqData) returns (stream StreamResData);//服务端流模式rpc PostStream(stream StreamReqData) returns (StreamResData);// 客户端流模式rpc AllStream(stream StreamReqData) returns (stream StreamResData);// 客户端流模式
}message StreamReqData{string data = 1;
}message StreamResData{string data = 1;
}

之后建立服务端

package serverimport ("context""google.golang.org/grpc""mygogongchengshi/stream_grpc_test/proto""net"
)const PORT = ":50052"type server struct{}func (s *server) GetStream(ctx context.Context, request proto.StreamReqData) (*proto.StreamResData, error) {return nil, nil
}func main() {lis, err := net.Listen("tcp", PORT)if err != nil {panic(err)}s := grpc.NewServer()proto.RegisterGreeterServer(s, &server{})
}

会发现最后一行报错,
在这里插入图片描述
原因是我们的PostStream和AllStream没有完成

而当我们用同样的方法创建PostStream和AllStream之后
依然报错
在这里插入图片描述

因为参数实际上是错误的

修改后

package serverimport ("google.golang.org/grpc""mygogongchengshi/stream_grpc_test/proto""net"
)const PORT = ":50052"type server struct {proto.UnimplementedGreeterServer
}func (s *server) GetStream(req *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {return nil
}func (s *server) PostStream(cliStr proto.Greeter_PostStreamServer) error {return nil
}func (s *server) AllStream(allStr proto.Greeter_AllStreamServer) error {return nil
}func main() {lis, err := net.Listen("tcp", PORT)if err != nil {panic(err)}s := grpc.NewServer()proto.RegisterGreeterServer(s, &server{})
}

注意:
在这里插入图片描述
新版go中需要额外实现一个接口UnimplementedGreeterServer()将他嵌入到结构体中

单向流,双向流代码实现

server总代码

package mainimport ("fmt""google.golang.org/grpc""mygogongchengshi/stream_grpc_test/proto""net""sync""time"
)const PORT = ":50052"type server struct {proto.UnimplementedGreeterServer
}func (s *server) GetStream(req *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {i := 0for {_ = res.Send(&proto.StreamResData{Data: fmt.Sprintf("%v", time.Now().Unix()),})i++time.Sleep(time.Second)if i > 10 {break}}return nil
}func (s *server) PostStream(cliStr proto.Greeter_PostStreamServer) error {for {if a, err := cliStr.Recv(); err != nil {fmt.Println(err)break} else {fmt.Println(a.Data)}}return nil
}func (s *server) AllStream(allStr proto.Greeter_AllStreamServer) error {wg := sync.WaitGroup{}wg.Add(2)go func() {defer wg.Done()for {data, _ := allStr.Recv()fmt.Println("收到客户端消息:" + data.Data)}}()go func() {defer wg.Done()for {_ = allStr.Send(&proto.StreamResData{Data: "你好,我是服务器"})time.Sleep(time.Second)}}()wg.Wait()return nil
}func main() {lis, err := net.Listen("tcp", PORT)if err != nil {panic(err)}s := grpc.NewServer()proto.RegisterGreeterServer(s, &server{})err = s.Serve(lis)if err != nil {panic(err)}
}

首先测试client为服务端数据流
客户端发起一次请求,服务端返回一段连续的数据流。我们就让服务端每个一秒,返回一个时间戳

client代码(服务端流模式)

package mainimport ("context""fmt""google.golang.org/grpc""mygogongchengshi/stream_grpc_test/proto"
)func main() {conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())if err != nil {panic(err)}defer conn.Close()c := proto.NewGreeterClient(conn)res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "mooc"})for {a, err := res.Recv() //socket编程 send,recvif err != nil {fmt.Println(err)break}fmt.Println(a)}}

可以看到结果

在这里插入图片描述
接下来是client–(客户端流模式)

添加:
//客户端流模式posts, _ := c.PostStream(context.Background())i := 0for {i++posts.Send(&proto.StreamReqData{Data: fmt.Sprintf("mooc%d", i),})time.Sleep(time.Second)if i > 10 {break}}

在客户端如之前一样输出了10次时间后
在这里插入图片描述
服务端开始输出
在这里插入图片描述
接下来是双向流模式(聊天模式)

package mainimport ("context""fmt""google.golang.org/grpc""mygogongchengshi/stream_grpc_test/proto""sync""time"
)func main() {conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())if err != nil {panic(err)}defer conn.Close()c := proto.NewGreeterClient(conn)res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "mooc"})for {a, err := res.Recv() //socket编程 send,recvif err != nil {fmt.Println(err)break}fmt.Println(a)}//客户端流模式posts, _ := c.PostStream(context.Background())i := 0for {i++posts.Send(&proto.StreamReqData{Data: fmt.Sprintf("mooc%d", i),})time.Sleep(time.Second)if i > 10 {break}}//双向流模式allStr, _ := c.AllStream(context.Background())wg := sync.WaitGroup{}wg.Add(2)go func() {defer wg.Done()for {data, _ := allStr.Recv()fmt.Println("收到服务端消息:" + data.Data)}}()go func() {defer wg.Done()for {_ = allStr.Send(&proto.StreamReqData{Data: "你好,我是客户端mooc"})time.Sleep(time.Second)}}()wg.Wait()}

在这里插入图片描述
在这里插入图片描述
可以看到,在完成之前的两个模式之后,进入双向流模式
两边同时开始,互发消息
完成


http://www.ppmy.cn/devtools/135398.html

相关文章

AI 技术在旅游和酒店行业的应用前景

目录 引言 第一章 AI 技术在旅游和酒店行业的现状 1.1 旅游行业的智能化体验 1.2 酒店行业的智能化管理 第二章 AI 技术在旅游和酒店行业的优势 2.1 提升用户体验的智能服务 2.2 优化运营效率,降低成本 2.3 增强安全性与数据分析能力 第三章 AI 技术对旅游和…

【从零开始的LeetCode-算法】3270. 求出数字答案

给你三个 正 整数 num1 &#xff0c;num2 和 num3 。 数字 num1 &#xff0c;num2 和 num3 的数字答案 key 是一个四位数&#xff0c;定义如下&#xff1a; 一开始&#xff0c;如果有数字 少于 四位数&#xff0c;给它补 前导 0 。答案 key 的第 i 个数位&#xff08;1 < …

Debian 11(Bullseye)上安装 MySQL 的 ODBC 驱动程序

在 Debian Bullseye 上&#xff0c;您可以尝试安装 mysql-connector-odbc&#xff0c;但如果该包不可用&#xff0c;您可以尝试安装 libmyodbc 的替代品: 步骤 1&#xff1a;安装 odbc-mariadb 如果您还没有安装 odbc-mariadb&#xff0c;可以使用以下命令进行安装&#xff1…

云计算研究实训室建设方案

一、引言 随着云计算技术的迅速发展和广泛应用&#xff0c;职业院校面临着培养云计算领域专业人才的迫切需求。本方案旨在构建一个先进的云计算研究实训室&#xff0c;为学生提供一个集理论学习、实践操作、技术研发与创新于一体的综合性学习平台&#xff0c;以促进云计算技术…

Spring Security 核心组件

Spring Security 是一个功能全面的安全框架&#xff0c;用于处理基于 Spring 应用程序的身份验证和授权。 它提供了开箱即用的支持&#xff0c;采用行业标准的做法和机制来保护你的应用。 无论你是开发简单的 Web 应用还是复杂的微服务架构&#xff0c;理解 Spring Security …

CSM32RV20:RISC-V核的低功耗MCU芯片,常用在智能门锁上

CSM32RV20是一款基于RISC-V核的低功耗MCU芯片。 内置RISC-V RV32IMAC内核&#xff08;2.6CoreMark/MHz&#xff09;&#xff1b; 蕞高32MHz工作频率&#xff1b; 内置4kB的SRAM&#xff1b; 内置8B的ALWAYS寄存器&#xff0c;能在掉电模式2下保存数据&#xff1b; 内置40kB的嵌…

执行flink sql连接clickhouse库

手把手教学&#xff0c;flink connector打通clickhouse大数据库&#xff0c;通过下发flink sql&#xff0c;来使用ck。 组件版本jdk1.8flink1.17.2clickhouse23.12.2.59 1.背景 flink官方不支持clickhouse连接器&#xff0c;工作中难免会用到。 2.方案 利用GitHub大佬提供…

Vue实现消息提示功能

1.首先要先定义消息提示的组件&#xff0c;在这个组件中需要实现自动关闭的功能&#xff08;看自己的爱好呗&#xff09;&#xff0c;并且设置自己喜欢的样式&#xff0c;vue中还有可以自定义进场和退场动画的样式&#xff08;就是那个v-enter-active和v-leave-active&#xff…