亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Golang > 正文

使用golang寫一個redis-cli的方法示例

2020-04-01 18:51:56
字體:
來源:轉載
供稿:網友

0. redis通信協議

redis的客戶端(redis-cli)和服務端(redis-server)的通信是建立在tcp連接之上, 兩者之間數據傳輸的編碼解碼方式就是所謂的redis通信協議。所以,只要我們的redis-cli實現了這個協議的解析和編碼,那么我們就可以完成所有的redis操作。

redis 協議設計的非常易讀,也易于實現,關于具體的redis通信協議請參考:通信協議(protocol)。后面我們在實現這個協議的過程中也會簡單重復介紹一下具體實現

1. 建立tcp連接

redis客戶端和服務端的通信是建立tcp連接之上,所以第一步自然是先建立連接

package mainimport ( "flag" "log" "net")var host stringvar port stringfunc init() { flag.StringVar(&host, "h", "localhost", "hsot") flag.StringVar(&port, "p", "6379", "port")}func main() { flag.Parse() tcpAddr := &net.TCPAddr{IP: net.ParseIP(host), Port: port} conn, err := net.DialTCP("tcp", nil, tcpAddr) if err != nil { log.Println(err)  }  defer conn.Close() // to be continue}

后續我們發送和接受數據便都可以使用conn.Read()和conn.Write()來進行了

2. 發送請求

發送請求第一個第一個字節是"*",中間是包含命令本身的參數個數,后面跟著"/r/n" 。之后使用"$"加參數字節數量并使用"/r/n"結尾,然后緊跟參數內容同時也使用"/r/n"結尾。如執行 SET key liangwt 客戶端發送的請求為"*3/r/n$3/r/nSET/r/n$3/r/nkey/r/n$7/r/nliangwt/r/n"

注意:

  1. 命令本身也作為協議的其中一個參數來發送
  2. /r/n 對應byte的十進制為 13 10

我們可以使用telnet測試下

wentao@bj:~/github.com/liangwt/redis-cli$ telnet 127.0.0.1 6379Trying 127.0.0.1...Connected to 127.0.0.1.Escape character is '^]'.*3$3SET$3key$7liangwt+OK

先暫時忽略服務端的回復,通過telnet我們可以看出請求協議非常簡單,所以對于請求協議的實現不做過多的介紹了,直接放代碼(如下使用基于字符串拼接,只是為了更直觀的演示,效率并不高,實際代碼中我們使用bytes.Buffer來實現)

func MultiBulkMarshal(args ...string) string { var s string s = "*" s += strconv.Itoa(len(args)) s += "/r/n" // 命令所有參數 for _, v := range args { s += "$" s += strconv.Itoa(len(v)) s += "/r/n" s += v s += "/r/n" } return s}

在實現了對命令和參數進行編碼之后,我們便可以通過conn.Write()把數據推送到服務端

func main() {  // .... req := MultiBulkMarshal("SET", "key", "liangwt") _, err = conn.Write([]byte(req)) if err != nil { log.Fatal(err) } // to be continue}

3. 獲取回復

我們首先實現通過tcp獲取服務端返回值,就是上面提到過的conn.Read()。

func main() {  // .... p := make([]byte, 1024) _, err = conn.Read(p) if err != nil { log.Fatal(err) } // to be continue}

4. 解析回復

我們拿到p之后我們就可以解析返回值了,redis服務端的回復是分為幾種情況的

  • 狀態回復
  • 錯誤回復
  • 整數回復
  • 批量回復
  • 多條批量回復

我們把前四種單獨看作一組,因為他們都是單一類型的返回值

我們把最后的多條批量回復看成單獨的一組,因為它是包含前面幾種類型的混合類型。而且你可以發現它和我們的請求協議是一樣的

也正是基于以上的考慮我們創建兩個函數來分別解析單一類型和混合類型,這樣在解析混合類型中的某一類型時就只需要調用單一類型解析的函數即可

在解析具體協議前我們先實現一個是讀取到/r/n為止的函數

func ReadLine(p []byte) ([]byte, error) { for i := 0; i < len(p); i++ { if p[i] == '/r' {  if p[i+1] != '/n' {  return []byte{}, errors.New("format error")  }  return p[0:i], nil } } return []byte{}, errors.New("format error")}

第一種狀態回復:

狀態回復是一段以 "+" 開始, "/r/n" 結尾的單行字符串。如 SET 命令成功的返回值:"+OK/r/n"

所以我們判斷第一個字符是否等于 '+' 如果相等,則讀取到/r/n

func SingleUnMarshal(p []byte) ([]byte, int, error) { var ( result []byte err  error length int ) switch p[0] { case '+': result, err = ReadLine(p[1:]) length = len(result) + 3 } return result, length, err}

注:我們在返回實際回復內容的同時也返回了整個回復的長度,方便后面解析多條批量回復時定位下一次的解析位置

第二種錯誤回復:

錯誤回復的第一個字節是 "-", "/r/n" 結尾的單行字符串。如執行 SET key缺少參數時返回值:"-ERR wrong number of arguments for 'set' command/r/n"

錯誤回復和狀態回復非常相似,解析方式也是一樣到。所以我們只需添加一個case即可

func SingleUnMarshal(p []byte) ([]byte, int, error) { var ( result []byte err  error length int ) switch p[0] { case '+', '-': result, err = ReadLine(p[1:]) length = len(result) + 3 } return result, length, err}

第三種整數回復:

整數回復的第一個字節是":",中間是字符串表示的整數,"/r/n" 結尾的單行字符串。如執行LLEN mylist命令時返回 ":10/r/n"

整數回復也和上面兩種是一樣的,只不過返回的是字符串表示的十進制整數

func SingleUnMarshal(p []byte) ([]byte, int, error) { var ( result []byte err  error length int ) switch p[0] { case '+', '-', ':': result, err = ReadLine(p[1:]) length = len(result) + 3 } return result, length, err}

第四種批量回復:

批量回復的第一個字節為 "$",接下來是字符串表示的整數,它表示實際回復的長度,之后跟著一個 "/r/n",再后面跟著的是實際回復數據,最末尾是另一個 "/r/n"。如GET key 命令的返回值:"$7/r/nliangwt/r/n"

所以批量回復解析的實現:

  • 讀取第一行得到實際回復的長度
  • 把字符串類型的長度轉換成對應十進制整數
  • 從第二行開始位置往下讀對應長度

但是對于某些不存在的key,批量回復會將特殊值 -1 用作回復的長度值, 此時我們不需要繼續往下讀取實際回復。例如GET NOT_EXIST_KEY 返回值:"$-1", 所以我們需要對此特殊情況判斷,讓函數返回一個空對象(nil)而不是空值("")

func SingleUnMarshal(p []byte) ([]byte, int, error) { // .... case '$': n, err := ReadLine(p[1:]) if err != nil {  return []byte{}, 0, err } l, err := strconv.Atoi(string(n)) if err != nil {  return []byte{}, 0, err } if l == -1 {  return nil, 0, nil } // +3 的原因 $ /r /n 三個字符 result = p[len(n)+3 : len(n)+3+l] length = len(n) + 5 + l } return result, length, err}

思考:

為什么redis要使用提前告知字節數,然后往下讀取指定長度的方式,而不是直接讀取第二行到/r/n為止?

答案很明顯:此方式可以讓redis讀取返回值時不受具體的返回內容影響,在按行讀取的情況下,無論使用任何分割符都有可能導致redis在解析具體內容時把內容中的分割符當作時結尾,導致解析錯誤。

思考一下這種情況:我們SET key "liang/r/nwt" ,那么當我們GET key時,服務端返回值為"$9/r/nliang/r/nwt/r/n" 完全規避了value中的/r/n影響

第五種多條批量回復:

多條批量回復是由多個回復組成的數組,它的第一個字節為"*", 后跟一個字符串表示的整數值, 這個值記錄了多條批量回復所包含的回復數量, 再后面是一個"/r/n"。如LRANGE mylist 0 -1的返回值:"*3/r/n$1/r/n3/r/n$1/r/n2/r/n$1/r/n1"。

所以多條批量回復解析的實現:

  • 解析第一行數據獲得字符串類型的回復數量
  • 把字符串類型的長度轉換成對應十進制整數
  • 按照單條回復依次逐個解析,一共解析成上面得到的數量

在這里我們用到了單條解析時返回的字節長度length,通過這個長度我們可以很方便的知道下次單條解析的開始位置為上一次位置+length

在解析多條批量回復時需要注意兩點:

第一,多條批量回復也可以是空白的(empty)。例如執行LRANGE NOT_EXIST_KEY 0 -1 服務端返回值"*0/r/n"。此時客戶端返回的應該空數組[][]byte

第二,多條批量回復也可以是無內容的(null multi bulk reply)。例如執行BLPOP key 1 服務端返回值"*-1/r/n"。此時客戶端返回的應該是nil

func MultiUnMarsh(p []byte) ([][]byte, error) { if p[0] != '*' { return [][]byte{}, errors.New("format error") } n, err := ReadLine(p[1:]) if err != nil { return [][]byte{}, err } l, err := strconv.Atoi(string(n)) if err != nil { return [][]byte{}, err } // 多條批量回復也可以是空白的(empty) if l == 0 { return [][]byte{}, nil } // 無內容的多條批量回復(null multi bulk reply)也是存在的, // 客戶端庫應該返回一個 null 對象, 而不是一個空數組。 if l == -1 { return nil, nil } result := make([][]byte, l) t := len(n) + 3 for i := 0; i < l; i++ { ret, length, err := SingleUnMarshal(p[t:]) if err != nil {  return [][]byte{}, errors.New("format error") } result[i] = ret t += length } return result, nil}

5. 命令行模式

一個可用的redis-cli自然是一個交互式的,用戶輸入指令然后輸出返回值。在go中我們可以使用以下代碼即可獲得一個類似的交互式命令行

func main() { // .... for { fmt.Printf("%s:%d>", host, port) bio := bufio.NewReader(os.Stdin) input, _, err := bio.ReadLine() if err != nil {  log.Fatal(err) } fmt.Printf("%s/n", input) }}

我們運行以上代碼就可以實現

localhost:6379>set key liangset key lianglocalhost:6379>get keyget keylocalhost:6379>

結合上我們的redis發送請求和解析請求即可完成整個redis-cli

func main() {  // .... for { fmt.Printf("%s:%d>", host, port) // 獲取輸入命令和參數 bio := bufio.NewReader(os.Stdin) input, err := bio.ReadString('/n') if err != nil {  log.Fatal(err) } fields := strings.Fields(input) // 編碼發送請求 req := MultiBulkMarshal(fields...) // 發送請求 _, err = conn.Write([]byte(req)) if err != nil {  log.Fatal(err) } // 讀取返回內容 p := make([]byte, 1024) _, err = conn.Read(p) if err != nil {  log.Fatal(err) } // 解析返回內容 if p[0] == '*' {  result, err := MultiUnMarsh(p) } else {  result, _, err := SingleUnMarshal(p) }  }  // ....}

6. 總結

到目前為止我們的cli程序已經全部完成,但其實還有很多不完美地方。但核心的redis協議解析已經完成,使用這個解析我們能完成任何的cli與服務器之間的交互

更詳細的redis-cli實現可以參考我的github:A Simaple redis cli - Rclient 

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
中文字幕在线观看日韩| 国产日韩综合一区二区性色av| 亚洲图片欧洲图片av| 国内精品久久久久久中文字幕| 国产欧美欧洲在线观看| 97色在线播放视频| 日韩黄色av网站| 欧美一级bbbbb性bbbb喷潮片| 久久久精品亚洲| 日本不卡免费高清视频| 久久久国产影院| 免费av一区二区| 欧美成aaa人片免费看| 欧美亚洲成人xxx| 国产福利成人在线| 中文字幕在线视频日韩| 日韩欧美国产中文字幕| 亚洲免费一在线| 日韩av在线不卡| 原创国产精品91| 性欧美长视频免费观看不卡| 91日韩在线视频| 色噜噜狠狠色综合网图区| 日韩精品久久久久久久玫瑰园| 成人观看高清在线观看免费| 国产欧美日韩免费看aⅴ视频| 欧美性xxxx18| 欧美性猛xxx| yw.139尤物在线精品视频| 久久久之久亚州精品露出| 色偷偷偷综合中文字幕;dd| 精品中文字幕视频| 欧美亚洲免费电影| 中文字幕一区电影| 成人信息集中地欧美| 色噜噜久久综合伊人一本| 一本色道久久综合狠狠躁篇怎么玩| 美女国内精品自产拍在线播放| 色一区av在线| 欧美亚洲第一页| 国产一区二区欧美日韩| 国产精品久久99久久| www.久久久久久.com| 亚洲欧洲在线免费| 51久久精品夜色国产麻豆| 国产欧美精品日韩| 8090理伦午夜在线电影| 欧美一级视频在线观看| 日本精品久久电影| 国产成人综合av| 亚洲欧美在线播放| 色综合视频一区中文字幕| 在线视频日本亚洲性| 久久国产天堂福利天堂| 亚洲精品久久久久久久久久久久久| 久久av在线看| 国产日韩欧美一二三区| 91系列在线观看| 国产日韩精品电影| 91日韩在线播放| 欧美视频13p| 精品爽片免费看久久| 麻豆国产va免费精品高清在线| 日韩电影免费在线观看| 国产精品一区二区久久国产| 欧美在线中文字幕| 91网站在线看| xvideos成人免费中文版| 国产视频精品va久久久久久| 成人免费视频网址| 亚洲a级在线播放观看| 久久的精品视频| 国产综合色香蕉精品| 亚洲精品456在线播放狼人| 日韩在线观看av| 一个人看的www久久| 成人综合网网址| 欧美理论电影网| 国产在线拍偷自揄拍精品| 久久97精品久久久久久久不卡| 一个人www欧美| 亚洲91精品在线观看| 91精品国产高清久久久久久久久| 欧美影院在线播放| 久久99青青精品免费观看| 成人黄色免费在线观看| 日韩av网址在线观看| 2024亚洲男人天堂| 久久久久www| 一区二区三区视频观看| 亚洲国产成人在线播放| 亚洲国产精彩中文乱码av在线播放| 亚洲日本中文字幕免费在线不卡| 久久久久久久久国产精品| 欧美日韩在线影院| 国产日韩精品综合网站| 亚洲国产精品久久久| 久久夜精品香蕉| 亚洲欧美日本精品| 国产欧美日韩精品在线观看| 国产午夜精品一区二区三区| 欧美激情性做爰免费视频| 国产精品无av码在线观看| 久久五月情影视| 色av吧综合网| 欧美激情一二区| 久久久视频免费观看| 91免费版网站入口| 精品久久久999| 久久久久久尹人网香蕉| 亚洲欧洲在线观看| 精品无人区太爽高潮在线播放| 久久久噜噜噜久久久| 日韩最新在线视频| 国产精品对白刺激| 国产99久久久欧美黑人| 国产精品久久久久秋霞鲁丝| 国产精品美女视频网站| 午夜精品三级视频福利| 亚洲最大福利视频| 日韩av在线免费看| 色老头一区二区三区| 国产精品福利无圣光在线一区| 中文字幕国产亚洲| 成人免费在线网址| 78色国产精品| 97超视频免费观看| 国产成人97精品免费看片| 97超级碰在线看视频免费在线看| 国产精品福利在线观看网址| 国产精品一区二区电影| 国产国语videosex另类| 日韩av一区在线观看| 亚洲毛茸茸少妇高潮呻吟| 亚洲欧美日韩天堂| 在线观看国产欧美| 在线日韩日本国产亚洲| 色一区av在线| 久久久久成人网| 欧美成人性色生活仑片| 一区二区三欧美| 国产欧美在线视频| 成人精品一区二区三区电影免费| 福利微拍一区二区| 欧美在线免费视频| 一区二区在线视频| 中文字幕久久久| 中文字幕不卡av| 欧美激情第三页| 亚洲经典中文字幕| 秋霞成人午夜鲁丝一区二区三区| 欧美xxxx14xxxxx性爽| 亚洲www永久成人夜色| 欧美日韩高清在线观看| 国产视频亚洲精品| 黑人狂躁日本妞一区二区三区| 亚洲аv电影天堂网| 国产精品久久久久999| 日韩美女视频中文字幕| 欧美成人精品影院| 91精品啪aⅴ在线观看国产| 久久国内精品一国内精品| 亚洲网站在线播放| 国产精品aaa|