位于$GOROOT/src/net/rpc/jsonrpc包下面的两个文件server.go和client.go是用json的序列化方式实现rpc的调用,默认使用go的rpc调用是传输的数据格式是gob,这是go语言特有的数据格式,能做到跨平台,但是很难做到跨语言,因此用jsonrpc来实现rpc功能,由于传输数据是json格式的,因此能做到跨语言,现在我们来分析一下jsonrpc下面的server.go和client.go文件所做的事情,以及rpc client端的发送操作和server端的接收操作。
server.go
从我截取的图中可以看到server首先定义了一个errMissingParams的参数缺失错误;然后定义了一个serverCodec结构,该结构包含了下面的一个serverRequest的结构,用于解析jsonrpc server端接收到的函数方法名和参数,serverRequest定义了一个reset()方法,再下面是一个serverResponse结构,用于封装jsonrpc返回的数据,在serverRequest和serverResponse中都有一个Id的属性,这个Id的作用后面会讲到。以及一个函数NewServerCodec()的函数,这个函数初始化一个serverCodec并返回,但是返回类型是rpc.ServerCodec,跟进rpc.ServerCodec看看怎么回事
原来rpc.ServerCodec是一个接口,只要实现了接口内定义的方法就可以将就可以将任何的数据结构赋值给rpc.ServerCodec。jsonrpc中的serverCodec也的确是实现了rpc.ServerCodec接口的所有方法的。
下面就关于jsonrpc实现的rpc.ServerCodec接口的每个方法逐一讲解。
ReadRequestHeader(* Request)error
这里面比较重要的是将c的method赋值给r.ServiceMethod,然后是加锁,然后将request的ID存入pending这个并发安全map中,之后将serverCodec中的serverRequest中的id置为nil,将c的seq赋值给r的seq,这个seq的主要作用是客户端的序列号,即根据这个序号将相应的调用结果返回该序号的client端,然后解锁。
而serverRequest中的ID作用是标记请求的序号,比如,相同的客户端有两次请求req1和req2,在server端调用后是response2比response1先调用结束,那么防止返回时顺序出错,加入这个id来标识哪个response该返回给哪个request结果。
ReadRequestBody(interface{})error
此函数主要讲serverCodec中的参数解析出来存入param中
WriteResponse(*Response,interface{})error
这个函数时返回client端请求的调用结果,它先是从serverCodec的pending取出client的seq为b,然后构造serverResponse,再用json编码serverResponse。
Close()error
这个函数更简单,就是关闭io.Closer
server端最后一个函数:
这个函数的功能是将server端收到的一个连接交给prc.ServeCodec处理。
client.go
client.go和server.go有很多相似的地方,这里只做初略说明
这段代码和server.go的很相似,这里不再做说明,不同的是clientCodec实现的接口是rpc.ClientCodec,rpc中ClientCodec接口的定义如下
WriteRequest(*Request,interface{})error
这个函数主要功能是将rpc.Request的method和param解析到clientCodec的clientRequest中
ReadResponseHeader(*Response)error
这里是将clientCodec中的method解析到rpc.Response中
ReadResponseBody(interface{})error
将clientCodec中的返回加过解析到x中
Close()和其他
Dial()函数则是client端用来连接到server端的函数
下面是用jsonrpc实现的一个client端和server端:
server端:
package main
import (
"crypto/md5"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
type Student struct {
Name string
Sex int
}
type Object string
//该函数是将args经过md5加密之后返回
func (this *Object) MD5(args *Student, reply *string) error {
write, _ := json.Marshal(args)
h := md5.New()
io.WriteString(h, string(write))
token := fmt.Sprintf("%x", h.Sum(nil))
fmt.Println(token)
*reply = token
return nil
}
func main() {
object := new(Object)
rpc.Register(object)
l, e := net.Listen("tcp", ":6060")
if e != nil {
log.Fatal("listen error:", e)
}
for {
conn, e := l.Accept()
if e != nil {
continue
}
fmt.Println(conn)
go jsonrpc.ServeConn(conn)
}
}
client端
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
type Student struct {
Name string
Sex int
}
func main() {
client, err := jsonrpc.Dial("tcp", "localhost:6060")
defer client.Close()
if err != nil {
log.Fatal("dialing:", err)
}
args := &Student{"初级赛亚人", 0}
var reply string
err = client.Call("Object.MD5", args, &reply)
if err != nil {
log.Fatal("object error:", err)
}
fmt.Println("reply=", reply)
}
调用结果:
reply= f084cc02af57e40f8d356f8a7775e87e
如果你还想了解一下client端的client.Call()和server端的jsonrpc.ServeConn()发生了什么,可以一步一步跟进代码看看。