TCP RPC 上面我们实现了基于HTTP协议的RPC,接下来我们要实现基于TCP协议的RPC,服务端的实现代码如下所示: ~~~ package main import ( "errors" "fmt" "net" "net/rpc" "os" ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } type Arith int func (t *Arith) Multiply(args *Args, reply *int) error { *reply = args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B == 0 { return errors.New("divide by zero") } quo.Quo = args.A / args.B quo.Rem = args.A % args.B return nil } func main() { arith := new(Arith) /* Register在server注册并公布rcvr的方法集中满足如下要求的方法: - 方法是导出的 - 方法有两个参数,都是导出类型或内建类型 - 方法的第二个参数是指针 - 方法只有一个error接口类型的返回值 如果rcvr不是一个导出类型的值,或者该类型没有满足要求的方法,Register会返回错误。Register也会使用log包将错误写入日志。客户端可以使用格式为"Type.Method"的字符串访问这些方法,其中Type是rcvr的具体类型。 */ rpc.Register(arith) /* ResolveTCPAddr将addr作为TCP地址解析并返回。参数addr格式为"host:port"或"[ipv6-host%zone]:port",解析得到网络名和端口名;net必须是"tcp"、"tcp4"或"tcp6"。 IPv6地址字面值/名称必须用方括号包起来,如"[::1]:80"、"[ipv6-host]:http"或"[ipv6-host%zone]:80"。 */ tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") checkError(err) /* ListenTCP在本地TCP地址laddr上声明并返回一个*TCPListener,net参数必须是"tcp"、"tcp4"、"tcp6",如果laddr的端口字段为0,函数将选择一个当前可用的端口,可以用Listener的Addr方法获得该端口。 */ listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { // AcceptTCP接收下一个呼叫,并返回一个新的*TCPConn。 conn, err := listener.Accept() if err != nil { continue } /* ServeConn在单个连接上执行DefaultServer。ServeConn会阻塞,服务该连接直到客户端挂起。调用者一般应另开线程调用本函数:"go ServeConn(conn)"。ServeConn在该连接使用gob(参见encoding/gob包)有线格式。要使用其他的编解码器,可调用ServeCodec方法。 */ rpc.ServeConn(conn) } } func checkError(err error) { if err != nil { fmt.Println("Fatal error ", err.Error()) os.Exit(1) } } ~~~ 上面这个代码和http的服务器相比,不同在于:在此处我们采用了TCP协议,然后需要自己控制连接,当有客户端连接上来后,我们需要把这个连接交给rpc来处理。 如果你留心了,你会发现这它是一个阻塞型的单用户的程序,如果想要实现多并发,那么可以使用goroutine来实现,我们前面在socket小节的时候已经介绍过如何处理goroutine。 下面展现了TCP实现的RPC客户端: ~~~ package main import ( "fmt" "log" "net/rpc" ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { // if len(os.Args) != 2 { // fmt.Println("Usage: ", os.Args[0], "server:port") // os.Exit(1) // } // service := os.Args[1] // client, err := rpc.Dial("tcp", service) client, err := rpc.Dial("tcp", "127.0.0.1:1234") if err != nil { log.Fatal("dialing:", err) } // Synchronous call args := Args{17, 8} var reply int err = client.Call("Arith.Multiply", args, &reply) if err != nil { log.Fatal("arith error:", err) } fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) var quot Quotient err = client.Call("Arith.Divide", args, &quot) if err != nil { log.Fatal("arith error:", err) } fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) } ~~~ 这个客户端代码和http的客户端代码对比,唯一的区别一个是DialHTTP,一个是Dial(tcp),其他处理一模一样。