开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:海量用户通讯系统-服务器接收消息2】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9801
海量用户通讯系统-服务器接收消息2
一、反复出现readPkg的原因及解决方法
1、反复出现readPkg的原因:
因为message.go的server这个地方是一直在进for循环的读取,读取包包,读的是-err =conn.Read(buf[:4])这个地方,读的时候它阻塞这个的前提是对方也就是conn一直是有效的,就是大家都持有的一个连接,才会在这里阻塞。也就是conn没有关闭的情况下,才会阻塞。如果说任意一方,不论是客户端还是服务端有一方关闭这个连接,这个就会立即停止。同理如果客户端关闭了conn则,不会阻塞。若不会堵塞就会出现刚才的情况,反复的打出”读取客户端发送的数据...
readPkg err= read pkg header error
Mes= < >”
反复打出这段话的原因是如果不堵塞就读不到东西,读不到东西就报一个错误,又return到下面这里
mes, err := readPkg(conn)
if err != nil {
fmt.PrintIn(“readPkg err=”err)
}
return到这个错误后,又说readPkg err,然后没有进行任何处理,又去进行读取,又返回到上面这个地方,结果发现又读不到东西,把错误抛出,又返回下面那处,就造成了这种现象。
2、解决反复出现现象的方法:
当以下这个代码读的时候
mes, err := readPkg(conn)
if err != nil {
fmt.PrintIn(“readPkg err=”err)
}
已经读错了,有两个方案。一个是双方均退出,就可以做一个判断。判断的方法是
mes, err := readPkg(conn)
if err != nil {
if err == io.EOF
fmt.PrintIn(“客户端退出,服务器端也正常退出“)
return
} else {
fmt.PrintIn(“readPkg err=”err)
return
}
fmt.PrintIn(“mes=”,mes)
这种判断分两种情况一种是服务端退出了,一种是其他情况,这两种方法均能保证代码没问题。这两个均做完后,EOF那里的原因是没有导io包。将io包导入,如下
package main
import(
“fmt”
“net”
“go_code/chatroom/common/message”
“encoding/binary”
“encoding/json”
“errors’
“io”
将io包导入后,在客户端login这处也先暂时不休眠。休眠测试已经完成了,休眠20秒后会自动关掉,接着测试一下。首先将server端编译一下,然后客户端在编译一下,接着启动一下server端,在启动conn端,接着看效果,假设输入100,密码是tom,接着运行,效果如图
由观察可得它在读取当中,因为现在不进行发送也没有退出,相对于卡在了read那个地方,因为连接都还持有,没有将其关闭,所以这个架构20秒就会关闭。20秒后的提示信息是”readPkg err= read pkg header error”,但是没有将信息打出来。那么这个错误便还不是那个错误,那么证明刚刚的分析还存在一定问题。接着重复刚才的操作,再进行一次,更改原来的代码,如下
package main
import(
“fmt”
“net”
“go_code/chatroom/common/message”
“encoding/binary”
“encoding/json”
—“errors’
“io”
接着在使其运行一次,输入100,密码abc,再次运行观察结果。与原猜想相同,出现了客户端退出,服务器端也退出的提示信息。
3、代码实现
Client/login.go做了修改
//发送了消息本身
-,err = conn.write(data)
if err != nil {
fmt.PrintIn(“conn.write(data) fail”, err)
return
}
//休眠20
time.Sleep(20 * time.Second)
fmt.PrintIn(“休眠了20..”)
//这里还需要处理服务端返回的消息
}
server/main.go
修改//处理客户端的通讯
func process(conn net.conn)
//这里需要延时关闭conn
defer conn.Close()
//循环的客户端发送的信息
for {
//这里我们将数据包,直接封装一个函数readPkg,返回Message, Err
mes,err := readPkg(conn)
if err != nil {
if err == io.EOF {
fmt.PrintIn(“客户端退出,服务器端也退出”)
return
} else
fmt.PrintIn(“readPkg err=”, err)
return
}
}
fmt.PrintIn(“mes=”, mes)
}
}
将读取包的任务封装到了一个函数中readPkg()
func readPkg(conn net.Conn) (mes message.Message, err error) {
buf := make([]byte,8096)
fmt.PrintIn(“读取客户端发送的数据...“)
//conn.Read 在conn没有被关的情况下,才会阻塞
//如果客户端关闭了conn则,就不会阻塞
-,err = conn.Read(buf[:4])
if err != nil {
//err = errors.New(“read pkg header error”)
return
}
//根据buf[:4]转成一个uint32类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(buf[0:4])
//根据pkgLen该消息内容
n, err := conn.Read(buf[:pkgLen])
if n != int(pkgLen) | | err != nil {
//err =errrors.New(“read pkg body errror”)
Return
}
//把pkgLen反序列化成-> message.Message
//技术就是一层窗户纸 &mes
err = json.Unmarshal(buf[:pkgLen], &mes)
if err != nil {
fmt.PrintIn(“json.Unmarsha err”, err)
return
}
return
}