溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言9-socket和redis

發(fā)布時間:2020-05-03 22:16:01 來源:網(wǎng)絡 閱讀:2092 作者:騎士救兵 欄目:編程語言

socket 編程

在Go里為我們提供了net包。
下面這篇貌似是官方文檔的翻譯:
https://blog.csdn.net/chenbaoke/article/details/42782571
上面的轉載,上面的頁面在IE下瀏覽貌似有點問題:
https://studygolang.com/articles/3600

Package net provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets.

net包對于網(wǎng)絡I/O提供了便攜式接口,包括TCP/IP,UDP,域名解析以及Unix Socket。

Although the package provides access to low-level networking primitives, most clients will need only the basic interface provided by the Dial, Listen, and Accept functions and the associated Conn and Listener interfaces. The crypto/tls package uses the same interfaces and similar Dial and Listen functions.

盡管net包提供了大量訪問底層的接口,但是大多數(shù)情況下,客戶端僅僅只需要最基本的接口,例如Dial,LIsten,Accepte以及分配的conn連接和listener接口。 crypto/tls包使用相同的接口以及類似的Dial和Listen函數(shù)。

服務端

服務端的處理流程:

  1. 監(jiān)聽端口
  2. 接收客戶端的連接
  3. 創(chuàng)建goroutine,處理連接

服務端代碼:

package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println("準備開啟Server...")
    listen, err := net.Listen("tcp", "0.0.0.0:8080")
    if err != nil {
        fmt.Println("監(jiān)聽端口ERROR:", err)
        return
    }
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Println("接收連接ERROR:", err)
        }
        go process(conn)
    }
}

func process(conn net.Conn) {
    defer conn.Close()
    for {
        buf := make([]byte, 512)
        n, err := conn.Read(buf)
        fmt.Println(n)  // 這個應該是讀取到的數(shù)據(jù)的長度
        if err != nil {
            fmt.Println("讀取數(shù)據(jù)ERROR:", err)
            return
        }
        fmt.Println("READ:", string(buf))
    }
}

測試,暫時還沒有客戶端,可以先用windows的telnet工具來測試一下:

>telnet 127.0.0.1 8080

進入telnet后按任意鍵盤,server端會做出反應,但是效果不是很友好。
修改一下服務端的process函數(shù)如下:

func process(conn net.Conn) {
    defer conn.Close()
    for {
        buf := make([]byte, 10)  // 這次一次只收10個
        _, err := conn.Read(buf)  // 接收的長度就不看了
        if err != nil {
            fmt.Println("讀取數(shù)據(jù)ERROR:", err)
            return
        }
        fmt.Printf("%v\n", buf)  // 查看buf的字符類型
    }
}

conn.Read用于讀取收到的數(shù)據(jù),把數(shù)據(jù)存到變量buf里。buf切片里的類型應該是字符類型默認就是0,從輸出里看到其他后面都是0。
conn.Read 返回的第一個參數(shù)是接收的長度,那么可以對buf進行切片,只把收到的數(shù)據(jù)打印出來:

package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println("準備開啟Server...")
    listen, err := net.Listen("tcp", "0.0.0.0:8080")
    if err != nil {
        fmt.Println("監(jiān)聽端口ERROR:", err)
        return
    }
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Println("接收連接ERROR:", err)
        }
        go process(conn)
    }
}

func process(conn net.Conn) {
    defer conn.Close()
    for {
        buf := make([]byte, 512)
        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println("讀取數(shù)據(jù)ERROR:", err)
            return
        }
        fmt.Printf(string(buf[0:n]))  // 接收了n個字符,就只打印前n個
    }
}

上面的代碼,在telnet連接后,鍵盤輸入任何內容,都會在server端打印出來。這里也能傳中文,但是接收到的的是亂碼,這個主要是telnet的問題(有編碼的問題,一個中文字符占2個長度,如果是utf-8是長度3。而且會把中文的2段編碼拆開發(fā)出去。不深究了。),后面用上客戶端就好了。

客戶端

客戶端的處理流程:

  1. 建立與服務端的連接
  2. 進行數(shù)據(jù)收發(fā)
  3. 關閉連接

客戶端代碼:

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:8080")
    if err != nil {
        fmt.Println("建立連接ERROR:", err)
        return
    }
    fmt.Println("建立連接成功")
    defer conn.Close()  // 這里一定記得要關閉
    inputReader := bufio.NewReader(os.Stdin)
    for {
        input, err := inputReader.ReadString('\n')
        input = strings.TrimSpace(input)
        if err != nil {
            fmt.Println("終端輸入ERROR:", err)
        }

        if input == "Q" {
            fmt.Println("退出...")
            return
        }
        _, err = conn.Write([]byte(input))  // 第一個參數(shù)是發(fā)送的字符數(shù)
        if err != nil {
            fmt.Println("發(fā)送數(shù)據(jù)ERROR:", err)
            return
        }
    }
}

發(fā)送http請求

這里需要一點基礎,你得知道Web服務的本質。
這里只用HTTP/1.1來做個示例,默認:Connection:keep-alive,示例里設置為:Connection:close。接收的數(shù)據(jù)的內容會有區(qū)別,Connection:close收到的數(shù)據(jù)會比較簡單(Connection:keep-alive應該是比較新的標準,默認是返回這樣的格式)。
發(fā)送http請求的代碼:

package main

import (
    "fmt"
    "io"
    "os"
    "net"
)

func main() {
    conn, err := net.Dial("tcp", "edu.51cto.com:80")
    if err != nil {
        fmt.Println("創(chuàng)建連接ERROR:", err)
        return
    }
    defer conn.Close()
    // 設置請求頭
    msg := "GET / HTTP/1.1\r\n"
    msg += "Host:edu.51cto.com\r\n"
    msg += "Connection:close\r\n"
    msg += "\r\n"  // 請求頭結束
    _, err = io.WriteString(conn, msg)  // 發(fā)送請求
    if err != nil {
        fmt.Println("發(fā)送數(shù)據(jù)ERROR:", err)
        return
    }
    buf := make([]byte, 1024)
    fmt.Println("接收數(shù)據(jù)...")
    for {
        n, err := conn.Read(buf)
        fmt.Println(string(buf[0:n]))
        if err != nil {
            if err == io.EOF{
                fmt.Println("接收完畢...")
                break
            } else {
                fmt.Println("接收數(shù)據(jù)ERROR:", err)
                os.Exit(0)
            }
        }
    }
}

Redis

本篇主要是講go如何使用Redis,就是一些簡單的代碼示例。關于Redis的基礎知識可以看下面這篇的第一章節(jié)。
Redis安裝和基礎知識:https://blog.51cto.com/steed/2057706
Redis是一個開源的高性能的key-value的內存數(shù)據(jù)庫,可以把它當成遠程的數(shù)據(jù)結構。
支持的value類型很多,比如:string、list(鏈表)、set(集合)、hash表等
Redis的性能非常高,單機能夠達到15w qps,通常用來做緩存。

使用第三方開源的redis庫:https://github.com/gomodule/redigo
安裝第三方庫:

go get github.com/gomodule/redigo/redis

連接 Redis

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

func main() {
    conn, err := redis.Dial("tcp", "192.168.3.108:6379")
    if err != nil {
        fmt.Println("連接Redis ERROR:", err)
        return
    }
    fmt.Println("連接Redis 成功:", conn)
    defer conn.Close()
}

Set 和 Get

上面的連接沒問題的,接下來就可以通過Redis存(Set)取(Get)數(shù)據(jù)了。這里還有個坑,
正常啟動Redis的命令:

$ redis-server

redis默認是以保護模式啟動的,只能本機連。本機以外可能連不上,或者只能連不能改。就不要那么麻煩再去整配置文件了,這里以學習測試為主,推薦這么啟動:

$ redis-server --protected-mode no

現(xiàn)在Redis可以用了,使用Set和Get進行存取的代碼:

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

func main() {
    conn, err := redis.Dial("tcp", "192.168.3.108:6379")
    if err != nil {
        fmt.Println("連接Redis ERROR:", err)
        return
    }
    fmt.Println("連接Redis 成功:", conn)
    defer conn.Close()

    resSet, err := conn.Do("Set", "age", 18)  // 這里存字符串或數(shù)值到Redis里都是一樣的
    if err != nil {
        fmt.Println("Set Redis ERROR:", err)
        return
    }
    fmt.Println("Redis Set:", resSet)

    resGet, err := redis.Int(conn.Do("Get", "age"))
    if err != nil {
        fmt.Println("Get Redis ERROR:", err)
        return
    }
    fmt.Println("Redis Get:", resGet)
}

/* 執(zhí)行結果
PS H:\Go\src\go_dev\day9\redis\connect> go run main.go
連接Redis 成功: &{{0 0} 0 <nil> 0xc042004030 0 0xc04203a120 0 0xc042036180 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0]}
Redis Set: OK
Redis Get: 18
PS H:\Go\src\go_dev\day9\redis\connect>
*/

批量Set

批量Set是redis原生就支持的功能,這里就是調用新的方法 MSet 和 MGet ,進行批量的操作:

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

func main() {
    conn, err := redis.Dial("tcp", "192.168.3.108:6379")
    if err != nil {
        fmt.Println("連接Redis ERROR:", err)
        return
    }
    fmt.Println("連接Redis 成功:", conn)
    defer conn.Close()

    resSet, err := conn.Do("MSet", "k1", "v1", "k2", "v2", "k3", "v3")
    if err != nil {
        fmt.Println("Set Redis ERROR:", err)
        return
    }
    fmt.Println("Redis MSet:", resSet)

    resGet, err := redis.Strings(conn.Do("MGet", "k1", "k2", "k3"))
    if err != nil {
        fmt.Println("Get Redis ERROR:", err)
        return
    }
    fmt.Println("Redis MGet:", resGet)
    for _, v := range resGet {
        fmt.Println(v)
    }
}

Hash 操作

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

func main() {
    conn, err := redis.Dial("tcp", "192.168.3.108:6379")
    if err != nil {
        fmt.Println("連接Redis ERROR:", err)
        return
    }
    fmt.Println("連接Redis 成功:", conn)
    defer conn.Close()

    resSet, err := conn.Do("HSet", "student", "age", 18)
    if err != nil {
        fmt.Println("Set Redis ERROR:", err)
        return
    }
    fmt.Println("Redis Set:", resSet)

    resGet, err := redis.Int(conn.Do("HGet", "student", "age"))
    if err != nil {
        fmt.Println("Get Redis ERROR:", err)
        return
    }
    fmt.Println("Redis Get:", resGet)
}

設置超時時間

超時時間是對key進行設置的。默認是沒有超時時間的,永不過期。這里的示例是在設置好key-value之后再對key設置超時時間:

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

func main() {
    conn, err := redis.Dial("tcp", "192.168.3.108:6379")
    if err != nil {
        fmt.Println("連接Redis ERROR:", err)
        return
    }
    fmt.Println("連接Redis 成功:", conn)
    defer conn.Close()

    // 假設之前已經(jīng)設置了name這個key值,設置后name會在10秒后過期
    resSet, err := conn.Do("expire", "name", 10)
    if err != nil {
        fmt.Println("Set Redis ERROR:", err)
        return
    }
    fmt.Println("Redis Set:", resSet)
    if int(resSet.(int64)) == 1 {
        fmt.Println("設置超時時間成功")
    }
}

在Set的時候,就可以加一個參數(shù),其中就有超時時間。這里的例子是對已有的key重新設置一個超時時間。如果key存在,則返回1,如果key不存在,Redis會返回0。

隊列操作

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

func main() {
    conn, err := redis.Dial("tcp", "192.168.3.108:6379")
    if err != nil {
        fmt.Println("連接Redis ERROR:", err)
        return
    }
    fmt.Println("連接Redis 成功:", conn)
    defer conn.Close()

    resPush, err := conn.Do("lpush", "names", "Clark", "Lois", "Kara")
    if err != nil {
        fmt.Println("Push Redis ERROR:", err)
        return
    }
    fmt.Println("Redis MSet:", resPush)

    for {
        resPop, err := redis.String(conn.Do("lpop", "names"))
        if err != nil {
            if err == redis.ErrNil {
                fmt.Println("隊列已經(jīng)取完:", err)
                break
            } else {
                fmt.Println("Pop Redis ERROR:", err)
                return
            }
        }
        fmt.Println("Redis lpop:", resPop)
    }
}

使用連接池

使用場景:
對于一些大對象,或者初始化過程較長的可復用的對象,我們如果每次都new對象出來,那么意味著會耗費大量的時間。我們可以將這些對象緩存起來,當接口調用完畢后,不是銷毀對象,當下次使用的時候,直接從對象池中拿出來即可。
使用連接池操作Redis的步驟:

  • 首先創(chuàng)建連接池,可以在init函數(shù)里創(chuàng)建。
  • 每次要連接的時候,就從池里獲取一個連接。記得defer關閉連接。
  • 獲取到連接后的操作方法就一樣了

代碼示例:

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

var pool redis.Pool

func init() {
    pool = redis.Pool {
        MaxIdle: 8,
        MaxActive: 0,
        IdleTimeout: 300,
        Dial: func() (redis.Conn, error) {
            return redis.Dial("tcp", "192.168.3.108:6379")
        },
    }
}

func main() {
    conn := pool.Get()  // 從池里獲取一個連接使用
    defer conn.Close()  // 還是記得要關閉連接
    resCon, err := conn.Do("Set", "school", "SHHS")
    if err != nil {
        fmt.Println("Redis Set ERROR:", err)
        return
    }
    fmt.Println(resCon)  // 返回值是OK沒什么用

    resGet, err := redis.String(conn.Do("Get", "school"))
    if err != nil {
        fmt.Println("Redis Get ERROR:", err)
        return
    }
    fmt.Println(resGet)
    pool.Close()  // 關閉pool
}
向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI