不得不懂系列(2)-玩转Go中的RPC

HideOnBush · · 1009 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

说明

做项目中发现自己对很多东西的理解有偏差,所以决定做个总结,也分享给需要的朋友。如有错误和遗漏,欢迎沟通交流。

GitHub示例源码

本文介绍了Go中原生和第三方RPC使用方法,环境搭建方法并提供了材料。

RPC

远程过程调用(Remote Procedure Call),通俗的说,RPC可以实现跨机器、跨语言调用其他计算机的程序。举个例子,我在机器A上用C语言封装了某个功能的函数,我可以通过RPC在机器B上用GO语言调用机器A上的指定函数。 RPC为C/S模型,通常使用TCP或http协议。

Golang官方RPC

go RPC可以利用tcp或http来传递数据,可以对要传递的数据使用多种类型的编解码方式。

net/rpc库

Golang官方的net/rpc库可以通过tcp或http传递数据,但net/rpc库使用encoding/gob进行编解码,支持tcp或http数据传输方式,由于其他语言不支持gob编解码方式,所以使用net/rpc库实现的RPC方法没办法进行跨语言调用。

server端代码

package main

import (
	"net/rpc"
	"net"
	"fmt"
	"net/http"
)

type Chen struct {
}

//rcp方法
//func (t *T) MethodName(argType T1, replyType *T2) error
func (this *Chen) GetAdd(data int, sum *int) error {

	*sum = data + 100

	return nil
}

func main() {
	//1.对象实例化
	pd := new(Chen)
	//2. rpc注册
	rpc.Register(pd)
	//3. rpc网络
	rpc.HandleHTTP()
	//4. 监听网络
	ln, err := net.Listen("tcp", "127.0.0.1:12306")
	if err != nil {
		fmt.Println("net.Listen error:", err)
		return
	}
	//5. 等待连接
	http.Serve(ln, nil)
}
复制代码

client代码

package main

import (
	"net/rpc"
	"fmt"
)

func main() {
	//1. 连接服务器
	cln, err := rpc.DialHTTP("tcp", "127.0.0.1:12306")
	if err != nil {
		fmt.Println("rpc.Dial error:", err)
		return
	}
	defer cln.Close()

	//2. 调用服务器函数
	var data int
	err = cln.Call("Chen.GetAdd",10, &data)
	if err != nil {
		fmt.Println("cln.Call error:", err)
		return
	}
	//3. 打印输出
	fmt.Println("计算结果为:", data)
}
复制代码

运行结果

客户端输出:计算结果为: 110

net/rpc/jsonrpc库

Go官方还提供了使用json编解码的rpc库:net/rpc/jsonrpc,但是使用tcp传递数据,不能用http。

server代码

package main

import (
	"net/rpc"
	"net"
	"fmt"
	"net/rpc/jsonrpc"
)

type Chen struct {
}

//rcp方法
//func (t *T) MethodName(argType T1, replyType *T2) error
func (this *Chen) GetAdd(data int, sum *int) error {

	*sum = data + 100

	return nil
}

func main() {
	//1.对象实例化
	pd := new(Chen)
	//2. rpc注册
	rpc.Register(pd)

	//3. 监听网络
	ln, err := net.Listen("tcp", "127.0.0.1:12306")
	if err != nil {
		fmt.Println("net.Listen error:", err)
		return
	}

	//4. 处理客户端请求
	for {
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("Accept error:", err)
			continue
		}

		go func(conn net.Conn) {
			jsonrpc.ServeConn(conn)
		}(conn)
	}
}
复制代码

client代码

package main

import (
	"fmt"
	"net/rpc/jsonrpc"
)

func main() {
	//1. 连接服务器
	cln, err := jsonrpc.Dial("tcp", "127.0.0.1:12306")
	if err != nil {
		fmt.Println("jsonrpc.Dial error:", err)
		return
	}
	defer cln.Close()

	//2. 调用服务器函数
	var data int
	err = cln.Call("Chen.GetAdd",10, &data)
	if err != nil {
		fmt.Println("cln.Call error:", err)
		return
	}
	//3. 打印输出
	fmt.Println("计算结果为:", data)
}
复制代码

运行结果

客户端输出:计算结果为: 110

gRPC

所以为了真正实现跨主机、跨语言的远程调用,需要使用第三方的RPC库,推荐使用谷歌开源的gRPC。gRPC基于HTTP/2,采用protobuf进行数据编解码,压缩和传输效率更高。可以参考本人的Go语言protobuf入门了解Go语言protobuf的环境搭建和使用。

gRPC安装

由于不能直接访问golang官网,所以安装gPRC和go扩展包比较麻烦,可以从本人gRPC环境包安装中获取压缩包。

unzip x.zip -d /$GOPATH/src/golang.org/x
unzip google.golang.org.zip -d /$GOPATH/src/google.golang.org
复制代码

gRPC环境测试

启动服务器端,

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_server
$ go run main.go
复制代码

启动客户端,

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_client
$ go run main.go
复制代码

如果客户端打印出2019/06/10 15:26:12 Greeting: Hello world字样即表示gRPC环境正常。

建立proto文件

//版本
syntax = "proto3";

//包名
package myproto;

//服务
service Hello {
    //这儿注释才有效
    rpc GetAdd(In)returns(Out);//这儿注释无效
}

//传入
message In {
    //此处1不是赋值,而是指参数序号
    int64 num = 1;
}

//传出
message Out {
    //此处1不是赋值,而是指参数序号
    int64 size = 1;
}
复制代码

生成go代码

在.proto文件所在目录执行下面的指令,

protoc --go_out=plugins=grpc:./ *.proto
复制代码

生成go代码时要指定plugins=grpc表示生成的是gRPC代码。

服务端代码

package main

import (
	"fmt"
	pb "gRPC/myproto" //给package起别名
	"context"
	"net"
	"google.golang.org/grpc"
)

//1. 结构体
type Chen struct {

}

//2. 该结构体实现HelloServer interface的方法
func (this *Chen)GetAdd(ctx context.Context, In *pb.In)(*pb.Out,error)  {
	return &pb.Out{Size:In.Num+100},nil
}

func main() {
	fmt.Println("server runing...")

	//3. 创建网络
	ln, err := net.Listen("tcp", "127.0.0.1:12345")
	if err != nil {
		fmt.Println("net.Listen error:", err)
		return
	}
	defer ln.Close()

	//4. 创建gRPC句柄
	srv := grpc.NewServer()

	//5. 注册server
	pb.RegisterHelloServer(srv, &Chen{})

	//6. 等待网络连接
	err = srv.Serve(ln)
	if err != nil {
		fmt.Println("srv.Serve error:", err)
		return
	}

}
复制代码

客户端代码

package main

import (
	"google.golang.org/grpc"
	"fmt"
	pb "gRPC/myproto"
	"context"
)

func main() {
	//1 连接服务器
	conn, err := grpc.Dial("127.0.0.1:12345",grpc.WithInsecure())//grpc.WithInsecure()指定后才不会报错
	if err != nil {
		fmt.Println("grpc.Dial error:", err)
		return
	}
	defer conn.Close()

	//2 创建客户端句柄
	cln := pb.NewHelloClient(conn)

	//3 调用服务器函数(RPC)
	out,err := cln.GetAdd(context.Background(), &pb.In{Num:10})
	if err != nil {
		fmt.Println("grpc.Dial error:", err)
		return
	}

	//4 打印
	fmt.Println("得到数据:", out.Size)
}

复制代码

运行测试

先后运行服务器和客户端代码,可在客户端打印输出得到数据: 110,说明已经成功在客户端调用服务端程序。


有疑问加站长微信联系(非本文作者)

本文来自:掘金

感谢作者:HideOnBush

查看原文:不得不懂系列(2)-玩转Go中的RPC

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1009 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传