2 回答

TA貢獻1712條經(jīng)驗 獲得超3個贊
你必須小心你正在做的事情。
避免數(shù)據(jù)競爭(完成變量由兩個不同的例程讀取/寫入,沒有同步機制)https://go.dev/doc/articles/race_detector
每次程序開始向新客戶端發(fā)送消息時,不要制作新的撥號器。這將打開一個新的本地地址并使用它將其發(fā)送給客戶端??蛻舳藢牧硪粋€地址接收消息,通常應該忽略這些消息,因為它沒有啟動與該遠程的任何交換。
不要將客戶端生命周期與程序上下文生命周期混淆。在提供的代碼中,客戶端發(fā)送停止消息將觸發(fā)整個程序的取消功能,它將停止所有客戶端。為每個客戶端創(chuàng)建一個新的上下文,派生自程序上下文,在收到停止消息時取消相關(guān)的客戶端上下文。
UDP conns 由所有客戶端共享,不能因為程序正在為客戶端提供服務而停止偵聽傳入的數(shù)據(jù)包。IE 調(diào)用
generateMessageToUDP
應該在另一個例程中執(zhí)行。
以下是針對這些評論的修訂版。
添加Avar peers map[string]peer
以將遠程地址與上下文匹配。類型peer
定義為struct {stop func();since time.Time}
。在接收到開始消息后,將與派生上下文一起peer
添加到 中。然后,新客戶端將在不同的例程中提供服務,該例程綁定到新創(chuàng)建的上下文和服務器套接字。收到停止消息后,程序執(zhí)行查找,然后取消關(guān)聯(lián)的對等上下文并忘記對等。map
pctx, pcancel := context.WithCancel(ctx)
go generateMessageToUDP(pctx, udpServer, addr)
peer, ok := peers[addr.String()]
peer.stop(); delete(peers, addr.String())
package main
import (
"context"
"fmt"
"math/rand"
"net"
"time"
)
func generateMessageToUDP(ctx context.Context, conn *net.UDPConn, addr *net.UDPAddr) {
fmt.Println("Generating message to UDP client", addr)
go func() {
for i := 0; ; i++ {
RandomInt := rand.Intn(100)
d := []byte(fmt.Sprintf("%d", RandomInt))
conn.WriteTo(d, addr)
time.Sleep(time.Second * 1)
}
}()
<-ctx.Done()
fmt.Println("Stopping writing to UDP client", addr)
}
//var addr *net.UDPAddr
//var conn *net.UDPConn
func main() {
fmt.Println("Hi this is a UDP server")
udpServer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 5010})
if err != nil {
fmt.Println("Error: ", err)
}
defer func(udpServer *net.UDPConn) {
err := udpServer.Close()
if err != nil {
fmt.Println("Error in closing the UDP Connection: ", err)
}
}(udpServer)
// create a buffer to read data into
type peer struct {
stop func()
since time.Time
}
peers := map[string]peer{}
buffer := make([]byte, 1024)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for {
// read the incoming connection into the buffer
n, addr, err := udpServer.ReadFromUDP(buffer)
if err != nil {
fmt.Println("Error: ", err)
}
fmt.Println("Received ", string(buffer[0:n]), " from ", addr)
if string(buffer[0:n]) == "stop" {
fmt.Println("Stopped listening")
peer, ok := peers[addr.String()]
if !ok {
continue
}
peer.stop()
delete(peers, addr.String())
continue
} else if string(buffer[0:n]) == "start" {
peer, ok := peers[addr.String()]
if ok {
continue
}
pctx, pcancel := context.WithCancel(ctx)
peer.stop = pcancel
peer.since = time.Now()
peers[addr.String()] = peer
// send a response back to the client
_, err = udpServer.WriteToUDP([]byte("Hi, I am a UDP server"), addr)
if err != nil {
fmt.Println("Error: ", err)
}
// start a routine to generate messages to the client
go generateMessageToUDP(pctx, udpServer, addr)
} else if string(buffer[0:n]) == "ping" {
peer, ok := peers[addr.String()]
if !ok {
continue
}
peer.since = time.Now()
peers[addr.String()] = peer
} else {
fmt.Println("Unknown command")
}
for addr, p := range peers {
if time.Since(p.since) > time.Minute {
fmt.Println("Peer timedout")
p.stop()
delete(peers, addr)
}
}
}
}
-- go.mod --
module play.ground
-- client.go --
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
fmt.Println("Hello, I am a client")
// Create a new client
localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:5011")
client3, err := net.DialUDP("udp", localAddr, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 5010})
if err != nil {
fmt.Println(err)
return
}
defer client3.Close()
var n int
n, err = client3.Write([]byte("start"))
if err != nil {
fmt.Println(err)
return
}
log.Println(n)
now := time.Now()
b := make([]byte, 2048)
for time.Since(now) < time.Second*10 {
n, addr, err := client3.ReadFrom(b)
fmt.Println(n, addr, err)
if err != nil {
fmt.Println(err)
continue
}
if addr.String() == "127.0.0.1:5010" {
m := b[:n]
fmt.Println("message:", string(m))
}
}
fmt.Println("Sending stop message")
_, err = client3.Write([]byte("stop"))
if err != nil {
fmt.Println(err)
}
}
在
go func() {
for i := 0; ; i++ {
RandomInt := rand.Intn(100)
d := []byte(fmt.Sprintf("%d", RandomInt))
conn.WriteTo(d, addr)
time.Sleep(time.Second * 1)
}
}()
我將在上下文通道上寫入缺失的選擇作為練習留給讀者,以確定例程是否應該退出。

TA貢獻1776條經(jīng)驗 獲得超12個贊
好的,我在服務器上做了一個簡單的修改,并在創(chuàng)建上下文之前添加了一個標簽 Start,當我取消上下文時,我添加了 goto 標簽。這意味著當任務被取消時,它將再次創(chuàng)建上下文并開始執(zhí)行其工作
- 2 回答
- 0 關(guān)注
- 144 瀏覽
添加回答
舉報