C語言中文網 目錄
首頁 > Go語言教程 > Go語言并發 閱讀:2,546

Go語言RPC(模擬遠程過程調用)

服務器開發中會使用RPC(Remote Procedure Call,遠程過程調用)簡化進程間通信的過程。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)
    }

}

客戶端請求和接收封裝

下面的代碼封裝了向服務器請求數據,等待服務器返回數據,如果請求方超時,該函數還會處理超時邏輯。

模擬 RPC 的代碼:
// 模擬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 行,正常接收時,打印收到的數據。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo