1. References
"Analysis of Blockchain Core Algorithms", page 15.
2. Three files: main.go, service.go, client.go
You can compile the three files in the same directory without using third-party libraries. Compiled and executable. Client and server are in the same process.
3.main.go
package main import ( "time" ) func main() { done := make(chan int) go startServer() time.Sleep(1000 * time.Millisecond) go runClient() <-done }
4.service.go
package main import ( "fmt" "log" "net" "net/rpc" "net/rpc/jsonrpc" "strconv" "strings" ) //server input parameters type Args struct { StrParams string } //Server output parameters type Reply struct { StrResult string } // structure of the service type PaxosService struct { //process parameters Host string `The host name or ip address where the process is located` Port string `The port number used by this process` //paxos parameter T_max int `Currently issued maximum ticket number` C string `Currently stored command` T_store int `Store ticket for command C` } // service test function func (ps *PaxosService) Test1(args *Args, reply *Reply) error { reply.StrResult = args.StrParams + "123" return nil } func (ps *PaxosService) Process(args *Args, reply *Reply) error { reply.StrResult = args.StrParams + "123" //segment parameters params := strings.Split(args.StrParams, ":") // output // for _, p := range params { // fmt.Println(p) // } //reply according to the parameters switch params[2] { case "require_ticket": // Phase 1 client request ticket fmt.Println("Phase 1 Client Request Ticket") require_t, err := strconv.Atoi(params[3]) if err != nil { fmt.Println("Failed to convert string to integer", err, params[3]) return err } fmt.Println("Phase 1 ,t=", require_t) if require_t > ps.T_max { ps.T_max = require_t reply.StrResult = ps.Host + ":" + ps.Port + ":" + "response_ticket" + ":" + "ok" + ":" + ps.C } case "require_propose": fmt.Println("Phase 2 client request propose") require_t, err := strconv.Atoi(params[3]) if err != nil { fmt.Println("Failed to convert string to integer", err, params[3]) return err } fmt.Println("require_t=", require_t, ", ps.T_max=", ps.T_max) if require_t == ps.T_max { ps.C = params[4] ps.T_store = require_t reply.StrResult = ps.Host + ":" + ps.Port + ":" + "response_propose" + ":" + "success" } case "require_commit": fmt.Println("Phase 3 client request commit") require_t, err := strconv.Atoi(params[3]) if err != nil { fmt.Println("Failed to convert string to integer", err, params[3]) return err } if require_t == ps.T_store { if params[4] == ps.C { reply.StrResult = ps.Host + ":" + ps.Port + ":" + "response_propose" + ":" + "run" + ":" + params[4] } } default: fmt.Println("The command is unknown, the stage cannot be confirmed, return fail") reply.StrResult = ps.Host + ":" + ps.Port + ":" + "fail" } return nil } // start the service func startServer() { fmt.Println("start server...") ps: = PaxosService { Host: "127.0.0.1", Port: "1234", T_max: 0, C: "none", T_store: 0, } server := rpc.NewServer() server.Register(&ps) server.HandleHTTP(rpc.DefaultRPCPath, rpc.DefaultDebugPath) listener, e := net.Listen("tcp", ":1234") if e != nil { log.Fatal("listen error:", e) } fmt.Println("server running...") for { if conn, err := listener.Accept(); err != nil { log.Fatal("accept error: " + err.Error()) } else { log.Printf("new connection established\n") go server.ServeCodec(jsonrpc.NewServerCodec(conn)) } } }
5.client.go
package main import ( "fmt" "log" "net" "net/rpc/jsonrpc" ) //Run the client once, submit the parameters, and get the result func runClient () { client, err := net.Dial("tcp", "127.0.0.1:1234") if err != nil { log.Fatal("dialing:", err) } //The first stage request ticket args := &Args{"127.0.0.1:1234:require_ticket:34"} var reply Reply c := jsonrpc.NewClient(client) err = c.Call("PaxosService.Process", args, &reply) if err != nil { log.Fatal("PaxosService error:", err) } fmt.Println("阶段1 response:", reply.StrResult) //The second stage propose args = &Args{"127.0.0.1:1234:require_propose:34:akp"} err = c.Call("PaxosService.Process", args, &reply) if err != nil { log.Fatal("PaxosService error:", err) } fmt.Println("阶段2 response:", reply.StrResult) // Phase 3 commit args = &Args{"127.0.0.1:1234:require_commit:34:akp"} err = c.Call("PaxosService.Process", args, &reply) if err != nil { log.Fatal("PaxosService error:", err) } fmt.Println("阶段3 response:", reply.StrResult) }