Go 在具有取消功能的UDP消息上启动多个计时器
我正在监听UDP上的消息。我们有这样宣布自己的设备。他们还说什么时候发布下一个公告。如果这种情况没有发生,我们假设一个设备已经不见了 我想列出当前网络中的设备列表。我想添加新设备,删除那些我没有听说过的设备 这是我到目前为止得到的 1) 我有一个内存数据库,可以保存所有设备Go 在具有取消功能的UDP消息上启动多个计时器,go,udp,goroutine,Go,Udp,Goroutine,我正在监听UDP上的消息。我们有这样宣布自己的设备。他们还说什么时候发布下一个公告。如果这种情况没有发生,我们假设一个设备已经不见了 我想列出当前网络中的设备列表。我想添加新设备,删除那些我没有听说过的设备 这是我到目前为止得到的 1) 我有一个内存数据库,可以保存所有设备 func NewDB() *DB { return &DB{ table: make(map[string]Announcement), } } type DB struct {
func NewDB() *DB {
return &DB{
table: make(map[string]Announcement),
}
}
type DB struct {
mutex sync.Mutex
table map[string]Announcement
}
func (db *DB) Set(ip string, ann Announcement) {
db.mutex.Lock()
defer db.mutex.Unlock()
db.table[ip] = ann
}
func (db *DB) Delete(ip string) {
db.mutex.Lock()
defer db.mutex.Unlock()
delete(db.table, ip)
}
func (db *DB) Snapshot() map[string]Announcement {
db.mutex.Lock()
defer db.mutex.Unlock()
return db.table
}
2) 我有一个web服务器,它将这个数据库服务于我的JavaScript前端
http.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(db.Snapshot())
})
// start server
go func() {
log.Fatal(http.ListenAndServe(":8085", nil))
}()
3) 最后,我正在监听UDP消息。每当一个新设备被添加到db中时,我也会创建一个具有提供的超时的新计时器(这里我只是将其设置为10秒)。当新消息到达时,我检查现有计时器,当它存在时停止它,如果不再发送消息,则再次启动它以清除设备
然而,我并没有真正工作。AfterFunc
经常被调用。尽管设备仍在网络中,但它已从mydb
中删除。有什么想法吗
// some global variable
var (
timers = map[string]*time.Timer{}
)
for {
// create new buffer
b := make([]byte, 1500)
// read message from udp into buffer
n, src, err := conn.ReadFromUDP(b)
if err != nil {
panic(err)
}
// convert raw json bytes to struct
var ann Announcement
if err := json.Unmarshal(b[:n], &ann); err != nil {
panic(err)
}
// add announcement to db
ip := src.IP.String()
db.Set(ip, ann)
// check for existing timer
timer, ok := timers[ip]
if ok {
log.Println("stopping timer", ip)
// stop existing timer
timer.Stop()
}
// start new timer for device
timer = time.AfterFunc(time.Second*10, func() {
log.Println("time after func", ip)
delete(timers, ip)
db.Delete(ip)
})
// store timer in timers db
timers[ip] = timer
time.Sleep(250 * time.Millisecond)
}
我认为您遇到的问题可能与您在
AfterFunc
func
中捕获的ip
变量的值有关
timer = time.AfterFunc(time.Second*10, func() {
log.Println("time after func", ip)
delete(timers, ip)
db.Delete(ip)
})
从该代码中,在ip
上的delete将在ip
变量这个计时器过期时被调用。因此,如果同时您从另一个具有不同IP的设备接收到一个数据包,则将删除该数据包
正在发生的情况的示例:
- 第二个1:具有IP 1.2.3.4的设备发送UDP数据包<代码>ip=1.2.3.4,
调用函数后,启动10秒计时器
- 第二个3:具有IP 4.5.6.7的设备发送UDP数据包。现在,
,ip=4.5.6.7
被调用,10秒计时器启动AfterFunc
- 第二个10:调用删除
变量当前值的函数,删除设备ip
4.5.6.7
- 秒13:秒计时器超时,我们再次尝试删除
4.5.6.7
1.2.3.4
的设备永远不会被删除
您可以通过创建一个接受参数并返回当前参数值为func()的函数来修复此问题
timer = time.AfterFunc(time.Second*10, func(ip string) func() {
return func() {
log.Println("time after func", ip)
delete(timers, ip)
db.Delete(ip)
}
}(ip))
更简单的工作示例:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Capturing value of i at the moment of execution of func()")
for i := 0; i < 5; i++ {
afterFuncTimer := time.AfterFunc(time.Second*2, func() {
fmt.Printf("AfterFunc() with %v\n", i)
})
defer afterFuncTimer.Stop()
}
time.Sleep(5 * time.Second)
fmt.Println("Capturing value of i from the loop")
for i := 0; i < 5; i++ {
afterFuncTimer := time.AfterFunc(time.Second*2, func(i int) func() {
return func() {
fmt.Printf("AfterFunc() with %v\n", i)
}
}(i))
defer afterFuncTimer.Stop()
}
time.Sleep(5 * time.Second)
}
主程序包
进口(
“fmt”
“时间”
)
func main(){
fmt.Println(“在执行func()时捕获i的值”)
对于i:=0;i<5;i++{
afterFuncTimer:=time.AfterFunc(time.Second*2,func(){
fmt.Printf(“带有%v\n”的AfterFunc(),i)
})
defer afterFuncTimer.Stop()
}
时间。睡眠(5*时间。秒)
fmt.Println(“从循环中捕获i的值”)
对于i:=0;i<5;i++{
afterFuncTimer:=time.AfterFunc(time.Second*2,func(i int)func(){
返回func(){
fmt.Printf(“带有%v\n”的AfterFunc(),i)
}
}(i) )
defer afterFuncTimer.Stop()
}
时间。睡眠(5*时间。秒)
}
在Go操场上跑步:对于循环来说也是如此。更简单的解决方案是在循环内部使用
i:=i
。有趣的是,我的代码工作正常,而且AfterFunc
捕获了正确的ip
。然而,我的错误是使用了10秒的测试计时器。一个设备出现了,说我15秒后回来,10秒后就被移除了。当使用真实的设备值时,一切都按预期工作。