eye_water
eye_water

Reputation: 107

Golang rpc get wrong struct variable

  1. I registered two RPC services on port numbers 8000 and 8100, they are server1 and server2 respectively.
  2. Then I created an RPC connection on the server1 side, and the destination of the connection is server2.
  3. After the connection is successfully established, I call the connection to make a remote call.
  4. The remote call is successful, but when I get the port number of the callee of the remote call, sometimes I get the port number of server1 and sometimes the port number of server2. In my understanding, the callee of the remote call is serve2, and the program should return the port number of server2.
  5. What caused this?

My code:

package main

import (
    "log"
    "net"
    "net/rpc"
    "sync"
)

type RPCServer struct {
    port    string
    clients map[string]rpc.Client
}

type MessageArgs struct {
    Sender  string
    Message string
}

type MessageReply struct {
    Receive bool
}

func (rs *RPCServer) SendMessage(args *MessageArgs, reply *MessageReply) error {
    log.Printf("current rpc server [%s] Receive Message [%s] from Sender[%s]", rs.port, args.Message, args.Sender)
    reply.Receive = true
    return nil
}

func (rs *RPCServer) server(wg *sync.WaitGroup) {
    l, e := net.Listen("tcp", rs.port)
    if e != nil {
        log.Fatal("listen error: ", e)
    } else {
        log.Printf("server start successfully on port %s\n", rs.port)
    }
    rpc.Register(rs)

    // wait for all server construct but not wait for loop
    wg.Done()

    for {
        conn, err := l.Accept()
        if err != nil {
            log.Fatal("accept error: ", err)
        }

        go rpc.ServeConn(conn)
    }
}

func (rs *RPCServer) connect(port string) {
    client, err := rpc.Dial("tcp", port)
    if err != nil {
        log.Fatal("dialing: ", err)
    } else {
        log.Printf("[%s] server connect other server [%s] successfully.\n", rs.port, port)
    }
    rs.clients[port] = *client
}

func main() {
    servers := make([]RPCServer, 2)
    ports := [2]string{":8000", ":8100"}
    for i := range servers {
        servers[i].clients = make(map[string]rpc.Client)
        servers[i].port = ports[i]
    }

    // start all rpc server
    var wg sync.WaitGroup
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go servers[i].server(&wg)
    }
    wg.Wait()

    servers[0].connect(":8100")

    args := &MessageArgs{Message: "Hello", Sender: ":8000"}
    reply := &MessageReply{}
    clientCall := servers[0].clients[":8100"]
    err := clientCall.Call("RPCServer.SendMessage", args, reply)
    if err != nil {
        log.Fatal(err)
    }
}

Sometimes the output of the program is

2021/07/07 14:39:10 server start successfully on port :8000
2021/07/07 14:39:10 server start successfully on port :8100
2021/07/07 14:39:10 [:8000] server connect other server [:8100] successfully.
2021/07/07 14:39:10 current rpc server [:8000] Receive Message [Hello] from Sender[:8000]

Sometimes the output of the program is

2021/07/07 14:39:16 server start successfully on port :8100
2021/07/07 14:39:16 server start successfully on port :8000
2021/07/07 14:39:16 [:8000] server connect other server [:8100] successfully.
2021/07/07 14:39:16 current rpc server [:8100] Receive Message [Hello] from Sender[:8000]

Upvotes: 2

Views: 1052

Answers (1)

wasmup
wasmup

Reputation: 16253

You are calling rpc.Register(rs) twice:

  1. Add error check to your code:
    err := rpc.Register(rs)
    if err != nil {
        log.Println(err)
    }

You'll see:

rpc: service already defined: RPCServer

Since you are running two goroutines here:

for i := 0; i < 2; i++ {
    wg.Add(1)
    go servers[i].server(&wg)
}

Then both of them will register an rpc server using:

rpc.Register(rs)

So one of them is the real registered server by chance. Since the time to run a goroutine is not known, it will vary by each run

Simply register just one server. e.g.:

    go servers[1].server(&wg)


  1. Code review notes:
rs.clients[port] = *client

contains sync.Mutex and:

A Mutex must not be copied after first use.

Upvotes: 2

Related Questions