簡介
go 提供了自帶的序列化協議gob(go binary),可以進行原生go型別的序列化和反序列化,其中一個應用就是go語言自帶的rpc功能,主要在net/rpc包下。
go 自帶的rpc提供了簡單的rpc相關的api,使用者只需要依照約定實現function然後進行服務註冊,就可以在客戶端進行呼叫了。
首先先列舉一下go rpc中的對於服務端提供的方法的相關約束:
-
the method's type is exported. 方法所屬的型別必須是外部可見的
-
the method is exported. 方法必須是外部可見的
-
the method has two arguments, both exported (or builtin) types. 方法引數只能有兩個,而且必須是外部可見的型別或者是基本型別。
-
the method's second argument is a pointer. 方法的第二個引數型別必須是指標
-
the method has return type error.方法的返回值必須是error型別
這裡結合個人的理解簡單解釋一下,方法和方法所屬的型別必須是exported,也就是必須是外部可見的,類似於java中介面的方法只能是public一樣,不然外部也不能呼叫。引數只能有兩個,而且第二個必須是指標,這是因為go rpc約定第一個引數是方法所需的入參,而第二個引數代表方法的實際返回值,所以第二個引數必須是指標型別,因為需要在方法內部修改它的值。返回值必須是error型別,表示方法執行中出現的異常或者rpc過程中的異常。
從這幾點約束可以看出來,go自帶的rpc對相關的條件約束的很緊,這也符合go的“一件問題只有一個解決方式”的理念,通過明確的規定,讓開發過程變得更簡單。
快速上手
根據上面的約束,我們先實際實現一個echo方法,它將客戶端傳遞來的引數原樣的返回:
1.首先服務端提供的方法必須歸屬於一個型別,而且是外部可見的型別,這裡我們就定義一個EchoService的空結構好了:
type EchoService struct {}
複製程式碼
2.服務端提供的方法必須也是外部可見的,所以定義一個方法叫做Echo:
func (service EchoService) Echo(arg string, result *string) error {
*result = arg //在這裡直接將第二個引數(也就是實際的返回值)賦值為arg
return nil //error返回nil,也就是沒有發生異常
}
複製程式碼
3.接下來我們將Echo方法對外進行暴露:
func RegisterAndServe() {
err := rpc.Register(&EchoService{})//註冊並不是註冊方法,而是註冊EchoService的一個例項
if err != nil {
log.Fatal("error registering", err)
return
}
rpc.HandleHTTP() //rpc通訊協議設定為http協議
err = http.ListenAndServe(":1234", nil) //埠設定為9999
if err != nil {
log.Fatal("error listening", err)
}
}
複製程式碼
4.然後我們定義一個客戶端:
func CallEcho(arg string) (result string, err error) {
var client *rpc.Client
client, err = rpc.DialHTTP("tcp", ":9999") //通過rpc.DialHTTP建立一個client
if err != nil {
return "", err
}
err = client.Call("EchoService.Echo", arg, &result) //通過型別加方法名指定要呼叫的方法
if err != nil {
return "", err
}
return result, err
}
複製程式碼
5.最後分別啟動服務端和客戶端進行呼叫:
func main() {
done := make(chan int)
go server.RegisterAndServe() //先啟動服務端
time.Sleep(1e9) //sleep 1s,因為服務端啟動是非同步的,所以等一等
go func() { //啟動客戶端
result, err := client.CallEcho("hello world")
if err != nil {
log.Fatal("error calling", err)
} else {
fmt.Println("call echo:", result)
}
done <- 1
}()
<- done //阻塞等待客戶端結束
}
複製程式碼
此外go自帶的rpc還提供rpc over tcp的選項,只需要在listen和dial時使用tcp連線就可以了,rpc over tcp和這裡的例子唯一的區別就是建立連線時的區別,實際的rpc over http也並沒有使用http協議,只是用http server建立連線而已。
go還提供基於json的rpc,只需要在服務端和客戶端把rpc.ServeConn和rpc.Dial替換成jsonrpc.ServeConn和jsonrpc.Dial即可。
原始碼解析
Server端
PRC over HTTP
第一個示例中,我們呼叫了rpc.HandleHTTP(),它的作用是將rpc server繫結到http埠上,執行了這個方法之後我們仍然需要主動呼叫http.ListenAndServe。rpc.HandleHTTP的具體實現如下:
const (
// Defaults used by HandleHTTP
DefaultRPCPath = "/_goRPC_"
DefaultDebugPath = "/debug/rpc"
)
// HandleHTTP registers an HTTP handler for RPC messages to DefaultServer
// on DefaultRPCPath and a debugging handler on DefaultDebugPath.
// It is still necessary to invoke http.Serve(), typically in a go statement.
func HandleHTTP() {
DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)
}
複製程式碼
可以看見,HandleHTTP方法呼叫了DefaultServer的HandleHTTP方法,DefaultServer是rpc包內定義的一個Server型別變數,Server定義了很多方法:
- func (server *Server) Accept(lis net.Listener)
- func (server *Server) HandleHTTP(rpcPath, debugPath string)
- func (server *Server) ServeCodec(codec ServerCodec)
- func (server *Server) ServeConn(conn io.ReadWriteCloser)
- func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (server *Server) ServeRequest(codec ServerCodec) error
DefaultServer就是一個Server例項:
// DefaultServer is the default instance of *Server.
var DefaultServer = NewServer()
// NewServer returns a new Server.
func NewServer() *Server {
return &Server{}
}
複製程式碼
其中HandleHTTP的具體實現如下:
// HandleHTTP registers an HTTP handler for RPC messages on rpcPath,
// and a debugging handler on debugPath.
// It is still necessary to invoke http.Serve(), typically in a go statement.
func (server *Server) HandleHTTP(rpcPath, debugPath string) {
http.Handle(rpcPath, server)
http.Handle(debugPath, debugHTTP{server})
}
複製程式碼
實際上HandleHTTP就是使用http包的功能,將server自身註冊到http的url對映上了;從上面列舉的Server型別的部分方法可以看出,Server自身實現了ServeHTTP方法,所以可以處理http請求:
// ServeHTTP implements an http.Handler that answers RPC requests.
func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "CONNECT" {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusMethodNotAllowed)
io.WriteString(w, "405 must CONNECT\n")
return
}
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
return
}
io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
server.ServeConn(conn)
}
複製程式碼
可以看到,rpc server收到http連線之後就會呼叫hijack方法接管這個連線,然後呼叫ServeConn方法進行處理,而ServeConn方法就和rpc over tcp沒區別了。
總的來說,rpc over http就是利用http包接收來自客戶端的連線,後續的流程和rpc over TCP一樣。
RPC over TCP
根據上面的第二個例子我們可以看到,在使用rpc over tcp時,使用者需要自己建立一個Listener並呼叫Accpet,然後呼叫Server的ServeConn方法。而我們之前使用的rpc.ServeConn實際上呼叫了DefaultServer.ServeConn。而ServeConn的具體實現如下:
// ServeConn runs the server on a single connection.
// ServeConn blocks, serving the connection until the client hangs up.
// The caller typically invokes ServeConn in a go statement.
// ServeConn uses the gob wire format (see package gob) on the
// connection. To use an alternate codec, use ServeCodec.
// See NewClient's comment for information about concurrent access.
func (server *Server) ServeConn(conn io.ReadWriteCloser) {
buf := bufio.NewWriter(conn)
srv := &gobServerCodec{
rwc: conn,
dec: gob.NewDecoder(conn),
enc: gob.NewEncoder(buf),
encBuf: buf,
}
server.ServeCodec(srv) //構造了一個私有的gobServerCodec然後呼叫servCodec方法,表示預設使用gob序列化協議
}
複製程式碼
可以看到,ServeConn實際上是構造了一個codec然後呼叫serveCodec方法,預設的邏輯是採用gobServerCodec,由此可以看出,如果我們想使用自定義的序列化協議,只需要實現一個自己的ServerCodec就可以了,serverCodec介面定義如下:
// A ServerCodec implements reading of RPC requests and writing of
// RPC responses for the server side of an RPC session.
// The server calls ReadRequestHeader and ReadRequestBody in pairs
// to read requests from the connection, and it calls WriteResponse to
// write a response back. The server calls Close when finished with the
// connection. ReadRequestBody may be called with a nil
// argument to force the body of the request to be read and discarded.
// See NewClient's comment for information about concurrent access.
type ServerCodec interface {
ReadRequestHeader(*Request) error
ReadRequestBody(interface{}) error
WriteResponse(*Response, interface{}) error
// Close can be called multiple times and must be idempotent.
Close() error
}
複製程式碼
ServerCodec的方法定義裡只出現了Request和Response,並沒有連線相關的定義,說明在連線相關的變數需要設定成ServerCodec的成員變數,每次呼叫都需要構造新的ServerCodec物件。
回到serveCodec方法,可以看到serveCodec的流程基本上就是:read request - invoke - close
// ServeCodec is like ServeConn but uses the specified codec to
// decode requests and encode responses.
func (server *Server) ServeCodec(codec ServerCodec) {
sending := new(sync.Mutex)
wg := new(sync.WaitGroup)
for {
service, mtype, req, argv, replyv, keepReading, err := server.readRequest(codec)
if err != nil {
if debugLog && err != io.EOF {
log.Println("rpc:", err)
}
if !keepReading {
break
}
// send a response if we actually managed to read a header.
if req != nil {
server.sendResponse(sending, req, invalidRequest, codec, err.Error())
server.freeRequest(req)
}
continue
}
wg.Add(1)
//每個請求的處理都在新的goroutine裡執行
go service.call(server, sending, wg, mtype, req, argv, replyv, codec)
}
// We've seen that there are no more requests.
// Wait for responses to be sent before closing codec.
wg.Wait()
codec.Close()
}
複製程式碼
這裡看到,serveCodec會一直呼叫ReadRequestHeader和ReadRequestBody方法讀取請求,直到客戶端連線不再傳送請求,在serveConn方法的註釋裡也提到了,對於serveConn方法通常建議使用goroutine來執行。
接下來仔細看一下readRequest的實現:
func (server *Server) readRequest(codec ServerCodec) (service *service, mtype *methodType, req *Request, argv, replyv reflect.Value, keepReading bool, err error) {
service, mtype, req, keepReading, err = server.readRequestHeader(codec)
if err != nil {
if !keepReading {
return
}
// discard body
codec.ReadRequestBody(nil)
return
}
// Decode the argument value.
argIsValue := false // if true, need to indirect before calling.
if mtype.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(mtype.ArgType.Elem())
} else {
argv = reflect.New(mtype.ArgType)
argIsValue = true
}
// argv guaranteed to be a pointer now.
if err = codec.ReadRequestBody(argv.Interface()); err != nil {
return
}
if argIsValue {
argv = argv.Elem()
}
replyv = reflect.New(mtype.ReplyType.Elem())
switch mtype.ReplyType.Elem().Kind() {
case reflect.Map:
replyv.Elem().Set(reflect.MakeMap(mtype.ReplyType.Elem()))
case reflect.Slice:
replyv.Elem().Set(reflect.MakeSlice(mtype.ReplyType.Elem(), 0, 0))
}
return
}
func (server *Server) readRequestHeader(codec ServerCodec) (svc *service, mtype *methodType, req *Request, keepReading bool, err error) {
// Grab the request header.
req = server.getRequest()
err = codec.ReadRequestHeader(req)
if err != nil {
req = nil
if err == io.EOF || err == io.ErrUnexpectedEOF {
return
}
err = errors.New("rpc: server cannot decode request: " + err.Error())
return
}
// We read the header successfully. If we see an error now,
// we can still recover and move on to the next request.
keepReading = true
dot := strings.LastIndex(req.ServiceMethod, ".")
if dot < 0 {
err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod)
return
}
serviceName := req.ServiceMethod[:dot]
methodName := req.ServiceMethod[dot+1:]
// Look up the request.
svci, ok := server.serviceMap.Load(serviceName)
if !ok {
err = errors.New("rpc: can't find service " + req.ServiceMethod)
return
}
svc = svci.(*service)
mtype = svc.method[methodName]
if mtype == nil {
err = errors.New("rpc: can't find method " + req.ServiceMethod)
}
return
}
複製程式碼
基本就是依次呼叫codec的readRequestHeader和readRequestBody,過程中會使用go自帶的gob序列化協議,這裡先不深入看,免得層次太深亂了。
接下來看invoke部分:
func (s *service) call(server *Server, sending *sync.Mutex, wg *sync.WaitGroup, mtype *methodType, req *Request, argv, replyv reflect.Value, codec ServerCodec) {
//wg由ServeConn方法持有,用於阻塞等待呼叫方斷開,這裡每次處理一個請求就count down一次
if wg != nil {
defer wg.Done()
}
//對方法加鎖,就為了把呼叫次數加一
mtype.Lock()
//呼叫次數加一,暫時沒看到是幹啥的
mtype.numCalls++
mtype.Unlock()
function := mtype.method.Func
// Invoke the method, providing a new value for the reply.
returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
// The return value for the method is an error.
errInter := returnValues[0].Interface()
errmsg := ""
if errInter != nil {
errmsg = errInter.(error).Error()
}
server.sendResponse(sending, req, replyv.Interface(), codec, errmsg)
server.freeRequest(req)
}
複製程式碼
invoke部分就是通過反射呼叫對應例項的方法,然後將結果通過sendResponse返回給客戶端,sendResponse實際上也是呼叫了codec的WriteResponse方法:
func (server *Server) sendResponse(sending *sync.Mutex, req *Request, reply interface{}, codec ServerCodec, errmsg string) {
resp := server.getResponse()
// Encode the response header
resp.ServiceMethod = req.ServiceMethod
if errmsg != "" {
resp.Error = errmsg
reply = invalidRequest
}
resp.Seq = req.Seq
sending.Lock()
err := codec.WriteResponse(resp, reply)
if debugLog && err != nil {
log.Println("rpc: writing response:", err)
}
sending.Unlock()
server.freeResponse(resp)
}
複製程式碼
這裡可以看到,服務端在傳送資料過程中是加了鎖的,也就是WriteResponse部分是序列的。
服務端流程大致就到此為止了,整體思路還是基本的RPC流程:通過Listener建立連線,呼叫codec進行編解碼,通過反射執行真正的方法。server會讀取到的request物件快取在記憶體中,具體是一個連結串列的格式,直到server端邏輯執行完
這裡再簡單看一下rpc server相關的其他部分:
rpc.Register:register方法通過反射會把引數對應的型別下所有符合規範的方法載入並快取起來
// Register publishes in the server the set of methods of the
// receiver value that satisfy the following conditions:
// - exported method of exported type
// - two arguments, both of exported type
// - the second argument is a pointer
// - one return value, of type error
// It returns an error if the receiver is not an exported type or has
// no suitable methods. It also logs the error using package log.
// The client accesses each method using a string of the form "Type.Method",
// where Type is the receiver's concrete type.
func (server *Server) Register(rcvr interface{}) error {
return server.register(rcvr, "", false)
}
// RegisterName is like Register but uses the provided name for the type
// instead of the receiver's concrete type.
func (server *Server) RegisterName(name string, rcvr interface{}) error {
return server.register(rcvr, name, true)
}
func (server *Server) register(rcvr interface{}, name string, useName bool) error {
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
sname := reflect.Indirect(s.rcvr).Type().Name()
if useName {
sname = name
}
if sname == "" {
s := "rpc.Register: no service name for type " + s.typ.String()
log.Print(s)
return errors.New(s)
}
if !isExported(sname) && !useName {
s := "rpc.Register: type " + sname + " is not exported"
log.Print(s)
return errors.New(s)
}
s.name = sname
// Install the methods
s.method = suitableMethods(s.typ, true)
if len(s.method) == 0 {
str := ""
// To help the user, see if a pointer receiver would work.
method := suitableMethods(reflect.PtrTo(s.typ), false)
if len(method) != 0 {
str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
} else {
str = "rpc.Register: type " + sname + " has no exported methods of suitable type"
}
log.Print(str)
return errors.New(str)
}
if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
return errors.New("rpc: service already defined: " + sname)
}
return nil
}
複製程式碼
rpc包內定義的各個struct:service、methodType、Server、Request、Response
type service struct { //儲存了服務提供者的各個資訊,包括名稱、型別、方法等等
name string // name of service
rcvr reflect.Value // receiver of methods for the service
typ reflect.Type // type of the receiver
method map[string]*methodType // registered methods
}
type methodType struct {//儲存了反射獲取到的方法的相關資訊,此外還有一個計數器,用來統計呼叫次數,還有一個繼承的Mutext介面,用來做計數器的同步
sync.Mutex // protects counters
method reflect.Method
ArgType reflect.Type
ReplyType reflect.Type
numCalls uint
}
// Server represents an RPC Server.
type Server struct { //server物件
serviceMap sync.Map // map[string]*service 儲存服務提供者資訊的map
reqLock sync.Mutex // protects freeReq 用於做freeReq的同步
freeReq *Request //rpc 請求
respLock sync.Mutex // protects freeResp 用於做freeResp的同步
freeResp *Response //rpc響應
}
// Request is a header written before every RPC call. It is used internally
// but documented here as an aid to debugging, such as when analyzing
// network traffic.
type Request struct { //Request僅標識請求頭,只包含一些後設資料
ServiceMethod string // format: "Service.Method"
Seq uint64 // sequence number chosen by client
next *Request // for free list in Server
}
// Response is a header written before every RPC return. It is used internally
// but documented here as an aid to debugging, such as when analyzing
// network traffic.
type Response struct {//Response僅標識請求頭,只包含一些後設資料
ServiceMethod string // echoes that of the Request
Seq uint64 // echoes that of the request
Error string // error, if any.
next *Response // for free list in Server
}
複製程式碼
我們可以留意到,Request和Response被定義成了連結串列一樣的結構,而且Server還在request和response上加了同步,這是因為在server中req和resp是複用的,而不是每次處理請求都建立新的物件,具體可以從getRequest/getResponse/freeReqeust/freeResponse看出來:
func (server *Server) getRequest() *Request {
server.reqLock.Lock()
req := server.freeReq
if req == nil {
req = new(Request)
} else {
server.freeReq = req.next
*req = Request{}
}
server.reqLock.Unlock()
return req
}
func (server *Server) freeRequest(req *Request) {
server.reqLock.Lock()
req.next = server.freeReq
server.freeReq = req
server.reqLock.Unlock()
}
func (server *Server) getResponse() *Response {
server.respLock.Lock()
resp := server.freeResp
if resp == nil {
resp = new(Response)
} else {
server.freeResp = resp.next
*resp = Response{}
}
server.respLock.Unlock()
return resp
}
func (server *Server) freeResponse(resp *Response) {
server.respLock.Lock()
resp.next = server.freeResp
server.freeResp = resp
server.respLock.Unlock()
}
複製程式碼
Client端
客戶端可以通過以下幾種方式和服務端建立連線:
-
func Dial(network, address string) (*Client, error) //直接建立tcp連線
-
func DialHTTP(network, address string) (*Client, error) //通過http傳送connect請求建立連線,使用預設的PATH
-
func DialHTTPPath(network, address, path string) (*Client, - error)//通過http傳送connect請求建立連線,使用自定義的PATH
-
func NewClient(conn io.ReadWriteCloser) *Client //根據給定的連線建立rpc客戶端
-
func NewClientWithCodec(codec ClientCodec) *Client //根據給定的ClientCodec建立rpc客戶端
客戶端的呼叫方式有兩種:Call和Go,其中Call是同步呼叫,而Go則是非同步呼叫。其中Call的返回值是error型別,而Go的返回值是Call型別。實際上Call也是呼叫Go方法實現的,只是在Call會阻塞等待Go方法返回結果而已。這裡還有一個問題,就是呼叫時只能通過channel控制超時,底層的邏輯不會有超時,如果server端一直不返回,客戶端快取的請求就會一直不釋放,導致洩漏。
// Go invokes the function asynchronously. It returns the Call structure representing
// the invocation. The done channel will signal when the call is complete by returning
// the same Call object. If done is nil, Go will allocate a new channel.
// If non-nil, done must be buffered or Go will deliberately crash.
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
call := new(Call)
call.ServiceMethod = serviceMethod
call.Args = args
call.Reply = reply
if done == nil {
done = make(chan *Call, 10) // buffered.
} else {
// If caller passes done != nil, it must arrange that
// done has enough buffer for the number of simultaneous
// RPCs that will be using that channel. If the channel
// is totally unbuffered, it's best not to run at all.
if cap(done) == 0 {
log.Panic("rpc: done channel is unbuffered")
}
}
call.Done = done
client.send(call)
return call
}
// Call invokes the named function, waits for it to complete, and returns its error status.
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {
call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
return call.Error
}
複製程式碼
這裡需要注意的是,Go方法接收一個channel型別的done作為結束的標記,而且這個channel必須是有緩衝的,至於為什麼,我猜測是防止往done裡寫入發生阻塞,具體還需要再確認下。
與server的實現類似,客戶端提供了一個ClientCodec的介面,用來做請求和響應的解析,其中的方法這裡就不列舉了。
下面看一下客戶端構造時的邏輯:
// Client represents an RPC Client.
// There may be multiple outstanding Calls associated
// with a single Client, and a Client may be used by
// multiple goroutines simultaneously.
type Client struct {
codec ClientCodec
reqMutex sync.Mutex // protects following
request Request
mutex sync.Mutex // protects following
seq uint64
pending map[uint64]*Call
closing bool // user has called Close
shutdown bool // server has told us to stop
}
// NewClient returns a new Client to handle requests to the
// set of services at the other end of the connection.
// It adds a buffer to the write side of the connection so
// the header and payload are sent as a unit.
//
// The read and write halves of the connection are serialized independently,
// so no interlocking is required. However each half may be accessed
// concurrently so the implementation of conn should protect against
// concurrent reads or concurrent writes.
func NewClient(conn io.ReadWriteCloser) *Client {
encBuf := bufio.NewWriter(conn)
client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
return NewClientWithCodec(client)
}
// NewClientWithCodec is like NewClient but uses the specified
// codec to encode requests and decode responses.
func NewClientWithCodec(codec ClientCodec) *Client {
client := &Client{
codec: codec,
pending: make(map[uint64]*Call),
}
go client.input()
return client
}
複製程式碼
Client物件中包含一個map型別的pending,用來快取所有未完成的請求,同時對request和seq做了同步。
可以看到,如果使用預設的NewClient方法,則會構造一個gobClientCodec,它使用gob作為序列化協議;也可以自己指定一個codec。
在構造時,會執行go client.input(),這個input方法就是client接收響應的邏輯了,這個方法會在迴圈中讀取響應,根據響應的seq找到對應的請求,然後通過請求的done傳送訊號。
func (client *Client) input() {
var err error
var response Response
for err == nil {
response = Response{}
err = client.codec.ReadResponseHeader(&response)
if err != nil {
break
}
seq := response.Seq
client.mutex.Lock()
call := client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
switch {
case call == nil:
// We've got no pending call. That usually means that
// WriteRequest partially failed, and call was already
// removed; response is a server telling us about an
// error reading request body. We should still attempt
// to read error body, but there's no one to give it to.
err = client.codec.ReadResponseBody(nil)
if err != nil {
err = errors.New("reading error body: " + err.Error())
}
case response.Error != "":
// We've got an error response. Give this to the request;
// any subsequent requests will get the ReadResponseBody
// error if there is one.
call.Error = ServerError(response.Error)
err = client.codec.ReadResponseBody(nil)
if err != nil {
err = errors.New("reading error body: " + err.Error())
}
call.done()
default:
err = client.codec.ReadResponseBody(call.Reply)
if err != nil {
call.Error = errors.New("reading body " + err.Error())
}
call.done()
}
}
// Terminate pending calls.
client.reqMutex.Lock()
client.mutex.Lock()
client.shutdown = true
closing := client.closing
if err == io.EOF {
if closing {
err = ErrShutdown
} else {
err = io.ErrUnexpectedEOF
}
}
for _, call := range client.pending {
call.Error = err
call.done()
}
client.mutex.Unlock()
client.reqMutex.Unlock()
if debugLog && err != io.EOF && !closing {
log.Println("rpc: client protocol error:", err)
}
}
複製程式碼
codec
除了go自帶的gob序列化,使用者還可以使用其他的序列化方式,包括前面提到的json。go 提供了json格式的rpc,可以支援跨語言的呼叫。
其他
值得注意的是,net/rpc下的內容目前已經不再更新(freeze)了,具體參考:github.com/golang/go/i…
網上有部落格說go 自帶的rpc效能遠優於grpc,不再更新的原因可能只是開發團隊不再願意花費過多精力而已。