162 lines
3.3 KiB
Go
162 lines
3.3 KiB
Go
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:
|
||
}
|
||
}
|
||
}
|