csdn0811 2023-12-01 21:59 采纳率: 60.7%
浏览 3
已结题

go-redis 提前关闭连接的问题

database.GetUserById函数先执行,defer database.Rdb.Close()后执行,为什么我运行后,就提前报错:redis: client is closed,而且我写在任何位置都不行,而当我删除defer database.Rdb.Close()命令后,就不会有问题,这又是为什么呢?

// loginRequest:登录请求函数
func (p *Process) LoginRequest(mes common.Message) {
    defer database.Rdb.Close() //请求登录函数执行完毕或发生错误时,确保关闭 Redis 客户端连接,释放资源并避免潜在的内存泄漏
    var loginMes common.LoginMes
    err := json.Unmarshal([]byte(mes.MessData), &loginMes)
    if err != nil {
        fmt.Println("userProcess_server.go ->>  LoginRequest ->> json.Unmarshal(...) err:", err)
        return
    }
    //提前定义总消息,和返回结果消息,准备发送给客户端(为了告知客户端用户id是否在服务器端存在)
    var resultMessage common.Message
    var returnResMes common.ReturnResMes

    // //如果用户id=tom,密码123,就认为合法,否则不合法
    // if loginMes.UserId == "tom" && loginMes.UserPwd == "123" {
    //     returnResMes.Code = 1 //1代表真,说明合法
    // } else {
    //     returnResMes.Code = 0 //0代表假,说明不合法,表示用户不存在
    // }
    status, errResult, err := database.GetUserById(loginMes.UserId, loginMes.UserPwd) //传入用户输入进来的用户名和密码
    if err != nil {
        fmt.Println("userProcess_server.go ->> database.GetUserById err ->> ", err)
        return
    }

    returnResMes.Code = status
    returnResMes.ErrResult = errResult
    //将returnResMes 序列化
    ret_ByteSli, err := json.Marshal(returnResMes)
    if err != nil {
        fmt.Println("userProcess_server.go ->>  LoginRequest ->> json.Marshal(returnResMes) err err:", err)
        return
    }
    //将返回类型和返回结果的两个消息赋给resultMessage(总消息)
    resultMessage.MessData = string(ret_ByteSli)
    resultMessage.MessType = common.ReturnResMesType

    //对resultMessage进行序列化
    res_ByteSli, err := json.Marshal(resultMessage)
    if err != nil {
        fmt.Println("userProcess_server.go ->>  LoginRequest ->> json.Marshal(resultMessage) err:", err)
        return
    }

    //发送总消息(发送后,客户端那边还需要反序列化,客户端收到消息1,表明用户存在,收到消息0,表面用户不存在)
    //因为使用分层模式(mvc),先创建一个Transfer实例,然后发送结果消息
    tf := common.NewTransfer(p.Conn)
    err = tf.WritePKG(res_ByteSli)
    if err != nil {
        fmt.Println("userProcess_server.go ->>  LoginRequest ->> tf.WritePKG(res_ByteSli) err:", err)
        return
    }
    if returnResMes.Code == 1 { //如果状态码被赋值1,说明用户存在,则可以进行一些逻辑操作
        fmt.Printf("%v 连接到服务器\n", p.Conn.RemoteAddr())
        ReceiveMsg(tf.Conn, tf.Buff) //这里的conn连接传入 LoginRequest方法的连接也行,传入Transfer的conn连接也行; 这里传入Transfer 的Buff切片是为了简洁,这样就不需要在ReceiveMsg中重新定义byte切片了,统一使用1024字节长度的字节切片(该切片使用工厂模式实例化好了,make了1024个字节)
        fmt.Println("等待新用户连接...")
    }

}

func ReceiveMsg(conn net.Conn, byteSlice []byte) { //持续接收消息函数(和服务端保持通讯)
    for {
        n, err := conn.Read(byteSlice) //从连接中读取数据到字节切片中
        if err != nil {
            if err == io.EOF { //表示文件结束的错误(已经读取到了文件末尾)
                fmt.Printf("客户端 %v 已中断连接\n", conn.RemoteAddr())
                break //这里对方连接中断会触发 io.EOF,表示通讯结束
            }
            fmt.Println("userProcess_server.go ->> ReceiveMsg(...) ->> conn.Read(byteSlice) err:", err)
            return
        }
        fmt.Printf("%v -> %v", conn.RemoteAddr(), string(byteSlice[:n])) //这里Printf 不使用换行符 "\n", 是因为客户端的 "ReadString('\n')" 命令发送数据时,已经带了换行符 "\n", 所以再加一次换行符是多余的
        fmt.Printf("本次接收 %v 字节\n", n)
    }
}

func GetUserById(userName, userPwd string) (status int, errResult string, err error) {
    myHash, err := Rdb.HGet(ctx, "users", userName).Result()
    if err != nil {
        if err == redis.Nil { //如果返回的错误为Nil,说明查询的键或字段在库中不存在(用户名不存在)
            return 0, ErrUserNotExist, nil
        }
        fmt.Println("userDao.go ->>  GetUserById(...) ->> Rdb.HGet(...) err")
        return //如果是其他错误,打印错误,直接返回结果和错误
    }
    
    var account common.LoginMes
    err = json.Unmarshal([]byte(myHash), &account)
    if err != nil {
        fmt.Println("userDao.go ->>  GetUserById(...) ->> json.Unmarshal(...) err")
        return
    }

    //这里完成登录密码验证(如果用户id和pwd都正确则返回user实例,否则返回错误)
    if account.UserPwd == userPwd {
        return 1, "", nil //如果用户输入的密码和数据库的密码一致,则把正确结果返回(可省略参数)
    } else {
        return 0, ErrUserPwd, nil //否则返回用户密码错误
    }
}
  • 写回答

1条回答 默认 最新

  • CSDN-Ada助手 CSDN-AI 官方账号 2023-12-02 08:19
    关注

    【以下回答由 GPT 生成】

    由于你在LoginRequest函数中使用了defer database.Rdb.Close()来关闭连接,所以无论在函数的哪个位置执行,都会在函数结束时关闭连接。但是,由于你的函数可能在其他地方被调用,可能会导致连接被过早关闭。

    为了解决这个问题,你可以将连接的关闭放在函数的最后执行,以确保在函数逻辑执行完毕后再关闭连接。你可以使用一个标志变量来判断连接是否已关闭,只有当连接未关闭时再执行关闭操作。

    以下是修改后的代码示例:

    // loginRequest:登录请求函数
    func (p *Process) LoginRequest(mes common.Message) {
        // 其他函数逻辑...
    
        if !database.Closed {
            defer database.Rdb.Close() // 请求登录函数执行完毕或发生错误时,确保关闭 Redis 客户端连接,释放资源并避免潜在的内存泄漏
            database.Closed = true
        }
    }
    

    在上述代码中,我们使用了一个名为Closed的布尔变量来标记连接是否已关闭。在每次函数执行时,我们首先检查这个变量。如果连接未关闭,则执行defer database.Rdb.Close()来关闭连接,并将Closed设置为true

    这样做可以确保连接只会在需要的时候才会关闭,避免了连接被提前关闭的问题。

    希望这个解决方案对你有帮助。如果还有其他问题,请随时提问。


    如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 系统已结题 6月1日
  • 已采纳回答 5月24日
  • 创建了问题 12月1日