
最近开始学习 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。 |