rpc原理很簡單,客戶端把請求方法,引數等資訊編碼後傳給服務端,服務端反解碼後,找到對應的方法執行,並且把結果編碼後返回給客戶端。
服務端
重要結構
type Server struct {
serviceMap sync.Map // map[string]*service 註冊的服務存放
reqLock sync.Mutex // protects freeReq
freeReq *Request
respLock sync.Mutex // protects freeResp
freeResp *Response
}
type gobServerCodec struct { //使用gob實現編碼器
rwc io.ReadWriteCloser
dec *gob.Decoder
enc *gob.Encoder
encBuf *bufio.Writer
closed bool
}
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
}
整體來說,Server處理ServerCodec,其他編解碼封裝conn實現ServerCodec,也是一樣的。
server.go
type HelloService struct{}
func (p *HelloService) Hello(request common.Request, response *common.Response) error {
return nil
}
func main() {
//1.註冊服務,寫到serviceMap中
rpc.RegisterName("HelloService", new(HelloService))//把服務註冊到rpc.Server.serviceMap
listener, _ := net.Listen("tcp", ":1234")
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go rpc.ServeConn(conn)
}
}
1.封裝ServerCodec
conn會用gob包封裝,標準庫gob是golang提供的“私有”的編解碼方式,它的效率會比json,xml等更高,特別適合在Go語言程式間傳遞資料。
func (server *Server) ServeConn(conn io.ReadWriteCloser) {
buf := bufio.NewWriter(conn)
srv := &gobServerCodec{
rwc: conn,
dec: gob.NewDecoder(conn),//封裝成解碼器,從conn讀取資料並解碼
enc: gob.NewEncoder(buf),//封裝成編碼器,編碼後寫入conn
encBuf: buf,
}
server.ServeCodec(srv)
}
2.讀取請求並呼叫對應的service
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)//讀取request,service
wg.Add(1)
go service.call(server, sending, wg, mtype, req, argv, replyv, codec)//呼叫service
}
wg.Wait()
codec.Close()
}
客戶端
重要結構
type Client struct {
codec ClientCodec //編解碼封裝的conn
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
}
type ClientCodec interface {
WriteRequest(*Request, interface{}) error
ReadResponseHeader(*Response) error
ReadResponseBody(interface{}) error
Close() error
}
type gobClientCodec struct { //gob實現的ClientCodec
rwc io.ReadWriteCloser
dec *gob.Decoder
enc *gob.Encoder
encBuf *bufio.Writer
}
client.go
func main() {
rpcClient, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
fmt.Println(err)
}
var reply common.Response
var req common.Request
req.UserId = 11
//向服務端傳送"HelloService.Hello",req, 從服務端讀到reply
err = rpcClient.Call("HelloService.Hello", req, &reply)
if err != nil {
fmt.Println(err)
}
fmt.Print(reply)
}
1.生成client
使用conn即可生成client,所有如果使用tls連線,可以自定義client
func Dial(network, address string) (*Client, error) {
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
return NewClient(conn), nil
}
func NewClient(conn io.ReadWriteCloser) *Client {
encBuf := bufio.NewWriter(conn)
client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}//使用gob編解碼
return NewClientWithCodec(client)
}
2.發起呼叫
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
}
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
done = make(chan *Call, 10) // buffered.
call.Done = done
client.send(call)
return call
}
編解碼
官方庫提供了encoding/gob,encoding/json兩種編碼模式,把io.ReadWrite分別封裝為編碼器,解碼器。
//gob包,需要一個io.ReadWrite引數,分別包裝一層實現編碼後寫入,解碼後讀出
enc := gob.NewEncoder(conn)
enc.Encode(data) //相當於編碼後conn.write(data)
dec := gob.NewDecoder(conn)
dec.Decode(&m) //解碼到變數m
//也可以使用json編解碼
enc := json.NewEncoder(conn)
json.Encode(data) //相當於編碼後conn.write(data)
dec := json.NewDecoder(conn)
json.Decode(&m) //解碼到變數m, 從conn讀取資料後解碼到變數m
gob包使用案例
底層使用了go的反射功能,所以這種方式編解碼只能在go語言中使用
func main() {
info := map[string]string{
"name": "C語言中文網",
"website": "http://c.biancheng.net/golang/",
}
name := "demo.gob"
EncodeToByte(name, info)
DecodeFromFile(name)
}
func EncodeToByte(name string, data interface{}) {
fd, _ := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0777)
defer fd.Close()
enc := gob.NewEncoder(fd)//生成編碼器
if err := enc.Encode(data); err != nil {//把資料編碼後寫入到檔案中
fmt.Println(err)
}
}
func DecodeFromFile(name string) {
var m map[string]string
fd, _ := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0777)
defer fd.Close()
D := gob.NewDecoder(fd) //生成解碼器
D.Decode(&m)//從檔案讀取資料,解碼到變數中
fmt.Println(m)
}
使用json做編解碼
服務端
使用的也是go標準庫net/rpc/jsonrpc
func jsonServer() {
rpc.RegisterName("HelloService", new(HelloService)) //把服務註冊到rpc.Server.serviceMap
listener, _ := net.Listen("tcp", ":1234")
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
客戶端
func jsonClient() {
conn, err := net.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("net.Dial:", err)
}
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
http協議實現rpc
service.go
func HttpRpcServer() {
rpc.RegisterName("HelloService", new(HelloService)) //把服務註冊到rpc.Server.serviceMap
rpc.HandleHTTP()//會註冊一個預設路徑到http,Handle註冊為rpcServer
if err := http.ListenAndServe(":1234", nil); err != nil {
log.Fatal("Error serving: ", err)
}
}
client.go
func HttpRpcClient() {
rpcClient, _ := rpc.DialHTTP("tcp", ":1234") //訪問服務端註冊的預設路徑
var reply common.Response
var req common.Request
req.UserId = 11
rpcClient.Call("HelloService.Hello", req, &reply)
fmt.Print(reply)
}
客戶端rpc非同步呼叫
client.go
func HttpRpcClient() {
rpcClient, err := rpc.DialHTTP("tcp", ":1234")
if err != nil {
panic(err)
}
var reply common.Response
var req common.Request
req.UserId = 11
async := rpcClient.Go("HelloService.Hello", req, &reply, nil)
<-async.Done //等待非同步返回結果,可以放到一個單獨協程等待,不用阻塞當前協程
fmt.Print(reply)
}
有位大佬用go重新實現了net/rpc並增加了功能
7天用Go從零實現RPC框架GeeRPC
本作品採用《CC 協議》,轉載必須註明作者和本文連結