Go语言RPC(模拟远程过程调用)

引入    

     服务器开发中会使用RPC(Remote Procedure Call,远程过程调用)简化进程间通信的过程。RPC能有效地封装通信过程。RPC能有效地封装通信过程,让远程的数据收发通信过程看起来就像本地的函数调用一样。

     本例中,使用通道代理Socket 实现RPC的过程。客户端与服务器运行在同一个进程,服务器和客服端在两个goroutine中运行。先给出完整代码,然后在详细分析每一个部分。

package main

import (

        "errors"

        "fmt"

        "time"

)

//模拟RPC客户端的请求和接收消息封装

func RPCClient(ch chan string, req string) (string, error) {

        //向服务器发送请求

        ch <- req

        //等待服务器返回

        select {

        //等待服务器返回

        case ack := <-ch: //接收服务返回数据

                return ack, nil

        case <-time.After(time.Second): //超时

                return "", errors.New("Time Out")

        }

}

//模拟RPC服务器端接收客户端请求和回应

func RPCServer(ch chan string) {

        for {

                //接收客户端请求

                data := <-ch

                //打印接收到的数据

                fmt.Println("server received:", data)

                //反馈给客户端收到

                ch <- "roger"

        }

}

func main() {

        //创建一个无缓冲串通道

        ch := make(chan string)

        //并发执行服务器逻辑

        go RPCServer(ch)

        recv, err := RPCClient(ch, "hi")

        if err != nil {

                //发生错误打印

                fmt.Println(err)

        } else {

                //正常接收到数据

                fmt.Println("Client received", recv)

        }

}

运行输出:

server received: hi

Client received roger

客户端请求和接收数据封装

下面的代码封装了向服务器请求数据,等待服务器返回数据,如果请求方超时,该函数还会处理超时逻辑。

//模拟RPC客户端的请求和接收消息封装

func RPCClient(ch chan string, req string) (string, error) {

        //向服务器发送请求

        ch <- req

        //等待服务器返回

        select {

        //等待服务器返回

        case ack := <-ch: //接收服务返回数据

                return ack, nil

        case <-time.After(time.Second): //超时

                return "", errors.New("Time Out")

        }

}

代码说明如下:

第 5 行,模拟 socket 向服务器发送一个字符串信息。服务器接收后,结束阻塞执行下一行。

第 8 行,使用 select 开始做多路复用。注意,select 虽然在写法上和 switch 一样,都可以拥有 case 和 default。但是 select 关键字后面不接任何语句,而是将要复用的多个通道语句写在每一个 case 上,如第 9 行和第 11 行所示。

第 11 行,使用了 time 包提供的函数 After(),从字面意思看就是多少时间之后,其参数是 time 包的一个常量,time.Second 表示 1 秒。time.After 返回一个通道,这个通道在指定时间后,通过通道返回当前时间。

第 12 行,在超时时,返回超时错误。

RPCClient() 函数中,执行到 select 语句时,第 9 行和第 11 行的通道操作会同时开启。如果第 9 行的通道先返回,则执行第 10 行逻辑,表示正常接收到服务器数据;如果第 11 行的通道先返回,则执行第 12 行的逻辑,表示请求超时,返回错误。

服务器接收和反馈数据

服务器接收到客服端的任意数据后,先打印在通过通道返回给客户端一个固定字符串,表示服务器已经接收到请求。

//模拟RPC服务器端接收客户端请求和回应

func RPCServer(ch chan string) {

        for {

                //接收客户端请求

                data := <-ch

                //打印接收到的数据

                fmt.Println("server received:", data)

                //反馈给客户端收到

                ch <- "roger"

        }

}

代码说明如下:

第 3 行,构造出一个无限循环。服务器处理完客户端请求后,通过无限循环继续处理下一个客户端请求。

第 5 行,通过字符串通道接收一个客户端的请求。

第 8 行,将接收到的数据打印出来。

第 11 行,给客户端反馈一个字符串。

运行整个程序,客户端可以正确收到服务器返回的数据,客户端RPCClient()函数的代码下面代码加粗部分的分支执行。

        //等待服务器返回

        select {

        //等待服务器返回

        case ack := <-ch: //接收服务返回数据

                return ack, nil

        case <-time.After(time.Second): //超时

                return "", errors.New("Time Out")

        }

 

运行输出:

server received: hi

Client received roger

模拟超时

   上面的例子虽然有客户端超时处理,但永远不会触发,因为服务器的处理速度很快,也没有真正的网络延时或者“服务器宕机”的情况。因此,为了展示select中超时的处理,在服务器逻辑中增加一条语句,故意让服务器延时处理一段时间,造成客户端请求超时,代码如下:

//模拟RPC服务器端接收客户端请求和回应

func RPCServer(ch chan string) {

        for {

                //接收客户端请求

                data := <-ch

                //打印接收到的数据

                fmt.Println("server received:", data)

                //通过睡眠函数让程序执行阻塞2秒的任务

                time.Sleep(time.Second * 2)

                //反馈给客户端收到

                ch <- "roger"

        }

}

第11行中,time.Sleep()函数会让goroutine执行暂停2秒。使用这种方法模拟服务器会延时,造成客户端超时。客户端处理超时1秒时通道就会返回:

//等待服务器返回

        select {

        //等待服务器返回

        case ack := <-ch: //接收服务返回数据

                return ack, nil

        case <-time.After(time.Second): //超时

                return "", errors.New("Time Out")

        }

主程序

主程序中会创建一个无缓冲的字符串格式通道。将通道传给服务器的RPCServer()函数,这个函数并发执行。使用RPCClient()函数通过ch对服务器发出RPC请求,同时接收服务器反馈数据或者等待超时,参考下面代码:

func main() {

        //创建一个无缓冲串通道

        ch := make(chan string)

        //并发执行服务器逻辑

        go RPCServer(ch)

        recv, err := RPCClient(ch, "hi")

        if err != nil {

                //发生错误打印

                fmt.Println(err)

        } else {

                //正常接收到数据

                fmt.Println("Client received", recv)

        }

}

代码说明如下:

第 4 行,创建无缓冲的字符串通道,这个通道用于模拟网络和 socke t概念,既可以从通道接收数据,也可以发送。

第 7 行,并发执行服务器逻辑。服务器一般都是独立进程的,这里使用并发将服务器和客户端逻辑同时在一个进程内运行。

第 10 行,使用 RPCClient() 函数,发送“hi”给服务器,同步等待服务器返回。

第 13 行,如果通信过程发生错误,打印错误。

第 16 行,正常接收时,打印收到的数据。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值