Camera streaming app for the pizero, hobby project.
I want the cam to be sending frames only if there is an active client on the other end.
For this purpose the cam is listening on a port for client keep alive messages. If no messages are received for 5 seconds, camera stops sending frames.
I built the detector but I am not confident I did a really good job. Mainly because timeout detection is not consistent. Cam will detect a timeout somewhere between 5 and 30s.
Advice on how to improve is very much appreciated.
EDIT:
It was pointed out that the code was not working as I intended. Feedback in the comment section helped me fix the inconsistency.
Code sample is now working.
package server
import (
"fmt"
"log"
"net"
"strconv"
"time"
)
type session struct {
Conn *net.UDPConn
Message chan string
Password string
Address chan *net.UDPAddr
}
type monitor struct {
Conn *net.UDPConn
IsLive bool
Kill chan bool
LastMsg time.Time
ListenTick *time.Ticker
MonitorTick *time.Ticker
}
func newMonitor(port string) *monitor {
return &monitor{
Conn: bindAddress(port),
IsLive: false,
Kill: make(chan bool),
ListenTick: time.NewTicker(time.Second),
MonitorTick: time.NewTicker(time.Second),
}
}
func (m *monitor) listen() {
//program hangs here if no ticker is used. why???
for range m.ListenTick.C {
if !m.IsLive {
continue
}
buffer := make([]byte, 10)
n, err := m.Conn.Read(buffer)
if err != nil {
log.Println(err)
}
if len(buffer[0:n]) > 0 {
m.LastMsg = time.Now()
}
}
}
func (m *monitor) detectTimeOut() {
for range m.MonitorTick.C {
if !m.IsLive {
continue
}
if time.Since(m.LastMsg) > time.Second*5 {
fmt.Println("No response")
m.IsLive = false
m.Kill <- true
}
}
}
func Run(port, password *string, frame <-chan []byte, done <-chan bool) {
session := newSession(port, password)
go session.listenForClients()
fmt.Println("Listening for clients on: ", session.Conn.LocalAddr())
defer session.Conn.Close()
heartBeatPort := stringPortToInt(*port)
monitor := newMonitor(fmt.Sprintf(":%v", heartBeatPort+1))
go monitor.listen()
go monitor.detectTimeOut()
defer monitor.Conn.Close()
var address *net.UDPAddr
for {
select {
case <-done:
fmt.Println("Stopping server")
return
case address = <-session.Address:
fmt.Println("New client authenticated from address: ", address)
monitor.IsLive = true
monitor.LastMsg = time.Now()
case f := <-frame:
switch address {
case nil:
continue
default:
_, err := session.Conn.WriteToUDP(f, address)
if err != nil {
log.Println(err)
}
}
case <-monitor.Kill:
fmt.Println("Client timed out")
address = nil
default:
continue
}
}
}
func newSession(port, password *string) *session {
return &session{
Conn: bindAddress(*port),
Message: make(chan string),
Password: *password,
Address: make(chan *net.UDPAddr),
}
}
func (s session) listenForClients() {
for {
buf := make([]byte, 1024)
n, addr, err := s.Conn.ReadFromUDP(buf)
if err != nil {
log.Println(err)
}
m := buf[0:n]
if s.Password == "" {
s.Address <- addr
continue
}
if s.authenticate(string(m), addr) {
s.Address <- addr
}
}
}
func (s *session) authenticate(message string, address *net.UDPAddr) bool {
if s.Password == message {
_, _ = s.Conn.WriteToUDP([]byte("ok"), address)
return true
}
return false
}
func bindAddress(port string) *net.UDPConn {
laddr, err := net.ResolveUDPAddr("udp", port)
if err != nil {
log.Fatal(err)
}
listenAddr, err := net.ListenUDP("udp", laddr)
if err != nil {
log.Fatal(err)
}
return listenAddr
}
func stringPortToInt(number string) int {
n, err := strconv.Atoi(number[1:])
if err != nil {
log.Fatal(err)
}
return n
}
for range m.Tick.C
read from the same channel : only one of them will get the generated ticks) \$\endgroup\$detector
loop. I edited the code on my question to reflect it. Code is now working as expected. \$\endgroup\$