[golang 微服务] 2. RPC架构先容以及通过RPC实现微服务

[复制链接]
发表于 2026-2-20 04:52:39 | 显示全部楼层 |阅读模式
一.简介

<blockquote class="kdocs-blockquote" style="">  在上一节简单相识了微服务界说和优缺点之后,在使用微服务框架之前,须要起宰衡识一下RPC架构,通过RPC可以更形象相识微服务的工作流程  

  • RPC的概念
<blockquote class="kdocs-blockquote" style="">  RPC(Remote Procedure Call Protocol),是  长途过程调用的缩写,普通的说就是  调用远处的一个函数,与之相对应的是  本地函数调用,先来看一下本地函数调用:当写下如下代码的时间:  
  result := Add(1,2)  
传入了1,2两个参数,调用了本地代码中的一个Add函数,得到result这个返回值,这时  参数,  返回值,  代码段都在一个进程空间内,这是  本地函数调用。  那有没有办法,可以大概调用一个  跨进程  (以是叫"长途",范例的事例,这个进程摆设在  另一台服务器  上)的函数呢?           
       <blockquote class="kdocs-blockquote" style="">  这就是  RPC重要实现的功能,也是  微服务的重要功能  

  • RPC入门
使用微服务化的一个利益就是:
(1).不限定服务的提供方使用什么技能选型,可以大概实现公司跨团队的技能解耦
(2).每个服务都被封装成进程,相互"独立"
(3).使用微服务可以跨进程通讯
         
       <blockquote class="kdocs-blockquote" style="">  RPC协议可以实现差别语言的直接相互调用,在互联网期间,  RPC已经和  IPC(进程间通讯)一样成为一个不可或缺的根本构件  IPC: 进程间通讯
RPC:长途进通讯 —— 应用层协议(http协议同层),底层使用 TCP 实现

在golang中实现RPC非常简单,有封装好的官方库和一些第三方库提供支持,Go RPC可以使用tcp或http来转达数据,可以对要转达的数据使用多种范例的编解码方式。golang官方的net/rpc库使用encoding/gob举行编解码,支持tcp或http数据传输方式,由于其他语言不支持gob编解码方式,以是使用net/rpc库实现的RPC方法没办法举行跨语言调用。
golang官方还提供了net/rpc/jsonrpc库实现RPC方法,JSON RPC采取JSON举行数据编解码,因而支持跨语言调用,但现在的jsonrpc库是基于tcp协议实现的,临时不支持使用http举行数据传输。
除了golang官方提供的rpc库,尚有许多第三方库为在golang中实现RPC提供支持,大部门第三方rpc库的实现都是使用protobuf举行数据编解码,根据protobuf声明文件自动天生rpc方法界说与服务注册代码,在golang中可以很方便的举行rpc服务调用
二.net/rpc库实现长途调用


  • 使用http作为RPC的载体实现长途调用(相识)
<blockquote class="kdocs-blockquote" style="">  演示怎样使用golang官方的 net/rpc 库实现RPC方法,使用   http 作为RPC的载体,通过 net/http 包监听客户端毗连哀求。http基于tcp,  多一层封包和反复握手校验,  性能自然比直接用tcp实现网络传输要  差一些,以是RPC微服务中一样寻常使用的都是tcp  (1).创建RPC微服务端

<blockquote class="kdocs-blockquote" style="">  新建server/main.go
  1. package main
  2. import (
  3.     "fmt"
  4.     "log"
  5.     "net"
  6.     "net/http"
  7.     "net/rpc"
  8.     "os"
  9. )
  10. // 定义类对象
  11. type World struct {
  12. }
  13. // 绑定类方法
  14. func (this *World) HelloWorld(req string, res *string) error {
  15.     *res = req + " 你好!"
  16.     return nil
  17.     //return errors.New("未知的错误!")
  18. }
  19. // 绑定类方法
  20. func (this *World) Print(req string, res *string) error {
  21.     *res = req + " this is Print!"
  22.     return nil
  23.     //return errors.New("未知的错误!")
  24. }
  25. func main() {
  26.     // 1. 注册RPC服务
  27.     rpc.Register(new(World)) // 注册rpc服务
  28.     rpc.HandleHTTP() // 采用http协议作为rpc载体
  29.     // 2. 设置监听
  30.     lis, err := net.Listen("tcp", "127.0.0.1:8800")
  31.     if err != nil {
  32.         log.Fatalln("fatal error: ", err)
  33.     }
  34.     fmt.Fprintf(os.Stdout, "%s", "start connection")
  35.     // 3. 建立链接
  36.     http.Serve(lis, nil)
  37. }
复制代码
注意:以上World结构体的方法方法必须满意Go语言的RPC规则:


  • 方法只能有两个可序列化的参数,此中第二个参数是指针范例,参数的范例不能是channel(通道)、complex(复数范例)、func(函数),由于它们不能举行 序列化


  • 方法要返回一个error范例,同时必须是公开的方法
(2). 创建RPC客户端

<blockquote class="kdocs-blockquote" style="">  客户端可以是  go web 也可以是一个  go应用,新建client/main.go
  1. package main
  2. import (
  3.     "fmt"
  4.     "net/rpc"
  5. )
  6. func main() {
  7.     // 1. 用 rpc 链接服务器 --Dial()
  8.     conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8800")
  9.     if err != nil {
  10.         fmt.Println("Dial err:", err)
  11.         return
  12.     }
  13.     defer conn.Close()
  14.     // 2. 调用远程函数
  15.     var reply1 string // 接受返回值 --- 传出参数
  16.     err1 := conn.Call("World.HelloWorld", "张三", &reply1)
  17.     if err1 != nil {
  18.         fmt.Println("Call:", err1)
  19.         return
  20.     }
  21.     fmt.Println(reply1)
  22.     var reply2 string // 接受返回值 --- 传出参数
  23.     err2 := conn.Call("World.Print", "李四", &reply2)
  24.     if err2 != nil {
  25.         fmt.Println("Call:", err2)
  26.         return
  27.     }
  28.     fmt.Println(reply2)
  29. }
复制代码

  • 使用tcp作为RPC的载体实现长途调用
(1).创建RPC微服务端

<blockquote class="kdocs-blockquote" style="">  新建server/main.go
  1. package main
  2. import (
  3.     "fmt"
  4.     "net"
  5.     "net/rpc"
  6. )
  7. // 定义类对象
  8. type World struct {}
  9. // 绑定类方法
  10. func (this *World) HelloWorld(req string, res *string) error {
  11.     *res = req + " 你好!"
  12.     return nil
  13. }
  14. func main() {
  15.     // 1. 注册RPC服务
  16.     err := rpc.RegisterName("hello", new(World))
  17.     if err != nil {
  18.         fmt.Println("注册 rpc 服务失败!", err)
  19.         return
  20.     }
  21.     // 2. 设置监听
  22.     listener, err := net.Listen("tcp", "127.0.0.1:8800")
  23.     if err != nil {
  24.         fmt.Println("net.Listen err:", err)
  25.         return
  26.     }
  27.     defer listener.Close()
  28.     fmt.Println("开始监听 ...")
  29.     // 3. 建立链接
  30.     for {
  31.         //接收连接
  32.         conn, err := listener.Accept()
  33.         if err != nil {
  34.             fmt.Println("Accept() err:", err)
  35.             return
  36.         }
  37.         // 4. 绑定服务
  38.         go rpc.ServeConn(conn)
  39.     }
  40. }
复制代码
注意:以上World结构体的方法方法必须满意Go语言的RPC规则:



  • 方法只能有两个可序列化的参数,此中第二个参数是指针范例,参数的范例不能是channel(通道)、complex(复数范例)、func(函数),由于它们不能举行 序列化


  • 方法要返回一个error范例,同时必须是公开的方法
(2). 创建RPC客户端

<blockquote class="kdocs-blockquote" style="">  新建client/main.go
  1. package main
  2. import (
  3.     "fmt"
  4.     "net/rpc"
  5. )
  6. func main() {
  7.     // 1. 用 rpc 链接服务器 --Dial()
  8.     conn, err := rpc.Dial("tcp", "127.0.0.1:8800")
  9.     if err != nil {
  10.         fmt.Println("Dial err:", err)
  11.         return
  12.     }
  13.     defer conn.Close()
  14.     // 2. 调用远程函数
  15.     var reply string // 接受返回值 --- 传出参数
  16.     err = conn.Call("hello.HelloWorld", "张三", &reply)
  17.     if err != nil {
  18.         fmt.Println("Call:", err)
  19.         return
  20.     }
  21.     fmt.Println(reply)
  22. }
复制代码
<blockquote class="kdocs-blockquote" style="">  阐明:   
   首选是通过rpc.Dial拨号RPC服务,然后通过client.Call调用详细的RPC方法,在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别界说RPC方法的两个参数  三.使用tcp作为RPC的载体实现长途调用详细案例

案例1.简单使用

<blockquote class="kdocs-blockquote" style="">  1.创建一个hello微服务端,编写微服务端RPC代码,完成后启动该微服务端  
2.创建一个hello客户端,编写客户端RPC代码,完成后启动该客户端,访问微服务端RPC功能,并返回相干数据  (1).创建hello微服务端

<blockquote class="kdocs-blockquote" style="">  创建mirco/server/hello/main.go文件,并编写代码,代码下所示:
  1. package main
  2. import (
  3.     "fmt"
  4.     "net"
  5.     "net/rpc"
  6. )
  7. //rpc服务端
  8. //定义一个远程调用的结构体,并创建一个远程调用的函数,函数一般是放在结构体中的
  9. type  Hello struct  {
  10. }
  11. /*
  12. 说明:
  13.     1、方法只能有两个可序列化的参数,其中第二个参数是指针类型
  14.         req 表示获取客户端传过来的数据
  15.         res 表示给客户端返回数据
  16.     2、方法要返回一个error类型,同时必须是公开的方法
  17.     3、req和res的类型不能是:channel(通道)、func(函数),因为以上类型均不能进行 序列化
  18. */
  19. func (this Hello) SayHello(req string, res *string) error {
  20.     fmt.Println("请求的参数:", req)
  21.     //设置返回的数据
  22.     *res = "你好" + req
  23.     return nil
  24. }
  25. func main()  {
  26.     //1、 注册RPC服务
  27.     //hello: rpc服务名称
  28.     err1 := rpc.RegisterName("hello", new(Hello))
  29.     if err1 != nil {
  30.         fmt.Println(err1)
  31.     }
  32.     //2、监听端口
  33.     listen, err2 := net.Listen("tcp", "127.0.0.1:8080")
  34.     if err2 != nil {
  35.         fmt.Println(err2)
  36.     }
  37.     //3、应用退出的时候关闭监听端口
  38.     defer listen.Close()
  39.     for {  // for 循环, 一直进行连接,每个客户端都可以连接
  40.         fmt.Println("开始创建连接")
  41.         //4、建立连接
  42.         conn, err3 := listen.Accept()
  43.         if err3 != nil {
  44.             fmt.Println(err3)
  45.         }
  46.         //5、绑定服务
  47.         rpc.ServeConn(conn)
  48.     }
  49. }
复制代码
(2).创建hello客户端

<blockquote class="kdocs-blockquote" style="">  创建mirco/client/hello/main.go文件,并编写代码,代码下所示:
  1. package main
  2. import (
  3.     "fmt"
  4.     "net/rpc"
  5. )
  6. //rpc服务端
  7. func main()  {
  8.     //1、用 rpc.Dial和rpc微服务端建立连接
  9.     conn, err1 := rpc.Dial("tcp", "127.0.0.1:8080")
  10.     if err1 != nil {
  11.         fmt.Println(err1)
  12.     }
  13.     //2、当客户端退出的时候关闭连接
  14.     defer conn.Close()
  15.     //3、调用远程函数
  16.     //微服务端返回的数据
  17.     var reply string
  18.     /*
  19.         1、第一个参数: hello.SayHello,hello 表示服务名称  SayHello 方法名称
  20.         2、第二个参数: 给服务端的req传递数据
  21.         3、第三个参数: 需要传入地址,获取微服务端返回的数据
  22.     */
  23.     err2 := conn.Call("hello.SayHello", "我是客户端", &reply)
  24.     if err2 != nil {
  25.         fmt.Println(err2)
  26.     }
  27.     //4、获取微服务返回的数据
  28.     fmt.Println(reply)
  29. }
复制代码
(3).启动微服务端,以及客户端访问

启动微服务端

         
       启动客户端

         
       案例2.模仿实现一个goods的微服务,增长商品 获取商品功能

<blockquote class="kdocs-blockquote" style="text-align:left;">  1.创建一个goods微服务端,编写微服务端RPC代码,增长函数:  增长商品函数,获取商品函数,完成后启动该微服务端  
2.创建一个goods客户端,编写客户端RPC代码,完成后启动该客户端,访问微服务端RPC功能,并返回相干数据  (1).创建goods微服务端

<blockquote class="kdocs-blockquote" style="text-align:left;">  创建mirco/server/goods/main.go文件,并编写代码,代码下所示:
  1. package main
  2. import (
  3.     "fmt"
  4.     "net"
  5.     "net/rpc"
  6. )
  7. // goods微服务:服务端,传入struct,增加商品,获取商品
  8. //创建远程调用的函数,函数一般是放在结构体里面
  9. type Goods struct{}
  10. //AddGoods参数对应的结构体
  11. //增加商品请求参数结构体
  12. type AddGoodsReq struct {
  13.     Id      int
  14.     Title   string
  15.     Price   float32
  16.     Content string
  17. }
  18. //增加商品返回结构体
  19. type AddGoodsRes struct {
  20.     Success bool
  21.     Message string
  22. }
  23. //GetGoods参数对应的结构体
  24. //获取商品请求结构体
  25. type GetGoodsReq struct {
  26.     Id int
  27. }
  28. //获取商品返回结构体
  29. type GetGoodsRes struct {
  30.     Id      int
  31.     Title   string
  32.     Price   float32
  33.     Content string
  34. }
  35. /*
  36. 说明:
  37.     1、方法只能有两个可序列化的参数,其中第二个参数是指针类型
  38.         req 表示获取客户端传过来的数据
  39.         res 表示给客户端返回数据
  40.     2、方法要返回一个error类型,同时必须是公开的方法
  41.     3、req和res的类型不能是:channel(通道)、func(函数),因为以上类型均不能进行 序列化
  42. */
  43. //增加商品函数
  44. func (this Goods) AddGoods(req AddGoodsReq, res *AddGoodsRes)  error {
  45.     //1、执行增加 模拟
  46.     fmt.Printf("%#v\n", req)
  47.     *res = AddGoodsRes{
  48.         Success: true, //根据增加结果,返回状态
  49.         Message: "增加商品成功",
  50.     }
  51.     return nil
  52. }
  53. //获取商品函数
  54. func (this Goods) GetGoods(req GetGoodsReq, res *GetGoodsRes) error {
  55.     //1、执行获取商品 模拟
  56.     fmt.Printf("%#v\n", req)
  57.     //2、返回获取的结果
  58.     *res = GetGoodsRes{
  59.         Id:      12,  //商品id
  60.         Title:   "服务器获取的数据",
  61.         Price:   24.5,
  62.         Content: "我是服务器数据库获取的内容",
  63.     }
  64.     return nil
  65. }
  66. func main()  {
  67.     //1、 注册RPC服务
  68.     //goods: rpc服务名称
  69.     err1 := rpc.RegisterName("goods", new(Goods))
  70.     if err1 != nil {
  71.         fmt.Println(err1)
  72.     }
  73.     //2、监听端口
  74.     listen, err2 := net.Listen("tcp", "127.0.0.1:8080")
  75.     if err2 != nil {
  76.         fmt.Println(err2)
  77.     }
  78.     //3、应用退出的时候关闭监听端口
  79.     defer listen.Close()
  80.     for {  // for 循环, 一直进行连接,每个客户端都可以连接
  81.         fmt.Println("准备建立连接")
  82.         //4、建立连接
  83.         conn, err3 := listen.Accept()
  84.         if err3 != nil {
  85.             fmt.Println(err3)
  86.         }
  87.         //5、绑定服务
  88.         rpc.ServeConn(conn)
  89.     }
  90. }
复制代码
(2).创建goods客户端

<blockquote class="kdocs-blockquote" style="text-align:left;">  创建mirco/client/goods/main.go文件,并编写代码,代码下所示:
  1. package main
  2. import (
  3.     "fmt"
  4.     "net/rpc"
  5. )
  6. //AddGoods参数对应的结构体
  7. //增加商品请求参数结构体
  8. type AddGoodsReq struct {
  9.     Id      int
  10.     Title   string
  11.     Price   float32
  12.     Content string
  13. }
  14. //增加商品返回结构体
  15. type AddGoodsRes struct {
  16.     Success bool
  17.     Message string
  18. }
  19. //GetGoods参数对应的结构体
  20. //获取商品请求结构体
  21. type GetGoodsReq struct {
  22.     Id int
  23. }
  24. //获取商品返回结构体
  25. type GetGoodsRes struct {
  26.     Id      int
  27.     Title   string
  28.     Price   float32
  29.     Content string
  30. }
  31. func main() {
  32.     //1、用 rpc.Dial和rpc微服务端建立连接
  33.     conn, err1 := rpc.Dial("tcp", "127.0.0.1:8080")
  34.     if err1 != nil {
  35.         fmt.Println(err1)
  36.     }
  37.     //2、当客户端退出的时候关闭连接
  38.     defer conn.Close()
  39.     //3、调用远程函数
  40.     //微服务端返回的数据
  41.     var reply AddGoodsRes
  42.     /*
  43.         1、第一个参数: goods.AddGoods,goods 表示服务名称  AddGoods 方法名称
  44.         2、第二个参数: 给服务端的req传递数据
  45.         3、第三个参数: 需要传入地址,获取微服务端返回的数据
  46.     */
  47.     err2 := conn.Call("goods.AddGoods", AddGoodsReq{
  48.         Id:      10,
  49.         Title:   "商品标题",
  50.         Price:   23.5,
  51.         Content: "商品详情",
  52.     }, &reply)
  53.     if err2 != nil {
  54.         fmt.Println(err1)
  55.     }
  56.     //4、获取微服务返回的数据
  57.     fmt.Println("%#v\n", reply)
  58.     // 5、 调用远程GetGoods函数
  59.     var goodsData GetGoodsRes
  60.     err3 := conn.Call("goods.GetGoods", GetGoodsReq{
  61.         Id: 12,
  62.     }, &goodsData)
  63.     if err3 != nil {
  64.         fmt.Println(err3)
  65.     }
  66.     //6、获取微服务返回的数据
  67.     fmt.Printf("%#v", goodsData)
  68. }
复制代码
(3).启动微服务端,以及客户端访问

启动微服务端

         
       启动客户端

         
       四.net/rpc/jsonrpc库以及RPC跨语言

<blockquote class="kdocs-blockquote" style="">  标准库的RPC默认采取Go语言特有的  gob编码,  没法实现跨语言调用,golang官方还提供了  net/rpc/jsonrpc库实现RPC方法,JSON RPC采  用JSON举行数据编解码,因而  支持跨语言调用,  但现在的jsonrpc库是  基于tcp协议  实现的,临时不支持使用http举行数据传输  

  • Linux下令之nc创建tcp服务测试数据传输
<blockquote class="kdocs-blockquote" style="">  nc是  netcat的简写,是一个功能强大的网络工具,有着网络界的瑞士军刀美誉,nc下令的  重要作用如下:  

  • 实现恣意TCP/UDP端口的侦听,nc可以作为server以TCP或UDP方式侦听指定端口


  • 端口的扫描,nc可以作为client发起TCP或UDP毗连


  • 呆板之间传输文件


  • 呆板之间网络测速
<blockquote class="kdocs-blockquote" style="">  centos中如果找不到nc下令可以使用 yum install -y nc 安装  使用nc作为微服务server端吸收客户端数据
  1. nc -l 192.XXX.XXX.XXX 8080
复制代码
nc作为微服务server端开启:
         
       客户端哀求和上面案例划一,也可以参考下面案例
<blockquote class="kdocs-blockquote" style="">  上面解说了使用 net/rpc 实现RPC的过程,但是没办法在其他语言中调用上面例子实现的RPC方法。以是接下来使用   net/rpc/jsonrpc 库实现RPC方法,此方式实现的  RPC方法支持跨语言调用  

  • 创建RPC微服务端
<blockquote class="kdocs-blockquote" style="">  使用   net/rpc/jsonrpc 库实现RPC方法:  
和rpc微服务端区别: 在 5. 绑定服务步调中使用  rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
  1. package main
  2. import (
  3.     "fmt"
  4.     "net"
  5.     "net/rpc"
  6.     "net/rpc/jsonrpc"
  7. )
  8. //rpc服务端
  9. //定义一个远程调用的结构体,并创建一个远程调用的函数,函数一般是放在结构体中的
  10. type  Hello struct  {
  11. }
  12. /*
  13. 说明:
  14.     1、方法只能有两个可序列化的参数,其中第二个参数是指针类型
  15.         req 表示获取客户端传过来的数据
  16.         res 表示给客户端返回数据
  17.     2、方法要返回一个error类型,同时必须是公开的方法
  18.     3、req和res的类型不能是:channel(通道)、func(函数),因为以上类型均不能进行 序列化
  19. */
  20. func (this Hello) SayHello(req string, res *string) error {
  21.     fmt.Println("请求的参数:", req)
  22.     //设置返回的数据
  23.     *res = "你好" + req
  24.     return nil
  25. }
  26. func main()  {
  27.     //1、 注册RPC服务
  28.     //hello: rpc服务名称
  29.     err1 := rpc.RegisterName("hello", new(Hello))
  30.     if err1 != nil {
  31.         fmt.Println(err1)
  32.     }
  33.     //2、监听端口
  34.     listen, err2 := net.Listen("tcp", "127.0.0.1:8080")
  35.     if err2 != nil {
  36.         fmt.Println(err2)
  37.     }
  38.     //3、应用退出的时候关闭监听端口
  39.     defer listen.Close()
  40.     for {  // for 循环, 一直进行连接,每个客户端都可以连接
  41.         fmt.Println("开始创建连接")
  42.         //4、建立连接
  43.         conn, err3 := listen.Accept()
  44.         if err3 != nil {
  45.             fmt.Println(err3)
  46.         }
  47.         //5、绑定服务
  48.         //rpc.ServeConn(conn)
  49.         // 5. 绑定服务
  50.         /*
  51.            jsonrpc和默认rpc的区别:
  52.                    以前rpc.ServeConn(conn)绑定服务
  53.                    jsonrpc中通过rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
  54.         */
  55.         rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
  56.     }
  57. }
复制代码
<blockquote class="kdocs-blockquote" style="">  代码中最大的厘革是用  rpc.ServeCodec函数更换了  rpc.ServeConn函数,传入的参数是针对服务端的json编解码器  

  • 创建RPC客户端
  1. package main
  2. import (
  3.     "fmt"
  4.     "net"
  5.     "net/rpc"
  6.     "net/rpc/jsonrpc"
  7. )
  8. //rpc服务端
  9. /*
  10. 把默认的rpc 改为jsonrpc
  11.     1、rpc.Dial需要调换成net.Dial
  12.     2、增加建立基于json编解码的rpc服务  client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
  13.     3、conn.Call 需要改为client.Call
  14. */
  15. func main()  {
  16.     //1、用 net.Dial和rpc微服务端建立连接
  17.     conn, err1 := net.Dial("tcp", "127.0.0.1:8080")
  18.     if err1 != nil {
  19.         fmt.Println(err1)
  20.     }
  21.     //2、当客户端退出的时候关闭连接
  22.     defer conn.Close()
  23.     //建立基于json编解码的rpc服务
  24.     client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
  25.     //3、调用远程函数
  26.     //微服务端返回的数据
  27.     var reply string
  28.     /*
  29.         1、第一个参数: hello.SayHello,hello 表示服务名称  SayHello 方法名称
  30.         2、第二个参数: 给服务端的req传递数据
  31.         3、第三个参数: 需要传入地址,获取微服务端返回的数据
  32.     */
  33.     err2 := client.Call("hello.SayHello", "张三", &reply)
  34.     if err2 != nil {
  35.         fmt.Println(err2)
  36.     }
  37.     //4、获取微服务返回的数据
  38.     fmt.Println(reply)
  39. }
复制代码
<blockquote class="kdocs-blockquote" style="">  先手工调用  net.Dial函数创建TCP链接,然后基于该链接创建针对客户端的json编解码器  

  • 启动微服务端,以及客户端访问
启动微服务端

         
       启动客户端

         
      

  • RPC跨语言
<blockquote class="kdocs-blockquote" style="">  以PHP跨语言调用RPC微服务为案例  PHP代码
  1. <?php
  2. class JsonRPC {
  3.     private $conn;
  4.     function __construct($host, $port) {
  5.         $this->conn = fsockopen($host, $port, $errno, $errstr, 3);
  6.         if (!$this->conn) {
  7.             return false;
  8.         }
  9.     }
  10.     public function Call($method, $params) {
  11.         if (!$this->conn) {
  12.             return false;
  13.         }
  14.         $err = fwrite($this->conn, json_encode(array(
  15.                 'method' => $method,
  16.                 'params' => array($params),
  17.                 'id' => 0,
  18.             ))."\n");
  19.         if ($err === false) {
  20.             return false;
  21.         }
  22.         stream_set_timeout($this->conn, 0, 3000);
  23.         $line = fgets($this->conn);
  24.         if ($line === false) {
  25.             return NULL;
  26.         }
  27.         return json_decode($line,true);
  28.         }
  29.     }
  30.     $client = new JsonRPC("127.0.0.1", 8080);
  31.     $args = "this is php aaa";
  32.     $r = $client->Call("Hello.SayHello", $args);
  33.     print_r($r);
  34. ?>
复制代码
服务端启动和上面微服务端启动划一,php端访问,效果如下:
         
      

  • RPC协议封装
<blockquote class="kdocs-blockquote" style="">  后期使用微服务框架  GRPC 和   Go-Micro的时间,都是使用  框架封装好的服务和客户端,接下来通过一个简单的示例演示一下  怎样封装,以此来明确  封装的原理,上面的代码服务名都是写死的,不敷机动(轻易写错),这里对RPC的服务端和客户端再次举行一次封装,来  屏蔽掉服务名,详细代码如下:  服务端封装

<blockquote class="kdocs-blockquote" style="">  新建server/models/tools
  1. package models
  2. import "net/rpc"
  3. var serverName = "HelloService"
  4. type RPCInterface interface {
  5.     HelloWorld(string, *string) error
  6. }
  7. // 调用该方法时, 需要给 i 传参, 参数应该是 实现了 HelloWorld 方法的类对象!
  8. func RegisterService(i RPCInterface) {
  9.     rpc.RegisterName(serverName, i)
  10. }
复制代码
封装之后的服务端实现如下:
  1. package main
  2. import (
  3.     "fmt"
  4.     "net"
  5.     "net/rpc"
  6.     "net/rpc/jsonrpc"
  7.     "server/models"
  8. )
  9. // 定义类对象
  10. type World struct {}
  11. // 绑定类方法
  12. func (this *World) HelloWorld(req string, res *string) error {
  13.     fmt.Println(req)
  14.     *res = req + " 你好!"
  15.     return nil
  16.     //return errors.New("未知的错误!")
  17. }
  18. func main() {
  19.     //注册rpc服务 维护一个hash表,key值是服务名称,value值是服务的地址
  20.     // rpc.RegisterName("HelloService", new(World))
  21.     models.RegisterService(new(World))
  22.     //设置监听
  23.     listener, err := net.Listen("tcp", ":8080")
  24.     if err != nil {
  25.         panic(err)
  26.     }
  27.     for {
  28.         //接收连接
  29.         conn, err := listener.Accept()
  30.         if err != nil {
  31.             panic(err)
  32.         }
  33.         //给当前连接提供针对json格式的rpc服务
  34.         go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
  35.     }
  36. }
复制代码
客户端封装

<blockquote class="kdocs-blockquote" style="">  新建client/models/tools
  1. package models
  2. import (
  3.     "fmt"
  4.     "net"
  5.     "net/rpc"
  6.     "net/rpc/jsonrpc"
  7. )
  8. var serverName = "HelloService"
  9. type RPCClient struct {
  10.     Client *rpc.Client
  11.     Conn net.Conn
  12. }
  13. func NewRpcClient(addr string) RPCClient {
  14.     conn, err := net.Dial("tcp", addr)
  15.     if err != nil {
  16.         fmt.Println("链接服务器失败")
  17.         return RPCClient{}
  18.     }
  19.     //套接字和rpc服务绑定
  20.     client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
  21.     return RPCClient{Client: client, Conn: conn}
  22. }
  23. func (this *RPCClient) CallFunc(req string, resp *string) error {
  24.     return this.Client.Call(serverName+".HelloWorld", req, resp)
  25. }
复制代码
封装之后客户端实现
  1. package main
  2. import (
  3.     "client/models"
  4.     "fmt"
  5. )
  6. func main() {
  7.     //建立tcp连接
  8.     client := models.NewRpcClient("127.0.0.1:8080")
  9.     //关闭连接
  10.     defer client.Conn.Close()
  11.     var reply string // 接受返回值 --- 传出参数
  12.     err := client.CallFunc("this is client", &reply)
  13.     if err != nil {
  14.         fmt.Println("Call:", err)
  15.         return
  16.     }
  17.     fmt.Println(reply)
  18. }
复制代码
[上一节][golang 微服务] 1.单体式架构以及微服务架构先容
[下一节][golang 微服务] 3. ProtoBuf熟悉与使用

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表