先让我们看一段tcp 的socket代码:
l, e := net.Listen("tcp", ":9090") //监听
if e != nil {
fmt.Println(e)
return
}
defer l.Close()
for {
c, e := l.Accept()
if e != nil {
fmt.Println(e)
c.Close()
break
}
buf := make([]byte, 10)
for {
//t := make([]byte, 10)
_, e := c.Read(buf)
//t=t[:n]
//buf = append(buf,t...)
if e == io.EOF {//对端关闭退出
break
}
fmt.Println("===")
}
fmt.Println(string(buf))
}
这段代码是我们在用go做socket的时候经常能百度出的一种,基本上搜出来的和这个都大同小异。
这段代码是一个服务端部分的代码,但在读写上无论在服务端还是客户端基本都是一致的。
这段代码大致意思是,建立一个socket监听,同时循环读取客户端发来的消息。(因为我们并不知道客户端会发来多少数据)
首先让我们看第一个错误,也是一个忽视点。重点就在这句:
if e == io.EOF {//对端关闭退出
break
}
这里说的很清楚,对端关闭后,才会EOF。那么问题来了,如果对端不关闭会怎么样呢?
首先让我们看看不关闭会输出什么:
可以看到他陷入了死循环永远也出不来了。那如何解决这个问题呢?
其实很简单,read方法会返回读取的字节和错误,我们只需要判断当读取字节数为0就让为是一次读取完毕就可以了。代码如下:
for {
//t := make([]byte, 10)
n, e := c.Read(buf) //这里为修改的地方
//t=t[:n]
//buf = append(buf,t...)
if n==0 || e == io.EOF {//这里为修改的地方
break
}
fmt.Println("===")
}
fmt.Println(string(buf))
修改成这样后再让我们看一下输出:
我在客户端发送的是,我是 tcp socket 测试。但是我们看到接收的字符不全还有乱码。这是为什么呢?让我们该一句代码再看一下,这次我们把buf的长度输出一下:
可以看到还是最初的10,并没有像我们想的一样进行扩容。这就是为什么我们会有乱码,因为我们3次数据都是在一个上面进行覆盖,并没有在后面继续写。大家也应该注意到了,我上面有2行注释的代码:
buf := make([]byte, 0)
for {
//t := make([]byte, 10)
_, e := c.Read(buf)
//t=t[:n]
//buf = append(buf,t...)
if e == io.EOF {//对端关闭退出
break
}
fmt.Println("===")
}
fmt.Println(string(buf))
我们只需要把注释打开,同时每次读的数据放到t中即可。这就相当于我们用t作为一个缓存区,每次缓存的数据放到buf中,最终拼接为一次的数据,同时把buf的初始0个值。
最终代码为:
l, e := net.Listen("tcp", ":9090")
if e != nil {
fmt.Println(e)
return
}
defer l.Close()
for {
c, e := l.Accept()
if e != nil {
fmt.Println(e)
c.Close()
break
}
buf := make([]byte, 0)
for {
t := make([]byte, 10)
n, e := c.Read(t)
t=t[:n]
buf = append(buf,t...)
if n == 0 || e == io.EOF {
break
}
fmt.Println("===")
}
fmt.Println(string(buf),len(buf))
}
输出结果为 :
但是这样就完了吗?上述的结果只是建立在,客户端发送一次数据后中断的情况下。其实服务端在read拿不到数据就是阻塞。等客户端异常中断后就会抛出wsarecv: An existing connection was forcibly closed by the remote host.
当我们客户端循环发送数据就会出现这种情况:
也就是你永远也拿不到0,你的数据会无限的拼到一个buf,
这样我就拿到了一个预期中的结果。下面让我们总结一下,这期注意说了3个问题:
1.当对端不关闭时,异常退出,服务端判断为e==io.EOF时会出现无法退出循环的问题。
2.放一个buf会出现数据被覆盖问题。解决方法是:建立一个临时的byte数组作为缓存区,然后统一放到buf中。同时要对缓存区做处理,因为最后一次取出的数据一般都放不满,会多很多0。
3..如果客户端一直保持连接,会出现一直阻塞不退出问题。在这点上可以设置超时时间,
注: 在使用过程中,e抛出的错误可能不是eof,如果客户端异常断开可能是wsarecv: An existing connection was forcibly closed by the remote host.
所以 e == io.EOF 最好换成 e != nil