环境:
Golang:go1.18.2 linux/amd64
完整代码:
https://github.com/WanshanTian/GolangLearning
cd GolangLearning/RPC/httpRPC
前面两篇文章【Golang | RPC】Golang-RPC机制的理解,【Golang | RPC】利用json编解码器实现RPC介绍了基于socket连接,分别采用gob,json作编解码器实现RPC服务。本文基于http协议实现RPC服务,并简单分析原理
2. 实践/net/rpc
2.1 服务端
2.1.1 首先新建项目httpRPC,并创建Server目录,新建main.go
[root@tudou workspace]# mkdir -p httpRPC/Server && cd httpRPC/Server && touch main.go
2.1.2 服务端使用map保存用户年龄信息,同时创建Query结构体,该结构体实现了GetAge方法
package main
import (
"fmt"
"log"
"net/http"
"net/rpc"
)
// 用户信息
var userinfo = map[string]int{
"foo": 18,
"bar": 20,
}
// 实现查询服务,结构体Query实现了GetAge方法
type Query struct {
}
func (q *Query) GetAge(req string, res *string) error {
*res = fmt.Sprintf("The age of %s is %d", req, userinfo[req])
return nil
}
rpc.RegisterNameQueryService
func main() {
// 注册服务方法
if err := rpc.RegisterName("QueryService", new(Query)); err != nil {
log.Println(err)
}
...
}
rpc.HandleHTTPpatternhandler
func main() {
...
// 绑定pattern和handler
rpc.HandleHTTP()
...
}
http.ListenAndServe
func main() {
...
// 开启监听
if err := http.ListenAndServe(":1234", nil); err != nil {
log.Panic(err)
}
}
2.1.6 运行服务
[root@tudou Server]# go build main.go && ./main
2.2 客户端
2.2.1 在httpRPC/Server同级目录下创建Client目录,新建main.go
[root@tudou workspace]# mkdir -p httpRPC/Client && cd httpRPC/Client && touch main.go
rpc.DialHTTPCallGetAge
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
// 建立http连接
client, _ := rpc.DialHTTP("tcp", ":1234")
// 远程调用GetAge方法
var res string
err := client.Call("QueryService.GetAge", "foo", &res)
if err != nil {
log.Panicln(err)
}
fmt.Println(res)
_ = client.Close()
}
2.2.3 运行客户端,得到如下结果
[root@tudou Client]# go run main.go
The age of foo is 18
3 原理分析
rpc.DialHttpDialHTTPPath
const DefaultRPCPath = "/_goRPC_"
func DialHTTP(network, address string) (*Client, error) {
return DialHTTPPath(network, address, DefaultRPCPath)
}
// DialHTTPPath connects to an HTTP RPC server
// at the specified network address and path.
func DialHTTPPath(network, address, path string) (*Client, error) {
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n\n")
// Require successful HTTP response
// before switching to RPC protocol.
resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})
if err == nil && resp.Status == connected {
return NewClient(conn), nil
}
...
}
DialHTTPPathnet.Dialio.WriteString()CONNECTDefaultRPCPathHTTP/1.0http.ReadResponsehttp.ResponseNewClientgob
func NewClient(conn io.ReadWriteCloser) *Client {
encBuf := bufio.NewWriter(conn)
client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
return NewClientWithCodec(client)
}
Call
4. 思考
net/rpc
- 基于socket连接,可以减少网络开销,RPC的一次调用时延更短
- 基于http连接,可以方便作一些认证(比如grpc里的各种拦截器)
rpc.HandleHTTP()http.ListenAndServerpc.DialHTTPgob