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 行,正常接收時,打印收到的數據。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章