Search code examples
gotcprpcjson-rpc

How can I catch errors on the server side of a jsonrpc server?


Suppose I have a basic golang jrpc server over tcp:

package main

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type Arith int

type Args struct {
    A, B int
}

func (t *Arith) Add(args *Args, result *int) error {
    *result = args.A + args.B
    return nil
}

func RunServer() {
    arith := new(Arith)
    rpc.Register(arith)

    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        fmt.Println("Error starting server:", err)
        return
    }
    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting connection:", err)
            continue
        }

        go jsonrpc.ServeConn(conn)
    }
}

Is there a way I can print "client disconnected!" when a client disconnects from the server? Is there a callback on the net.Conn or something similar?

func main() {
    go RunServer()
    client, err := jsonrpc.Dial("tcp", "localhost:8080")
    if err != nil {
        fmt.Println("Error connecting to server:", err)
        return
    }
    

    args := &Args{A: 3, B: 5}
    var result int
    
    // run a method
    err = client.Call("Arith.Add", args, &result)
    if err != nil {
        fmt.Println("Error calling Add method:", err)
        return
    }
    fmt.Printf("Result of %d + %d = %d\n", args.A, args.B, result)
    
    // suppose the client closes the client
    client.Close()
    // how does the server detect that the client is closed?
    // would be nice if I could print "client disconnected!"
    // without having to send jsonrpc heartbeats

}

It would be nice if I did not have to send jsonrpc heartbeat messages.

I tried periodically making dummy reads from the conn in a goroutine, but this interferes with the jsonrpc connection.


Solution

  • The jsonrpc.ServeConn method returns when the client hangs up. Print the "client disconnected" message after ServeConn returns.

    func RunServer() {
        arith := new(Arith)
        rpc.Register(arith)
    
        listener, err := net.Listen("tcp", ":8080")
        if err != nil {
            fmt.Println("Error starting server:", err)
            return
        }
        defer listener.Close()
    
        for {
            conn, err := listener.Accept()
            if err != nil {
                fmt.Println("Error accepting connection:", err)
                continue
            }
    
            go func() {
               jsonrpc.ServeConn(conn)
               fmt.Println("client disconnected") // <--- new code here
            }()
        }
    }