Обмен UDP-сообщениями в Go
Полный обмен требует двух ролей: кто-то
слушает порт, кто-то шлет датаграмму и
ждет ответ. В учебном примере обе части
можно держать в одном файле: в отдельной
горутине крутится прием и ответ, а из
main после короткой паузы уходит
запрос и читается ответ.
Порядок запуска важен: слушатель должен успеть открыть порт до того, как клиентская часть пошлет первый пакет. Пауза в несколько миллисекунд или канал готовности здесь обычнее, чем надежда на удачную гонку.
Соберем вопрос "ask" и ответ "ans"
на "127.0.0.1:9091":
package main
import (
"fmt"
"net"
"time"
)
func serve(conn *net.UDPConn, ready chan struct{}) {
close(ready)
buf := make([]byte, 64)
n, remote, err := conn.ReadFromUDP(buf)
if err != nil {
return
}
if string(buf[:n]) == "ask" {
conn.WriteToUDP([]byte("ans"), remote)
}
}
func main() {
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:9091")
if err != nil {
fmt.Println("resolve error")
return
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println("listen error")
return
}
defer conn.Close()
ready := make(chan struct{})
go serve(conn, ready)
<-ready
client, err := net.Dial("udp", "127.0.0.1:9091")
if err != nil {
fmt.Println("dial error")
return
}
defer client.Close()
_, err = client.Write([]byte("ask"))
if err != nil {
fmt.Println("write error")
return
}
buf := make([]byte, 64)
client.SetReadDeadline(time.Now().Add(time.Second))
n, err := client.Read(buf)
if err != nil {
fmt.Println("read error")
return
}
fmt.Println(string(buf[:n]))
}
На экране "ans". Канал ready
закрывается, когда ветка-слушатель дошла
до ожидания пакета; только после этого
main шлет "ask".
Тот же сценарий можно разнести на две программы: одна только слушает и отвечает, вторая только шлет и читает. Тогда перед запуском клиента сервер уже должен работать в отдельном окне терминала:
package main
import (
"fmt"
"net"
)
func main() {
addr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:9091")
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println("listen error")
return
}
defer conn.Close()
fmt.Println("listening")
buf := make([]byte, 64)
n, remote, err := conn.ReadFromUDP(buf)
if err != nil {
fmt.Println("read error")
return
}
conn.WriteToUDP([]byte("ans"), remote)
fmt.Println("got", string(buf[:n]))
}
Дано сообщение запроса:
question := []byte("time?")
В ветке-слушателе на "127.0.0.1:9091"
примите пакет и ответьте "now";
из main отправьте question
и выведите ответ.
Дан фрагмент клиента:
client, _ := net.Dial("udp", "127.0.0.1:9091")
client.Write([]byte("ask"))
buf := make([]byte, 32)
n, _ := client.Read(buf)
Скажите, почему Read может зависнуть,
если серверную часть не запустить раньше
и не ответить на порт клиента.
Скажите, зачем в одном файле перед отправкой
ждут сигнал от ветки-слушателя, а не
шлют пакет сразу после go.
Файлы и первые предложения программ такие.
В main.go из первого примера первая
строка на Go это package main; первый
вывод клиента после обмена печатает ответ
сервера. В server.go первая строка
та же package main, а первое
сообщение на экран это "listening",
когда порт уже открыт.