Files
MARTe_IO_Components/Client/WebUI/udpclient.go
T
Martino Ferrari e3389f932b FIrs
2026-05-15 17:42:14 +02:00

162 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"log"
"net"
"time"
)
const (
silenceTimeout = 5 * time.Second
reconnectDelay = 2 * time.Second
readBufSize = 65536
)
// UDPClient manages the UDP connection to the MARTe2 streamer.
type UDPClient struct {
serverAddr string
localPort int
hub *Hub
stopCh chan struct{}
}
// NewUDPClient creates a UDPClient. Call Run() in a goroutine.
func NewUDPClient(serverAddr string, localPort int, hub *Hub) *UDPClient {
return &UDPClient{
serverAddr: serverAddr,
localPort: localPort,
hub: hub,
stopCh: make(chan struct{}),
}
}
// Stop asks the client to disconnect and exit.
func (u *UDPClient) Stop() {
close(u.stopCh)
}
// Run is the main loop; it reconnects automatically if the server goes silent.
func (u *UDPClient) Run() {
for {
select {
case <-u.stopCh:
return
default:
}
log.Printf("udp: connecting to %s (local port %d)", u.serverAddr, u.localPort)
err := u.runSession()
if err != nil {
log.Printf("udp: session ended: %v", err)
}
// Check stop before sleeping.
select {
case <-u.stopCh:
return
case <-time.After(reconnectDelay):
}
}
}
// runSession opens a UDP socket, sends CONNECT, reads data until the server
// goes silent or an error occurs.
func (u *UDPClient) runSession() error {
localAddr := &net.UDPAddr{Port: u.localPort}
conn, err := net.ListenUDP("udp4", localAddr)
if err != nil {
return err
}
defer conn.Close()
serverAddr, err := net.ResolveUDPAddr("udp4", u.serverAddr)
if err != nil {
return err
}
// Send CONNECT.
if _, err := conn.WriteToUDP(BuildConnectPacket(), serverAddr); err != nil {
return err
}
log.Printf("udp: sent CONNECT")
reassembler := NewReassembler(2 * time.Second)
buf := make([]byte, readBufSize)
var currentSigs []SignalInfo
for {
// Silence timeout.
conn.SetReadDeadline(time.Now().Add(silenceTimeout))
n, _, err := conn.ReadFromUDP(buf)
arrivalTime := time.Now()
if err != nil {
// Send DISCONNECT best-effort before returning.
conn.WriteToUDP(BuildDisconnectPacket(), serverAddr)
return err
}
if n < HeaderSize {
log.Printf("udp: short datagram (%d bytes), skipping", n)
continue
}
hdr, err := ParseHeader(buf[:n])
if err != nil {
log.Printf("udp: parse header: %v", err)
continue
}
payload := make([]byte, n-HeaderSize)
copy(payload, buf[HeaderSize:n])
complete, ok := reassembler.AddFragment(hdr, payload)
if !ok {
continue
}
switch hdr.Type {
case PktConfig:
sigs, err := ParseConfig(complete)
if err != nil {
log.Printf("udp: parse config: %v", err)
continue
}
currentSigs = sigs
log.Printf("udp: received CONFIG (%d signals)", len(sigs))
u.hub.UpdateConfig(sigs)
case PktData:
if len(currentSigs) == 0 {
// We haven't received a CONFIG yet ignore.
continue
}
sample, err := ParseData(complete, currentSigs, arrivalTime)
if err != nil {
log.Printf("udp: parse data: %v", err)
continue
}
u.hub.PushData(sample)
case PktACK:
log.Printf("udp: received ACK (counter=%d)", hdr.Counter)
case PktDisconnect:
log.Printf("udp: server sent DISCONNECT")
return nil
default:
log.Printf("udp: unknown packet type %d", hdr.Type)
}
// Check stop request.
select {
case <-u.stopCh:
conn.WriteToUDP(BuildDisconnectPacket(), serverAddr)
return nil
default:
}
}
}