Package RPC
Remote precedure call
The package net/rpc allows Go programs to communicate each others. The package provides access to remote exported methods of an object, implementing a client-server paradigm. Methods have made visible to remote clients as a service with the name of the type of the object. Objects must be exported and registered on the server in order to be accessible. A server may register multiple objects of different types but you can not register objects of the same type.Only the exported methods that meet certain conditions are made accessible:
- the method has two arguments, both exported (or builtin) types
- the method's second argument is a pointer, but usually also the first is a pointer
- the method has return type error
The methods that meet these criteria are remotely accessible, other methods of the object are ignored and not accessible.
Communication occurs serializing the parameters through the gob format. The transport is done using a raw network connection (TCP protocol) or HTTP (HTTP over TCP protocol).
Here a non-trivial example.
The data model
These are objects used as an interchange between client and server.
package model
type Order struct {
IdCustomer int
Items []*Product
}
type Product struct {
Id int
Name string
Quantity int
Cost float64
}
type OrderReference struct {
Id int
IdCustomer int
Total float64
}
The server
The server exposes the method RegisterOrder to store orders.
package main
import(
"fmt"
"log"
"errors"
"net"
"net/rpc"
"net/http"
"github.com/maxzerbini/packagemain/rpc/model"
)
type OrderServer struct {
endpoint string
referenceId int
}
// Register a new order.
func (srv *OrderServer) RegisterOrder(order *model.Order, reply *model.OrderReference) (err error) {
defer func() {
// Executes normally even if there is a panic
if e:= recover(); e != nil {
log.Println("Run time panic: %v", e)
err = errors.New("Runtime error.")
}
}()
// simulate order management creating a order reference id
// (in a true case the order will be saved on a database)
log.Printf("Order received %v", order)
reply.Id = srv.referenceId
for _,item := range order.Items {
reply.Total += item.Cost * float64(item.Quantity)
}
reply.IdCustomer = order.IdCustomer
return err
}
// Start listening commands.
func (srv *OrderServer)Do(){
rpc.Register(srv)
rpc.HandleHTTP()
listener, e := net.Listen("tcp", srv.endpoint)
if e != nil {
log.Fatal("Starting RPC-server -listen error:", e)
}
log.Println("Server started ...")
http.Serve(listener, nil)
}
func NewOrderServer(endpoint string) *OrderServer{
s := &OrderServer{endpoint:endpoint, referenceId:1}
return s
}
func main(){
endpoint := "localhost:9000"
s := NewOrderServer(endpoint)
go s.Do()
_,_ = fmt.Scanln()
}
The client
The client sends commands to the server and get the ID generated by the server.
package client
import(
"log"
"errors"
"net/rpc"
"github.com/maxzerbini/packagemain/rpc/model"
)
type OrderClient struct {
endpoint string
client *rpc.Client
}
func (nc *OrderClient) ConnectClient(){
defer func() {
// Println executes normally even if there is a panic
if err := recover(); err != nil {
log.Println("run time panic: %v", err)
}
}()
client, err := rpc.DialHTTP("tcp", nc.endpoint)
if err != nil {
log.Printf("Error dialing server: %v \r\n", err)
} else {
log.Printf("Dialing server on %s done.\r\n",nc.endpoint)
nc.client = client
}
}
// Send an order
func (nc *OrderClient) SendOrder(order *model.Order) ( reference *model.OrderReference, err error) {
defer func() {
// executes normally even if there is a panic
if e := recover(); e != nil {
log.Printf("Error %v\r\n",e)
reference = nil
err = errors.New("Client runtime error.")
}
}()
reference = new (model.OrderReference)
err = nc.client.Call("OrderServer.RegisterOrder", order, ref)
if err != nil {
reference = nil
log.Println("OrderServer.RegisterOrder error: ", err)
}
return reference, err
}
func NewOrderClient(endpoint string) *OrderClient{
c := &OrderClient{endpoint:endpoint}
c.ConnectClient()
return c
}
Testing the client
This is an example of a call between clients and servers.
package main
import(
"log"
"github.com/maxzerbini/packagemain/rpc/model"
"github.com/maxzerbini/packagemain/rpc/client"
)
func main(){
endpoint := "localhost:9000"
c := client.NewOrderClient(endpoint)
o := &model.Order{IdCustomer:101,Items:make([]*model.Product,10)}
for i := 0; i<10; i++ {
o.Items[i] =&model.Product{Id:i,Name:"product",Quantity:1,Cost:5.3}
}
if ref, err := c.SendOrder(o); err == nil {
log.Printf("Order reference: %v\r\n", ref)
} else {
log.Printf("Error: %v", err)
}
}
The full example code can be downloaded here:https://github.com/maxzerbini/packagemain/tree/master/rpc
Commenti
Posta un commento