为了账号安全,请及时绑定邮箱和手机立即绑定

Go语言9-socket和redis

标签:
Go


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包对于网络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包提供了大量访问底层的接口,但是大多数情况下,客户端仅仅只需要最基本的接口,例如Dial,LIsten,Accepte以及分配的conn连接和listener接口。 crypto/tls包使用相同的接口以及类似的Dial和Listen函数。

服务端

服务端的处理流程:

监听端口

接收客户端的连接

创建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("监听端口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)  // 这个应该是读取到的数据的长度

        if err != nil {

            fmt.Println("读取数据ERROR:", err)

            return

        }

        fmt.Println("READ:", string(buf))

    }

}

测试,暂时还没有客户端,可以先用windows的telnet工具来测试一下:

>telnet 127.0.0.1 8080

进入telnet后按任意键盘,server端会做出反应,但是效果不是很友好。

修改一下服务端的process函数如下:

func process(conn net.Conn) {

    defer conn.Close()

    for {

        buf := make([]byte, 10)  // 这次一次只收10个

        _, err := conn.Read(buf)  // 接收的长度就不看了

        if err != nil {

            fmt.Println("读取数据ERROR:", err)

            return

        }

        fmt.Printf("%v\n", buf)  // 查看buf的字符类型

    }

}

conn.Read用于读取收到的数据,把数据存到变量buf里。buf切片里的类型应该是字符类型默认就是0,从输出里看到其他后面都是0。

conn.Read 返回的第一个参数是接收的长度,那么可以对buf进行切片,只把收到的数据打印出来:

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("监听端口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("读取数据ERROR:", err)

            return

        }

        fmt.Printf(string(buf[0:n]))  // 接收了n个字符,就只打印前n个

    }

}

上面的代码,在telnet连接后,键盘输入任何内容,都会在server端打印出来。这里也能传中文,但是接收到的的是乱码,这个主要是telnet的问题(有编码的问题,一个中文字符占2个长度,如果是utf-8是长度3。而且会把中文的2段编码拆开发出去。不深究了。),后面用上客户端就好了。

客户端

客户端的处理流程:

建立与服务端的连接

进行数据收发

关闭连接

客户端代码:

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))  // 第一个参数是发送的字符数

        if err != nil {

            fmt.Println("发送数据ERROR:", err)

            return

        }

    }

}

发送http请求

这里需要一点基础,你得知道Web服务的本质。

这里只用HTTP/1.1来做个示例,默认:Connection:keep-alive,示例里设置为:Connection:close。接收的数据的内容会有区别,Connection:close收到的数据会比较简单(Connection:keep-alive应该是比较新的标准,默认是返回这样的格式)。

发送http请求的代码:

package main

import (

    "fmt"

    "io"

    "os"

    "net"

)

func main() {

    conn, err := net.Dial("tcp", "edu.51cto.com:80")

    if err != nil {

        fmt.Println("创建连接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)  // 发送请求

    if err != nil {

        fmt.Println("发送数据ERROR:", err)

        return

    }

    buf := make([]byte, 1024)

    fmt.Println("接收数据...")

    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("接收数据ERROR:", err)

                os.Exit(0)

            }

        }

    }

}

Redis

本篇主要是讲go如何使用Redis,就是一些简单的代码示例。关于Redis的基础知识可以看下面这篇的第一章节。

Redis安装和基础知识:http://blog.51cto.com/steed/2057706

Redis是一个开源的高性能的key-value的内存数据库,可以把它当成远程的数据结构。

支持的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)数据了。这里还有个坑,

正常启动Redis的命令:

$ redis-server

redis默认是以保护模式启动的,只能本机连。本机以外可能连不上,或者只能连不能改。就不要那么麻烦再去整配置文件了,这里以学习测试为主,推荐这么启动:

$ redis-server --protected-mode no

现在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)  // 这里存字符串或数值到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)

}

/* 执行结果

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()

    // 假设之前已经设置了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的时候,就可以加一个参数,其中就有超时时间。这里的例子是对已有的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("队列已经取完:", err)

                break

            } else {

                fmt.Println("Pop Redis ERROR:", err)

                return

            }

        }

        fmt.Println("Redis lpop:", resPop)

    }

}

使用连接池

使用场景:

对于一些大对象,或者初始化过程较长的可复用的对象,我们如果每次都new对象出来,那么意味着会耗费大量的时间。我们可以将这些对象缓存起来,当接口调用完毕后,不是销毁对象,当下次使用的时候,直接从对象池中拿出来即可。

使用连接池操作Redis的步骤:

首先创建连接池,可以在init函数里创建。

每次要连接的时候,就从池里获取一个连接。记得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

}

©著作权归作者所有:来自51CTO博客作者骑士救兵的原创作品,如需转载,请注明出处,否则将追究法律责任


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消