在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ 8.4 RPC
7 文本處理
3 Web基礎
14 擴展Web框架
10.4 小結
2.2 Go基礎
2.8 總結
6.1 session和cookie
5.5 使用beedb庫進行ORM開發(fā)
8.3 REST
13.6 小結
5.4 使用PostgreSQL數據庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調試
7.7 小結
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結
13 如何設計一個Web框架
14.3 表單及驗證支持
12 部署與維護
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數據庫操作
6.5 小結
9.4 避免SQL注入
12.1 應用日志
4.2 驗證表單的輸入
10.1 設置默認地區(qū)
1.3 Go 命令
9.6 加密和解密數據
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務
12.3 應用部署
5.7 小結
12.5 小結
11 錯誤處理,調試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預防session劫持
12.4 備份和恢復
8.1 Socket編程
13.1 項目規(guī)劃
13.4 日志和配置設計
7.6 字符串處理
13.2 自定義路由器設計
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點
7.5 文件操作
7.4 模板處理
9.1 預防CSRF攻擊
13.3 controller設計
2.6 interface
14.4 用戶認證
2.3 流程和函數
附錄A 參考資料
11.1 錯誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網站錯誤處理
6 session和數據存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向對象
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結
13.5 實現博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結
4.3 預防跨站腳本
5.3 使用SQLite數據庫
14.7 小結
3.2 Go搭建一個Web服務器
2.7 并發(fā)
5 訪問數據庫
4 表單
3.5 小結
1.4 Go開發(fā)工具
11.4 小結
9 安全與加密
5.2 使用MySQL數據庫
4.6 小結
8.4 RPC

8.4 RPC

前面幾個小節(jié)我們介紹了如何基于Socket和HTTP來編寫網絡應用,通過學習我們了解了Socket和HTTP采用的是類似"信息交換"模式,即客戶端發(fā)送一條信息到服務端,然后(一般來說)服務器端都會返回一定的信息以表示響應??蛻舳撕头斩酥g約定了交互信息的格式,以便雙方都能夠解析交互所產生的信息。但是很多獨立的應用并沒有采用這種模式,而是采用類似常規(guī)的函數調用的方式來完成想要的功能。

RPC就是想實現函數調用模式的網絡化。客戶端就像調用本地函數一樣,然后客戶端把這些參數打包之后通過網絡傳遞到服務端,服務端解包到處理過程中執(zhí)行,然后執(zhí)行的結果反饋給客戶端。

RPC(Remote Procedure Call Protocol)——遠程過程調用協(xié)議,是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協(xié)議。它假定某些傳輸協(xié)議的存在,如TCP或UDP,以便為通信程序之間攜帶信息數據。通過它可以使函數調用模式網絡化。在OSI網絡通信模型中,RPC跨越了傳輸層和應用層。RPC使得開發(fā)包括網絡分布式多程序在內的應用程序更加容易。

RPC工作原理

http://wiki.jikexueyuan.com/project/go-web-programming/images/8.4.rpc.png" alt="" />

圖8.8 RPC工作流程圖

運行時,一次客戶機對服務器的RPC調用,其內部操作大致有如下十步:

  • 1.調用客戶端句柄;執(zhí)行傳送參數
  • 2.調用本地系統(tǒng)內核發(fā)送網絡消息
  • 3.消息傳送到遠程主機
  • 4.服務器句柄得到消息并取得參數
  • 5.執(zhí)行遠程過程
  • 6.執(zhí)行的過程將結果返回服務器句柄
  • 7.服務器句柄返回結果,調用遠程系統(tǒng)內核
  • 8.消息傳回本地主機
  • 9.客戶句柄由內核接收消息
  • 10.客戶接收句柄返回的數據

Go RPC

Go標準包中已經提供了對RPC的支持,而且支持三個級別的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是獨一無二的RPC,它和傳統(tǒng)的RPC系統(tǒng)不同,它只支持Go開發(fā)的服務器與客戶端之間的交互,因為在內部,它們采用了Gob來編碼。

Go RPC的函數只有符合下面的條件才能被遠程訪問,不然會被忽略,詳細的要求如下:

  • 函數必須是導出的(首字母大寫)
  • 必須有兩個導出類型的參數,
  • 第一個參數是接收的參數,第二個參數是返回給客戶端的參數,第二個參數必須是指針類型的
  • 函數還要有一個返回值error

舉個例子,正確的RPC函數格式如下:

func (t *T) MethodName(argType T1, replyType *T2) error

T、T1和T2類型必須能被encoding/gob包編解碼。

任何的RPC都需要通過網絡來傳遞數據,Go RPC可以利用HTTP和TCP來傳遞數據,利用HTTP的好處是可以直接復用net/http里面的一些函數。詳細的例子請看下面的實現

HTTP RPC

http的服務端代碼實現如下:

package main

import (
    "errors"
    "fmt"
    "net/http"
    "net/rpc"
)

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)
    rpc.Register(arith)
    rpc.HandleHTTP()

    err := http.ListenAndServe(":1234", nil)
    if err != nil {
        fmt.Println(err.Error())
    }
}

通過上面的例子可以看到,我們注冊了一個Arith的RPC服務,然后通過rpc.HandleHTTP函數把該服務注冊到了HTTP協(xié)議上,然后我們就可以利用http的方式來傳遞數據了。

請看下面的客戶端代碼:

package main

import (
    "fmt"
    "log"
    "net/rpc"
    "os"
)

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")
        os.Exit(1)
    }
    serverAddress := os.Args[1]

    client, err := rpc.DialHTTP("tcp", serverAddress+":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_c localhost
Arith: 17*8=136
Arith: 17/8=2 remainder 1

通過上面的調用可以看到參數和返回值是我們定義的struct類型,在服務端我們把它們當做調用函數的參數的類型,在客戶端作為client.Call的第2,3兩個參數的類型??蛻舳俗钪匾木褪沁@個Call函數,它有3個參數,第1個要調用的函數的名字,第2個是要傳遞的參數,第3個要返回的參數(注意是指針類型),通過上面的代碼例子我們可以發(fā)現,使用Go的RPC實現相當的簡單,方便。

TCP RPC

上面我們實現了基于HTTP協(xié)議的RPC,接下來我們要實現基于TCP協(xié)議的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)
    rpc.Register(arith)

    tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
    checkError(err)

    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)

    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        rpc.ServeConn(conn)
    }

}

func checkError(err error) {
    if err != nil {
        fmt.Println("Fatal error ", err.Error())
        os.Exit(1)
    }
}

上面這個代碼和http的服務器相比,不同在于:在此處我們采用了TCP協(xié)議,然后需要自己控制連接,當有客戶端連接上來后,我們需要把這個連接交給rpc來處理。

如果你留心了,你會發(fā)現這它是一個阻塞型的單用戶的程序,如果想要實現多并發(fā),那么可以使用goroutine來實現,我們前面在socket小節(jié)的時候已經介紹過如何處理goroutine。 下面展現了TCP實現的RPC客戶端:

package main

import (
    "fmt"
    "log"
    "net/rpc"
    "os"
)

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)
    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的客戶端代碼對比,唯一的區(qū)別一個是DialHTTP,一個是Dial(tcp),其他處理一模一樣。

JSON RPC

JSON RPC是數據編碼采用了JSON,而不是gob編碼,其他和上面介紹的RPC概念一模一樣,下面我們來演示一下,如何使用Go提供的json-rpc標準包,請看服務端代碼的實現:

package main

import (
    "errors"
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
    "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)
    rpc.Register(arith)

    tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
    checkError(err)

    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)

    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        jsonrpc.ServeConn(conn)
    }

}

func checkError(err error) {
    if err != nil {
        fmt.Println("Fatal error ", err.Error())
        os.Exit(1)
    }
}

通過示例我們可以看出 json-rpc是基于TCP協(xié)議實現的,目前它還不支持HTTP方式。

請看客戶端的實現代碼:

package main

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

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")
        log.Fatal(1)
    }
    service := os.Args[1]

    client, err := jsonrpc.Dial("tcp", service)
    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)

}

總結

Go已經提供了對RPC的良好支持,通過上面HTTP、TCP、JSON RPC的實現,我們就可以很方便的開發(fā)很多分布式的Web應用,我想作為讀者的你已經領會到這一點。但遺憾的是目前Go尚未提供對SOAP RPC的支持,欣慰的是現在已經有第三方的開源實現了。

上一篇:12.3 應用部署下一篇:7.7 小結