使用golang进行rpc开发之二使用json进行开发

上文介绍了如何在golang 中使用原生的net/rpc 进行开发,但是使用原生的rpc有一个问题,不能跨语言,只能服务端和客户端都是golang时才可以,本文介绍如何使用json作为序列化在不同的语言间进行rpc调用。

首先我们先来修改一个服务端的代码,之前的代码中,服务端收到客户端发来的数据以后,是通过 rpc.ServeConn(conn)来处理请求的

for {
    fmt.Println("等待连接。。。。")
    // 3. 建立连接
    conn, err := lister.Accept()
    if err != nil {
        fmt.Println(err)
    }
    // 4. 绑定服务
    rpc.ServeConn(conn)
}

如果要使用json来处理的话,只需要将原来的rpc.ServeConn(conn)修改为 rpc.ServeCodec(jsonrpc.NewServerCodec(conn))即可。

for {
    fmt.Println("等待连接。。。。")
    // 3. 建立连接
    conn, err := lister.Accept()
    if err != nil {
        fmt.Println(err)
    }
    // 4. 绑定服务
    rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}

但是客户端修改的相对多一些,

func main() {
    // 修改处 1
    conn, err := net.Dial("tcp", "127.0.0.1:8080")

    if err != nil {
        fmt.Println(err)
        return
    }
    // 修改处 2
    rclient := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    defer rclient.Close()
    // 2. 调用远程方法
    var rep Res
    req := Person{"yangyanxing", 18}

    err = rclient.Call("hello.SayHi", req, &rep)
    if err != nil {
        fmt.Println("call fail:", err)
        return
    }
    fmt.Printf("%#v \n", rep)
    fmt.Println("over....")
}

客户端有两处修改,

  1. 之前使用rpc.Dial("tcp", "127.0.0.1:8080"), 修改后为使用net.Dial("tcp", "127.0.0.1:8080")
  2. 之前使用rpc.Dial返回的连接执行Call方法,现在需要将这个连接包上一层
rclient := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

其他的代码不用改,这时我们来看一下服务端在收到以下客户端发过来的结构体数据以后会收到什么样的数据?

req := Person{"yangyanxing", 18}
err = rclient.Call("hello.SayHi", req, &rep)

服务端此时可以正常的接收go客户端发送过来的请求,并且将数据映射到了Person结构体,但是我们并没有看到发送过来的原始数据是什么样的,我单独再写一个打印接收到的tcp数据服务, 代码参考 https://www.jianshu.com/p/6db6dffb04e5

package main

//  开启tcp 监听

import (
    "fmt"
    "log"
    "net"
    "strings"
)

func connHandler(c net.Conn) {
    //1.conn是否有效
    if c == nil {
        log.Panic("无效的 socket 连接")
    }

    //2.新建网络数据流存储结构
    buf := make([]byte, 4096)
    //3.循环读取网络数据流
    for {
        //3.1 网络数据流读入 buffer
        cnt, err := c.Read(buf)
        //3.2 数据读尽、读取错误 关闭 socket 连接
        if cnt == 0 || err != nil {
            c.Close()
            break
        }

        //3.3 根据输入流进行逻辑处理
        //buf数据 -> 去两端空格的string
        inStr := strings.TrimSpace(string(buf[0:cnt]))
        fmt.Println("客户端传输->" + inStr)
        c.Write([]byte("msg received!"))
        c.Close() //关闭client端的连接,telnet 被强制关闭
        fmt.Printf("来自 %v 的连接关闭\n", c.RemoteAddr())
    }
}

//开启serverSocket
func main() {
    //1.监听端口
    server, err := net.Listen("tcp", ":9090")

    if err != nil {
        fmt.Println("开启socket服务失败")
    }

    fmt.Println("正在开启 Server ...")

    for {
        //2.接收来自 client 的连接,会阻塞
        conn, err := server.Accept()

        if err != nil {
            fmt.Println("连接出错")
        }
        //并发模式 接收来自客户端的连接请求,一个连接 建立一个 conn,服务器资源有可能耗尽 BIO模式
        go connHandler(conn)
    }

}

这个服务监听在了9090端口,我们修改一下go客户端代码的端口,然后再发个请求,此时我们得到客户端发送过来的原始数据

客户端传输->{"method":"hello.SayHi","params":[{"Name":"yangyanxing","Age":18}],"id":0}

可以看到这是一个json格式的字符串,格式化以后为

{
  "method":"hello.SayHi",
  "params":[
    {
      "Name":"yangyanxing",
      "Age":18
    }
  ],
  "id":0
}

这个数据指明了要调用的方法与所传的参数,在服务端接收到这样的数据以后,根据这些参数,就可以将数据映射到go中的结构体,并且执行相应的方法。

上面还是使用go来远程调用,那么如何在其他语言中进行远程调用呢? 我以python 为例,其实就是发送tcp数据

import socket
import threading
import json


sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 8080))
d = {"method":"hello.SayHi","params":[{"Name":"yyx","Age":8}],"id":0}
message = json.dumps(d)
sock.sendall(bytes(message))
response = sock.recv(1024)
print(response)
注意我先定义一个字典变量,这里的参数就是上面获取到的数据,之后对这个字典变量执行json.dumps转化为字符串,再使用bytes来转化为bytes,之后使用socket 来发送数据,此时golang中的服务端也可以正常的执行 hello.SayHi方法! 这样我们就通过使用json 来达到不同语言之间的rpc 远程调用了!