最近开始学习 go,照着教程写了一个简单的 tcp server 的代码,实现了 5 秒客户端还没有任何的数据发送,就自动关闭当前连接。这个地方我用的是 time.afterfunc 来实现的 运行起来也达到了预期,但不知道为什么,明明当前连接已经关闭,处理连接的函数已经返回,但是里面的这个 time.afterfunc 包裹的函数继续执行,真是见鬼了。下面是代码
package main
import (
"fmt"
"net"
"time"
)
func main() {
l, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("listen 报错:", err)
return
}
defer l.Close()
fmt.Println("listen ok")
var i int
for {
if conn, err := l.Accept(); err != nil {
fmt.Println("accept 报错:", err)
break
} else {
i++
fmt.Printf("%d: 接收到新的连接\n", i)
go fmt.Println(handleconntimeout(conn, 5))
}
}
}
func handleconntimeout(conn net.Conn, timeout int) int {
b := make([]byte, 1024)
var closetimer *time.Timer
f := realfunc(conn)
closetimer = time.AfterFunc(time.Duration(timeout)*time.Second, f)
defer conn.Close()
for n, err := conn.Read(b); err == nil; n, err = conn.Read(b) {
closetimer.Reset(time.Duration(timeout) * time.Second)
fmt.Printf("收到来自 %s 的一共 %d 数量字节\n", conn.RemoteAddr(), n)
fmt.Println(string(b[:n]))
}
fmt.Println("连接报错")
return 10000
}
func closeconn(conn net.Conn) {
fmt.Println("时间到,关闭连接")
conn.Close()
}
func realfunc(conn net.Conn) func() {
return func() {
closeconn(conn)
}
}
我启动服务端,用客户端连接没有问题,等待 5 秒钟,服务端这边一切如预期的一样, 第一行出现:时间到,关闭连接 第二行出现:连接报错 第三行打印出 handleconntimeout 这个函数的返回值 1000
诡异的是,我这边如果主动关闭客户端,服务端出现的是 第一行打印出:连接报错 第二行打印出:handleconntimeout 的返回值 1000 我本来以为后面就不应该再继续出现任何东西了 结果过了 5 秒第三行打印出 时间到,连接关闭
奇怪了,这个明明应该在 handleconntimeout 里面存在的动作,
time.AfterFunc(time.Duration(timeout)*time.Second, f) 这个延后执行的函数,不是应该随着 handleconntimeout 的返回,不应该继续执行的啊 怎么后来还在继续执行 由于是初学 golang,高手懂的给指点指点。 谢谢
1
yuikns 2019-01-17 05:07:21 +08:00
return 10000 前加上
closetimer.Stop() |
2
yuikns 2019-01-17 05:11:23 +08:00
另外,若你只是想要个 timeout,其实还可以这么写:
func handleconntimeout(conn net.Conn, timeout int) int { b := make([]byte, 1024) defer conn.Close() conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second)) for n, err := conn.Read(b); err == nil; n, err = conn.Read(b) { fmt.Printf("收到来自 %s 的一共 %d 数量字节\n", conn.RemoteAddr(), n) fmt.Println(string(b[:n])) conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second)) } return 10000 } |
3
zzlettle OP @yuikns 是的,我就是最后加上 closetimer.Stop()来避免。但这个还么做,还是没有让我搞清楚,为什么外面的函数已经返回了,里面的还能继续运行。
|
4
yuikns 2019-01-17 06:31:35 +08:00
@zzlettle 因为没有回收啊。
帮你找个源码: ------ func AfterFunc(d Duration, f func()) *Timer { t := &Timer{ r: runtimeTimer{ when: when(d), f: goFunc, arg: f, }, } startTimer(&t.r) return t } func goFunc(arg interface{}, seq uintptr) { go arg.(func())() } ----- closetimer := time.AfterFunc(time.Duration(timeout)*time.Second, f) 这个就相当于开一个 goroutine, 等到 timer 到了然后执行一下。这个要是回去就把 goroutine 里面的东西全给撤了,那 go 可以废了... |
5
yuikns 2019-01-17 06:42:57 +08:00
哦,刚才的回复结论没啥问题,说明有问题。
startTimer 的实现参考这个链接: https://golang.org/src/runtime/time.go#L110 读源码可知它就是在全局 goroutine 里面加上了一个 timer。 上述那个似乎是在暗示当场 go func() 。开个自定义的。但其实不是。它是先检查已有的 timer。若有多个,会共享一个 routine。然后到它的时候,回调执行 go arg(xxx). 这样可以不阻塞别的 timer。 |