GoLang下的rpc开发
Python下的rpc开发可阅读我的另一篇文章:Python下的rpc开发
1、Go语言的rpc之hello world
Go语言的rpc包的是建立在net包基础之上的(内置)。基于TCP协议
1.1 服务端
server.go
package main
import (
"net"
"net/rpc"
)
type HelloService struct{}
func (h *HelloService) Hello(request string, reply *string) error {
*reply = "hello " + request
return nil
}
func main() {
// 1. 实例化server
listener, err := net.Listen("tcp", ":1234")
if err != nil {
panic("端口监听失败")
}
// 2. 注册处理逻辑handle
_ = rpc.RegisterName("HelloService", &HelloService{})
// 3.启动服务
conn, err := listener.Accept()
if err != nil {
panic("建立连接失败")
}
rpc.ServeConn(conn)
}
说明:
1、Hello方法必须满足Go语言的RPC规则:方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法,然后就可以将HelloService类型的对象注册为一个RPC服务:(TCP RPC服务)
2、其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在“HelloService”服务空间之下。
3、建立一个唯一的TCP链接,并且通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务
1.2 客户端
client.go
package main
import (
"fmt"
"net/rpc"
)
func main() {
// 建立连接
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
panic("dialing fail")
}
var reply string
err = client.Call("HelloService.Hello", "jason", &reply)
if err != nil {
panic("response fail")
}
fmt.Println(reply)
}
说明:
1、首先是通过rpc.Dial拨号RPC服务,然后通过client.Call调用具体的RPC方法。
2、在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别我们定义RPC方法的两个参数。
2、基于json实现rpc的调用
- 仍然基于内置的net库实现,基于TCP协议
- 标准库的RPC默认采用Go语言特有的gob编码,因此从其它语言调用Go语言实现的RPC服务将比较困难。在互联网的微服务时代,每个RPC以及服务的使用者都可能采用不同的编程语言,因此跨语言是互联网时代RPC的一个首要条件。得益于RPC的框架设计,Go语言的RPC其实也是很容易实现跨语言支持的。
- Go语言的RPC框架有两个比较有特色的设计:一个是RPC数据打包时可以通过插件实现自定义的编码和解码;另一个是RPC建立在抽象的io.ReadWriteCloser接口之上的,我们可以将RPC架设在不同的通讯协议之上。这里我们将尝试通过官方自带的net/rpc/jsonrpc扩展实现一个跨语言的PPC。
2.1 服务端
server.go
package main
import (
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
type HelloService struct{}
func (h *HelloService) Hello(request string, reply *string) error {
*reply = "hello " + request
return nil
}
func main() {
// 1. 实例化server
listener, err := net.Listen("tcp", ":8888")
if err != nil {
panic("端口监听失败")
}
// 2. 注册处理逻辑handle
_ = rpc.RegisterName("HelloService", &HelloService{})
// 3.启动服务
for {
conn, err := listener.Accept()
if err != nil {
panic("建立连接失败")
}
// 传入参数是针对服务端的json编解码器
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
说明:代码中最大的变化是用rpc.ServeCodec函数替代了rpc.ServeConn函数,传入的参数是针对服务端的json编解码器
2.2 客户端
client.go
package main
import (
"fmt"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func main() {
// 建立连接
conn, err := net.Dial("tcp", "localhost:8888")
if err != nil {
panic("dialing fail")
}
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
var reply string
err = client.Call("HelloService.Hello", "jason", &reply)
if err != nil {
panic("response fail")
}
fmt.Println(reply)
}
2.3 彩蛋:Python客户端尝试请求
go_client.py
import json
import socket
request = {
"id": 0,
"params": ["jason"],
"method": "HelloService.Hello"
}
# socket连接
client = socket.create_connection(("localhost", 8888))
# 发送数据
client.sendall(json.dumps(request).encode())
# 接收数据
rsp = client.recv(1000)
rsp = json.loads(rsp.decode())
print(rsp)
3、基于http实现rpc的调用 (基础版)
3.1 服务端
server.go
package main
import (
"io"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
)
type HelloService struct{}
func (h *HelloService) Hello(request string, reply *string) error {
*reply = "hello " + request
return nil
}
func main() {
_ = rpc.RegisterName("HelloService", &HelloService{})
http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
var conn io.ReadWriteCloser = struct {
io.Writer
io.ReadCloser
}{
ReadCloser: r.Body,
Writer: w,
}
_ = rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
})
_ = http.ListenAndServe(":1234", nil)
}
3.2 客户端
client.go
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
var p = struct {
Id int `json:"id"`
Params []string `json:"params"`
Method string `json:"method"`
}{
Id: 1,
Params: []string{"jason"},
Method: "HelloService.Hello",
}
b, _ := json.Marshal(p)
text, _ := sendJsonPost("http://127.0.0.1:1234/jsonrpc", string(b))
fmt.Println(text)
}
// 发送post请求
func sendJsonPost(targetUrl string, params string) (content string, err error) {
var jsonStr = []byte(params)
req, _ := http.NewRequest("POST", targetUrl, bytes.NewBuffer(jsonStr))
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", errors.New(fmt.Sprintf("请求出错 %s", err))
}
defer func() {
_ = resp.Body.Close()
}()
body, _ := ioutil.ReadAll(resp.Body)
return string(body), nil
}
4、基于http实现rpc的调用 (改进版)
4.1 公共文件
解耦:server端和client端统一serviceName
公共文件: handler.go
package handler
const HelloServiceName = "handler/HelloService"
type HelloService struct{}
func (h *HelloService) Hello(request string, reply *string) error {
*reply = "hello " + request
return nil
}
4.2 服务端代理
服务端代理: server_proxy.go
package server_proxy
import (
"demo/http_forward/handler"
"net/rpc"
)
// HelloServer 定义服务接口
type HelloServer interface {
Hello(request string, reply *string) error
}
// RegisterHelloService 注册服务
func RegisterHelloService(srv HelloServer) error {
return rpc.RegisterName(handler.HelloServiceName, srv)
}
4.3 服务端
服务端: server.go
package main
import (
"demo/http_forward/handler"
"demo/http_forward/server_proxy"
"net"
"net/rpc"
)
func main() {
// helloService具体实现
helloHandler := &handler.HelloService{}
// 注册服务
_ = server_proxy.RegisterHelloService(helloHandler)
listener, err := net.Listen("tcp", ":8001")
if err != nil {
panic("监听端口失败")
}
conn, err := listener.Accept()
if err != nil {
panic("建立链接失败")
}
rpc.ServeConn(conn)
}
4.4 客户端代理
client_proxy.go
package client_proxy
import (
"demo/http_forward/handler"
"net/rpc"
)
// NewClient 客户端
func NewClient(protocol string, address string) HelloServiceStub {
conn, err := rpc.Dial(protocol, address)
if err != nil {
panic("连接服务器错误")
}
return HelloServiceStub{conn}
}
type HelloServiceStub struct {
*rpc.Client
}
func (c *HelloServiceStub) Hello(request string, reply *string) error {
err := c.Call(handler.HelloServiceName+".Hello", request, reply)
if err != nil {
return err
}
return nil
}
4.5 客户端
client.go
package main
import (
"demo/http_forward/client_proxy"
"fmt"
)
func main() {
client := client_proxy.NewClient("tcp", "127.0.0.1:8001")
var reply string
err := client.Hello("jason", &reply)
if err != nil {
panic("调用失败")
}
fmt.Println(reply)
}