csdn0811 2023-11-27 22:26 采纳率: 60.7%
浏览 9
已结题

go语言的Conn.Read相关问题

客户端和服务端运行后,客户端输入用户名和密码后,程序就不动卡住了, ReadPKG()函数加入for循环就会卡住,不加就不卡,为什么呢?



func (t *Transfer) ReadPKG() (mes Message, err error) {
    for {
        //t.Conn.Read(...) 会起到阻塞作用,有消息时,for循环就不停的循环读取消息,没有消失时,就阻塞在这里
        _, err = t.Conn.Read(t.Buff[:4]) //先读取长度(因为 binary.BigEndian.Uint32 方法最大占4个字节的内存,而Buff字段变量分配了1024个字节,所以读取长度时截取4个字节长度的内存)
        fmt.Println("无限循环????????????????????")
        if err != nil {
            if err == io.EOF { //这里只需添加该错误语句,别处不需要,因为这里是开头,for语句再次循环到这里没有文件末尾没有数据时,直接return该错误,下面的 t.Conn.Read 执行不到了
                fmt.Println("ReadPKG() ->> err == io.EOF 会走这里吗???????????????????????")
                return
            }
            fmt.Println("ReadPKG() ->> t.Conn.Read(t.Buff[:4]) err", err)
            return //这里不带返回参数,默认就是返回 mes 和 err
        }

        pkgLen := binary.BigEndian.Uint32(t.Buff[:4]) //将这4个字节切片转换成一个无符号整数pkgLen(这里使用了大端序(BigEndian)进行编解码)
        var n int
        n, err = t.Conn.Read(t.Buff) //后读取真正的消息; 在for 循环内部使用 := 运算符会创建一个新的变量,而不是对已有的变量赋值,所以会报错:"返回时结果参数err不在范围内",因此可将 err 的定义改为赋值操作,而不使用 := 运算符 ,或者定义并返回新的错误变量,如"return otherErr"
        if err != nil {
            fmt.Println("ReadPKG() ->> t.Conn.Read(t.Buff) err")
            return
        } else if pkgLen != uint32(n) { //如果先发来的长度和后发来的消息长度不一样,则可能丢包
            return mes, errors.New("客户端的数据大小与服务端的数据大小不一致,疑似丢包")
        }
        //把总消息反序列化为具体消息结构体类型(序列化后的)
        err = json.Unmarshal(t.Buff[:n], &mes)
        if err != nil {
            fmt.Println("ReadPKG() ->> json.Unmarshal(t.Buff[:n], &mes) err")
            return
        }else{
            break
        }
    }
    return mes, nil //返回值可省略; 当 err == io.EOF 的时候,说明读取数据完毕,没有数据了,这个时候就break跳出无限循环,把结果返回
}




// loginRequest:登录请求函数
func (p *Process) LoginRequest(mes common.Message) {

    //先从mes中取出 MessData,并反序列化为LoginMes
    var loginMes common.LoginMes
    err := json.Unmarshal([]byte(mes.MessData), &loginMes)
    if err != nil {
        fmt.Println("json.Unmarshal([]byte(mes.MessData), &loginMes) 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代表假,说明不合法,表示用户不存在
    }

    //将returnResMes 序列化
    ret_ByteSli, err := json.Marshal(returnResMes)
    if err != nil {
        fmt.Println("json.Marshal(returnResMes) 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("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("服务端 common.WritePKG(conn, res_ByteSli) err:", err)
        return
    }
    if returnResMes.Code == 1 { //如果状态码被赋值1,说明用户存在,则可以进行一些逻辑操作
        fmt.Printf("%v 连接到服务器\n", tf.Conn.RemoteAddr())
        ReceiveMsg(tf.Conn)
    }
}
  • 写回答

1条回答 默认 最新

  • CSDN-Ada助手 CSDN-AI 官方账号 2023-11-28 08:36
    关注

    【以下回答由 GPT 生成】

    问题的关键在于ReadPKG()函数中的for循环导致程序卡住的原因。

    首先,我们需要了解Read()函数的工作原理。Read()函数是一个阻塞式函数,当没有数据可读时,它会一直等待直到有数据可读。因此,如果在一个循环中多次调用Read()函数,当没有数据可读时,程序会陷入无限循环,无法继续执行后面的代码。

    在代码中,ReadPKG()函数使用for循环来读取数据并处理报文。但是,由于Read()函数会阻塞程序执行,所以在没有数据可读的情况下,程序会一直等待导致卡住。

    解决这个问题的方法是使用非阻塞的方式读取数据。可以使用SetReadDeadline()方法来设置读取数据的超时时间,如果超过指定时间仍然没有数据可读,就会返回一个错误。通过捕捉这个错误,我们可以终止程序的阻塞状态。

    下面是一个修改后的代码示例:

    func (t *Transfer) ReadPKG() (mes Message, err error) {
        t.Conn.SetReadDeadline(time.Now().Add(time.Second)) // 设置读取数据的超时时间为1秒
    
        // 先读取长度
        _, err = t.Conn.Read(t.Buff[:4])
        if err != nil {
            if err == io.EOF {
                fmt.Println("ReadPKG() ->> err == io.EOF")
                return
            }
            fmt.Println("ReadPKG() ->> t.Conn.Read(t.Buff[:4]) err", err)
            return
        }
    
        pkgLen := binary.BigEndian.Uint32(t.Buff[:4])
    
        // 后读取真正的消息
        t.Conn.SetReadDeadline(time.Now().Add(time.Second)) // 重新设置读取数据的超时时间为1秒
        _, err = t.Conn.Read(t.Buff[:pkgLen])
        if err != nil {
            fmt.Println("ReadPKG() ->> t.Conn.Read(t.Buff) err", err)
            return
        }
    
        err = json.Unmarshal(t.Buff[:pkgLen], &mes)
        if err != nil {
            fmt.Println("ReadPKG() ->> json.Unmarshal(t.Buff[:pkgLen], &mes) err", err)
            return
        }
    
        return mes, err
    }
    

    在修改后的代码中,我们在读取数据之前使用SetReadDeadline()方法设置了一个超时时间。如果在指定时间内没有数据可读,Read()函数会返回一个错误,我们可以捕捉并终止程序的阻塞状态。

    需要注意的是,这只是一个简单的解决方案,如果出现其他问题,还需要进一步进行排查。如果问题依然存在,请提供更多的信息以便进一步分析和解决。


    如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^
    评论

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 11月28日
  • 创建了问题 11月27日