Go 在具有取消功能的UDP消息上启动多个计时器

Go 在具有取消功能的UDP消息上启动多个计时器,go,udp,goroutine,Go,Udp,Goroutine,我正在监听UDP上的消息。我们有这样宣布自己的设备。他们还说什么时候发布下一个公告。如果这种情况没有发生,我们假设一个设备已经不见了 我想列出当前网络中的设备列表。我想添加新设备,删除那些我没有听说过的设备 这是我到目前为止得到的 1) 我有一个内存数据库,可以保存所有设备 func NewDB() *DB { return &DB{ table: make(map[string]Announcement), } } type DB struct {

我正在监听UDP上的消息。我们有这样宣布自己的设备。他们还说什么时候发布下一个公告。如果这种情况没有发生,我们假设一个设备已经不见了

我想列出当前网络中的设备列表。我想添加新设备,删除那些我没有听说过的设备

这是我到目前为止得到的

1) 我有一个内存数据库,可以保存所有设备

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
经常被调用。尽管设备仍在网络中,但它已从my
db
中删除。有什么想法吗

// 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
    AfterFunc
    被调用,10秒计时器启动
  • 第二个10:调用删除
    ip
    变量当前值的函数,删除设备
    4.5.6.7
  • 秒13:秒计时器超时,我们再次尝试删除
    4.5.6.7
因此,IP
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秒后就被移除了。当使用真实的设备值时,一切都按预期工作。